ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DVWA 命令注入通关记录:Low / Medium / High 三档源码分析与绕过

DVWA 命令注入通关记录:Low / Medium / High 三档源码分析与绕过

最近在打 DVWA,把 Command Injection 这一关从 Low 到 Impossible 全过了一遍,把源码和 payload 都记下来,方便以后复习,也顺便给同样在打 DVWA 的朋友一个参考。

环境说明:Windows 11 加 WSL2 里的 Kali 发行版,DVWA 用 Docker 跑的,镜像是 vulnerables/web-dvwa,端口映射到宿主机 8081。难度在 DVWA Security 菜单里手动切换,每换一次难度代码就换一套。

命令注入是个什么洞

这一关的页面功能很简单:输入一个 IP,后端去 ping 它,把结果返回给浏览器。功能本身没什么毛病,问题出在后端是怎么拼接这条命令的。

Linux shell 里,几条命令可以用特殊符号串起来。a && b 是 a 执行成功才执行 b;a & b 是 a、b 都执行,不管 a 成败;a ; b 是顺序执行;a | b 是 a 的输出交给 b 处理。

如果用户输入被原样拼进命令字符串,这些符号就成了追加任意命令的入口。

Low:没有任何过滤

先看 Low 级别的后端源码,页面右下角可以点 View Source 查看。

关键就这一行:

$cmd = shell_exec('ping -c 4 ' . $target);

用户输入的 target 变量没经过任何处理,直接拼到了 ping 命令后面。

输入框里填:

127.0.0.1 && whoami

点 Submit 后 ping 会先跑几秒,-c 4 是发四个包,别急着刷新,等它出结果。

返回结果里除了 ping 的输出,还多了一行 www-data,这是容器里跑 Web 服务的用户。到这里命令执行就实锤了。

其他几个符号也都能用。127.0.0.1 后面接分号加 whoami 是顺序执行,效果一样。后面接单个竖线加 whoami 是管道,ping 的输出丢给 whoami,最后只剩用户名。后面接两个竖线加 whoami 这个反而看不到结果,因为 ping 成功了所以后面不执行。

Medium:黑名单出现

切到 Medium,再看源码,比 Low 多了两行。

$target = str_replace( '&&', '', $target);
$target = str_replace( ';', '', $target);

思路是黑名单:把 && 和 ; 从输入里删掉。但 shell 的命令连接符号不止这两个,单个 & 就没在名单里。

填这个照样能通:

127.0.0.1 &;& whoami

& 和 && 的区别是:& 不关心前面的命令是否执行成功,直接把后面这条也丢进后台跑。过滤的人大概默认想连命令肯定用 &&,漏了单字符的情况。

还有一个比较猥琐的玩法。str_replace 是一次性替换,所以可以构造一个删完刚好剩下 && 的输入:

127.0.0.1 &;& whoami

中间的分号被删掉后,剩下的正是 &&,最终执行的还是前面那条完整命令。这种绕过方式挺能说明黑名单的脆弱:只要替换逻辑有规律,就能反着构造。

High:一个空格的安全边界

High 的黑名单升级成了一个数组。

$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

乍一看各种符号全堵了,好像没路了。

但仔细看第三项,过滤的是竖线加一个空格,不是单独的竖线。也就是说竖线后面只要不跟空格,就不会被替换掉。

payload,注意竖线和 whoami 之间没有空格:

127.0.0.1|whoami

这一关给我的印象挺深。写过滤规则的人大概是想保留 cmd 加空格加竖线加空格加 grep 这种带空格的正常管道用法,结果正着用不了,歪着用就直接绕过去了。一个空格,就是安全边界。

Impossible:正确答案长什么样

Impossible 的代码值得单独贴一段,因为它就是这道题的标准修复写法。

先用正则校验,如果不是标准 IP 格式直接拒绝,根本不给拼进命令的机会。校验通过后再用 escapeshellarg 转义,把所有特殊字符处理掉,再拼进命令。

if( !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $target) ) {
echo "Invalid IP format";
} else {
$cmd = shell_exec('ping -c 4 ' . escapeshellarg($target));
}

白名单加转义,两道保险。对比前三关的 str_replace,能明显感觉到思路上的差距。

小结

三个难度的差异,本质就是过滤强度的差异。

Low 无过滤,直接 && 或竖线就能执行任意命令。
Medium 用 str_replace 删 && 和分号,单个 & 就能绕,或者用 &;& 这种组合还原。
High 黑名单更全,但竖线加空格这一项过滤规则里藏了个细节,不带空格的竖线逃过一劫。

修复思路从 Impossible 里已经能看出来了。第一,输入做白名单校验,格式、长度、字符集都卡死。第二,实在要拼进命令,用 escapeshellarg 或 escapeshellcmd 转义。第三,更根本的做法是能不拼就不拼,改用参数化的 API。

黑名单是堵,白名单是筛。堵永远堵不全,筛才筛得干净。这一关打完,对这句话的理解会比看十篇科普都深。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进