命令注入攻击的常见模式为:仅仅需要输入数据的场合,却伴随着数据同时输入了恶意代码,而装载数据的系统对此并未设计良好的过滤过程,导致恶意代码也一并执行,最终导致信息泄露或者正常数据的破坏
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' ] ) ) { $target = $_REQUEST [ 'ip' ]; if ( stristr( php_uname( 's' ), 'Windows NT' ) ) { $cmd = shell_exec( 'ping ' . $target ); } else { $cmd = shell_exec( 'ping -c 4 ' . $target ); } $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' ] ) ) { $target = $_REQUEST [ 'ip' ]; $substitutions = array ( '&&' => '' , ';' => '' , ); $target = str_replace( array_keys( $substitutions ), $substitutions , $target ); 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 2 3 4 5 6 7 8 $substitutions = array ( '&&' => '' , ';' => '' , ); $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' ] ) ) { $target = trim($_REQUEST [ 'ip' ]); $substitutions = array ( '&' => '' , ';' => '' , '| ' => '' , '-' => '' , '$' => '' , '(' => '' , ')' => '' , '`' => '' , '||' => '' , ); $target = str_replace( array_keys( $substitutions ), $substitutions , $target ); if ( stristr( php_uname( 's' ), 'Windows NT' ) ) { $cmd = shell_exec( 'ping ' . $target ); } else { $cmd = shell_exec( 'ping -c 4 ' . $target ); } $html .= "<pre>{$cmd} </pre>" ; } ?>
同样是黑名单,不过依然可以绕过,注意到
此处的 ‘|’ 后面跟了一个空格,那么 payload 就可以通过将后面指令紧贴 ‘|’ 来绕过,如
依旧造成了命令注入
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' ] ) ) { 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 ); } $html .= "<pre>{$cmd} </pre>" ; } else { $html .= '<pre>ERROR: You have entered an invalid IP.</pre>' ; } } 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 )