网络摄像头是怎么被“发现→爆破→控制”的:pwneye 的 ONVIF/RTSP 攻击链模拟拆解与四条检测线
原创
赛博57库
赛博57库
赛博57库
2026年11月5日 05:00
广东
去阅读
| 栏目 · 攻防技术 网络摄像头是怎么被“发现→爆破→控制”的:pwneye 的 ONVIF/RTSP 攻击链模拟拆解与四条检测线 未认证对时探针 · WS-Discovery 组播发现 · 404/454 误报 · 四条检测线 |
外网暴露面清理做完之后,很多单位会在清单上留下一个盲区:摄像头。它不在 CMDB 里,没有负责人,却常年开着 80 和 554 两个端口,凭据还是出厂默认的那一组。
2026 年 9 月,GitHub 上的 pwneye 更新到 1.3.2(最后一次提交在 9 月 6 日)。它把 ONVIF 发现、鉴权测试、媒体配置枚举、PTZ 控制、OSD 文字替换、RTSP 路径爆破和取证录制拼成了一条 CLI 流水线。对使用者来说它很省事;对防守方来说,它刚好把\"一台摄像头能被问到哪些东西\"完整地摆了出来。
本文不复述 README。我在本地写了一个 RTSP 摄像头模拟器和一个假 ONVIF 服务,把它的整条攻击链回放了一遍,然后回答三个问题:它在网络上留下的形状是什么、它给出的结论什么时候会错、以及防守方能在哪里提前看到它。
边界先讲清楚:这是一次模拟拆解,没有接触任何真实摄像头。文中的\"跑了\"\"回放了\"\"抓到\"等说法,都指 127.0.0.1 上的模拟环境;ONVIF 侧只验证了指纹请求,onvif-python 依赖在测试里被替换成空壳,其余结论来自源码阅读。所有数字来自代码与数据文件,可以按仓库复现。 |
| 01 · 先看清目标:一台摄像头背后是 ONVIF 与 RTSP 两个协议 |
大部分人把摄像头理解成\"一个网页后台\"。实际上它至少暴露两个面:
•ONVIF(管理面):SOAP over HTTP,走 80/8080/8899 这类端口。设备信息、用户列表、网络配置、媒体 Profile、流地址、PTZ、图像参数、重启和复位,都在这个面上。
•RTSP(流面):554 或厂商自定义端口。DESCRIBE 拿 SDP,SETUP/PLAY 取流。
两个面通常共用一套账号密码,但鉴权实现完全独立。这就是为什么很多设备\"网页后台锁得很死,RTSP 却连试错次数都不限\"——两套代码,两个团队,两种安全水位。
pwneye 的规模能说明这块地有多大:30 个 Python 文件、11,705 行;RTSP 知识库里躺了 466 家厂商、3,221 条连接路径。这不是一个\"顺手写的脚本\",而是一份把厂商差异吃透了的资产地图。
▲ 图 1:一台摄像头实际暴露的是管理面、流面和组播发现面;三者共用凭据,但鉴权、限速、日志各写各的
| 02 · 攻击者第一步不问密码:一条 GetSystemDateAndTime 就能确认目标 |
看清楚这两个面之后,攻击者的第一个动作不是攻击,而是确认目标——这一步连密码都不用问。
pwneye 判断\"这台设备是不是 ONVIF\",只做一件事:向 /onvif/device_service(失败再试 /device_service)POST 一段未认证的 SOAP,内容是 GetSystemDateAndTime,超时 1.5 秒。只要返回 200、401 或 403,就算命中。
我在本机起两个服务做了对照:
| 被测服务 | 返回 | probe_onvif_service 判定 |
| 假 ONVIF 端点(401 + Digest 挑战) | 401 | True |
普通 HTTP 服务(返回 hello) |
200 | True |
第一行符合预期。第二行说明一件事:这套探测的真实含义是\"这个端口上有个 HTTP 服务\",不是\"这是台摄像头\"。
这不是实现缺陷,而是成本取舍——ONVIF 设备对未认证请求的响应码从 200 到 401 到 400 都有,能用一条请求筛掉大部分无关端口,已经够用。但对做资产盘点的防守方,它意味着一个具体误差:拿这类工具统计出来的\"ONVIF 资产数\"会明显偏高,打印机管理页、路由器后台、甚至某些 IoT 网关都会被算进去。
为什么一条未认证请求能得到响应?因为 ONVIF 的默认访问策略里本来就有一个 Anonymous 组,对时、能力发现这类请求被允许匿名访问;而且 WS-Security 的摘要校验需要双方时钟先对齐,设备必须先把时间告诉你。这是设计,不是漏洞。
防守上它仍是一条好线索:正常业务里,几乎没有谁会主动向摄像头发一条 GetSystemDateAndTime。
| 03 · 第二步让设备自己报家门:WS-Discovery 一问,型号全出来 |
比单点探测更省事的是组播发现。pwneye --discover 调用的是 onvif-python 的 ONVIFDiscovery,超时 4 秒、重试 3 次、间隔 0.75 秒,向 239.255.255.250:3702 发 WS-Discovery 的 Probe,设备用 ProbeMatch 回答。
回答里不只有 IP,还有 XAddrs(服务地址)和 scopes:厂商名、硬件型号、设备名、位置。也就是说,攻击者不需要知道你有哪些摄像头,只要在同一广播域里问一声,设备就会把型号报上来。拿到型号之后,默认口令表和该型号的公开 CVE 都能对上号。
现实里的放大器是交换机的组播泛洪。没做摄像头 VLAN 的单位,一次组播能把整栋楼的摄像头、打印机、会议室一体机一起列出来。这也是为什么\"内网很安全\"这句话在摄像头面前经常不成立。
防守动作有三条,成本从低到高:
1. 摄像头单独 VLAN,并在交换机上限制组播;
2. 设备侧关闭 Discovery 模式(部分固件叫 ONVIF Discovery / WS-Discovery);
3. 把这个发现机制反过来用:定期跑一次,结果当作资产台账——它比人工盘点准。
| 04 · 第三步才是试口令:为什么它默认单线程、只认 MD5 摘要 |
确认了目标、拿到了型号,接下来才轮到试口令。这一步的形状,由认证实现决定——看代码比看输出更能理解它的边界。
ONVIF 侧,每一次尝试都是\"新建一个客户端 + 调一次 GetDeviceInformation\",单次超时 3.5 秒;任务是用户名、口令、端口的笛卡尔积。默认数据库是 7 个端口、10 个用户名、11 个口令,最坏情况 770 次组合。命令行 --threads 的默认值是 1——全默认跑一遍,理论耗时要 40 分钟以上。
这个默认值很值得琢磨:单线程慢,但每个端口上只留下一条连接,流量形状干净;调到 10 线程,速度上来了,401 的密度也跟着上来了。速度与隐蔽,在这个工具上是同一个旋钮。
RTSP 侧,我在模拟器上抓到了它发出的原始请求:
| DESCRIBE rtsp://127.0.0.1:35537/live RTSP/1.0 CSeq: 1 User-Agent: pwneye/0.1.0 Accept: application/sdp |
三个细节:
•User-Agent 硬编码成 pwneye/0.1.0,而工具版本是 1.3.2。字符串没跟着版本走,等于给检测留了一个稳定指纹。
• 401 回来后,它解析 WWW-Authenticate,支持 Basic 和 Digest;Digest 只实现 MD5,algorithm 不是 MD5 就直接放弃。
• 因此对启用了 SHA-256 摘要的设备,工具的结论是\"不支持该挑战\",而不是\"口令错误\"。这会让人误判设备\"没法测\",而不是\"已经加固\"。
| 05 · 用假摄像头回放整条链:五条判断里两条是误报 |
前四节讲的是它怎么打,这一节把整条链在假摄像头上回放一遍,看它什么时候会看走眼。同一台 mock 设备,五种情况,probe_rtsp_url 与 rtsp_credentials_valid 的输出:
| 场景 | 设备实际返回 | 工具判定 |
| 无凭据访问有效路径 | 401 Digest | 凭据无效,路径未知 |
| 正确凭据 | 200 + SDP | 凭据有效,流可用 |
| 错误凭据 | 401 | 凭据无效 |
| 错误凭据 + 不存在路径 | 404 | 凭据有效 、路径无效 |
| 错误凭据 + 设备回 454 | 454 | 凭据有效 、路径无效 |
后两行是误报。原因在判定逻辑:认证后只要不是 401,就被当成\"凭据过了,只是路径不对\";而 rtsp_credentials_valid 在\"凭据有效但路径无效\"时直接返回真。
后果是双向的:
• 攻击者的\"命中清单\"里会混进假货,拿着错误口令去连监控客户端,自然连不上;
• 防守方如果用同类工具做口令审计,会得到\"存在默认口令\"的结论,实际可能只是设备对任何路径都回 404。
▲ 图 2:同一台 mock 设备、五条判断,后两条是误报——判定逻辑把“非 401”当成了“凭据有效”
README 自己也留了一句提醒:\"有效的流不一定是有效的画面。\"这句话在模拟回放里得到了验证:判断一台摄像头是否真的被拿到,唯一的证据是拿回 SDP 或首帧,而不是工具打出的那个绿色对勾。
| 06 · 凭据到手后,最危险的不是画面:重启、复位、压黑、改屏显 |
ONVIF 的管理面在拿到凭据后能做的事,比\"看画面\"多得多。pwneye 实现的动作和对应后果:
| 动作 | 用到的调用 | 业务后果 | 可观测点 |
| 盘点设备 | GetDeviceInformation |
型号、固件、序列号 | 一次集中枚举 |
| 读网络配置 | GetNetworkInterfaces |
MAC、网段、DNS/NTP | 一次集中读取 |
| 拉用户表 | GetUsers |
设备上所有账号名 | 后续撞库的靶子 |
| 读日志 | GetSystemLog |
反取证前先看审计记录 | 日志读取行为 |
| 重启 | SystemReboot |
断流、掩盖痕迹、制造\"设备故障\" | 重启前后流中断 |
| 恢复出厂 | SetSystemFactoryDefault |
硬复位:清配置与凭据,回到默认口令 | 配置消失、重新可登录 |
| 转镜头 | PTZ ContinuousMove |
视线移开,等于物理盲区 | PTZ 调用 |
| 压黑画面 | SetImagingSettings |
亮度、对比度、饱和度压到最小 | 图像参数突变 |
| 改屏显 | SetOSD 覆盖 Text/Plain 图层 |
画面上出现任意文字 | OSD 内容变化 |
最后两行放在一起看,就是\"可见性攻击\":画面被压黑、再写上一行字,流还在推、录像还在存,但看到的人只会记住那行字。这对宣传、施压、羞辱类行动的价值,远高于偷偷看一眼。而\"恢复出厂\"是破坏性的——它顺手把凭据清回默认,等于给下一个攻击者留了门。
▲ 图 3:枚举类动作负责“看清有什么”,操作类动作负责“改变状态”;后者才是检测该盯的地方
防守方在这里的优势是:这些调用在正常业务里低频到几乎为零。谁在半夜调 SetSystemFactoryDefault?谁把亮度从 50 改到 0?把 ONVIF 管理动作做成基线,比追 0day 现实得多。
| 07 · 466 家厂商、3221 条路径:这份知识库暴露的是默认口令的产业通病 |
上一节那些管理动作之所以能对那么多设备生效,是因为工具背后压着一份庞大的厂商知识库。把它拆开统计:
| 数据 | 数量 | 说明 |
| 厂商条目 | 466 | 按厂商名索引 |
| RTSP 连接路径 | 3,221 | 含 6 条 UDP 路径 |
| 厂商 banner 指纹 | 4 | 只有 Hipcam、Tenda、TVT、H.264DVR |
| 厂商专属凭据 | 6 条 | 绝大多数厂商条目是空凭据 |
| 通用口令组合 | 18 用户名 × 60 口令 | 覆盖主要靠这一组 |
| RTSP 端口 | 63 | 只有 9 个在优先列表里 |
三个结论:
1.路径本身就是指纹。/cam/realmonitor?channel=1&subtype=0 是大华系,/live/ch00_0 是一类国产 IPC,/11 在国产 IPC 上很常见。防守方用同一份路径清单去核自己的设备,看到哪个路径返回 200/401,就知道暴露了哪一族实现。
2.banner 识别名存实亡。4 条指纹对 466 家厂商,靠 Server: 头认厂商基本不可行,工具实际是靠\"路径命中\"反推。
3.真正的覆盖面来自通用弱口令,不是厂商知识。这也解释了为什么这类工具在野外的命中率高度依赖目标是否改过默认口令——而 20 年过去,改过的仍然不多。
另外两个容易被忽略的运维事实:工具启动时会访问 api.github.com 检查版本,也就是说在客户网络里跑它,会留下一条到 GitHub 的出站连接;它把成功凭据明文写进 ~/.pwneye/cache,一台做过多个项目的笔记本,缓存里可能躺着几十套摄像头账号。渗透机上最该被清理的目录,往往不是工具目录,而是缓存目录。
| 08 · 四条检测线:从“谁在问时间”到“谁在改屏显” |
知道它怎么打,才能知道从哪拦。四条线按成本从低到高排:
L1 网络流量。 最信噪比最高的是三条固定形状:向 /onvif/device_service 发 POST 且 SOAPAction 为 GetSystemDateAndTime、请求头里没有 Authorization;同一个源在短时间内对 554 端口先 OPTIONS 再连续 DESCRIBE,且都不带认证;User-Agent: pwneye/0.1.0。前两条对任何 ONVIF 扫描器都成立,第三条只对这一款工具成立——但够用。
L2 设备侧。 摄像头的 Web 后台大多有\"认证失败\"计数和操作日志,只是没人开。把 ONVIF 管理调用(重启、复位、OSD、图像参数)纳入日志采集,再把日志送到 SIEM,是唯一能看见\"谁在动镜头\"的办法。
L3 口令核查。 不要在生产网里跑爆破。做法是导出设备清单,用配置核查脚本比对默认口令表,再抽检 3% 做人工登录验证。默认口令整改的性价比,仍然高于任何检测规则。
L4 架构。 摄像头 VLAN 与办公网隔离、禁止摄像头主动访问互联网、关闭 UPnP 与 ONVIF 发现、强制 HTTPS、唯一强口令、固件跟进厂商公告。最后加一条基线:每周对每个通道截一张图,做直方图比对。画面被压黑、屏显被改,第一眼就能看出来。
▲ 图 4:从流量形状到资产归属,四条线覆盖成本与依赖各不相同
| # 检测思路(伪代码,落地时按自己的日志字段改写) if http.method == \"POST\" and http.uri == \"/onvif/device\_service\" and http.header[\"Authorization\"] is null and body contains \"GetSystemDateAndTime\": emit\_alarm(\"ONVIF probe\", src\_ip) if rtsp.method == \"DESCRIBE\" and rtsp.header[\"Authorization\"] is null and count(src\_ip, 60s) > 20: emit\_alarm(\"RTSP probing\", src\_ip) |
| 09 · 边界:模拟拆解能证明什么、不能证明什么 |
必须交代清楚:我没有真实摄像头,mock 只模拟了 RTSP 的正常与异常返回,ONVIF 侧只验证了那条未认证探测,onvif-python 在测试里被替换成空壳,所以\"枚举用户\"\"改 OSD\"这些结论来自代码阅读,不是真实设备上的验证。466 家厂商、3,221 条路径这些数字随数据文件变化,工具版本停在 1.3.2(2026-09-06 提交)。
还有一条判断上的边界:这类工具的价值在\"发现\",不在\"打穿\"。它不会利用固件漏洞,也不会处理 SHA-256 摘要;它能拿到的,都是\"凭据弱 + 服务暴露\"这两件事的产物。
| 10 · 结论:把它移出可达面,比任何检测规则都强 |
摄像头的问题从来不是\"有没有 0day\",而是三个叠加的常态:两个协议面各写各的鉴权、出厂凭据被沿用、管理 API 低频到没人监控。
所以防守的抓手也很具体:谁在问时间,谁在转镜头,谁在改屏显。这三件事在正常业务里几乎不会发生,一旦出现,就已经越过了\"扫描\"进入\"操作\"。
至于最强的缓解,不是任何一条规则,而是把它移出可达面——一台只能被录像机访问的摄像头,比一台打了所有补丁、但暴露在公网上的摄像头安全得多。
| 技术内容仅供学习与研究,请勿用于非法用途。 赛博57库 · 2026-11-04 |
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论