命令执行漏洞
漏洞简述
命令执行漏洞常见于路由器、防火墙、入侵检测等设备的web管理界面中,这些界面常常会提供给用户进行远程命令操作的接口。
例如:提供给用户ping操作的web界面,用户从web界面输入IP,后台会根据用户输入的内容来做ping操作。如果后台未作严格的安全控制,则可能会导致攻击者通过该接口提交恶意命令。如,在ping检测功能中 提交: 192.168.56.137 & whoami
,这段字符串分两部分,通过管道符 &
连接,后台则会ping完IP地址后,执行后续的 whoami
指令,由此黑客获得服务器管理员的用户名,以下是其他常见管道符:
管道符 | 效果 |
---|---|
A | B | 无论A=1 or 0,B都会执行 |
A ; B | 无论A=1 or 0,B都会执行 |
A & B | 无论A=1 or 0,B都会执行 |
A || B | 仅当A=0时,B执行 |
A && B | 仅当A=1时,B执行 |
常用的cmd命令:
命令 | 效果 |
---|---|
whoami | 查看当前用户名 |
ipconfig | 查看网卡信息 |
shutdown -s -t 0 | 立即关机(-s代表shutdown,-t代表time,0表示0s后shutdown) |
net user [username] [password] /add | 增加一个用户名为username,密码为password的新用户 |
type [file_name] | 查看名为filename文件内容 |
难度-Low
过程
web页面给的入口是IP测试,我们输入回环地址进行测试,测试结果:
可以看到,服务器反馈的结果是正常ping通地址的结果
在low等级里,我们可以通过简单地在地址后添加“管道符+指令的方式”来利用漏洞:
&whoami
让服务器显示用户名:
在反馈信息中可以找到用户名的域和用户名
源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// isset()函数用于判断括号内变量是否为空,返回bool值
// Get input
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// stristr(A,B)函数查找B在A中的第一次出现,并返回B及其后面的字符串
// stristr()不区分大小写,strstr()区分大小写
// Windows
$cmd = shell_exec( 'ping ' . $target );
// shell_exec()通过shell环境执行命令,并且将完整的输出以字符串的方式返回
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
源码解析:
判断浏览器是否提交 → 若提交 → 将提交内容传给$target → 判断服务器系统 → 使用shell执行响应命令,并将命令传给$cmd → 打印$cmd
总结
-
攻方:
在面对web命令执行接口时,我们要抓住潜在的漏洞,该漏洞可以通过在要上传的字符串后添加管道符和攻击指令来对服务器造成影响。 -
守方:
在编写命令执行接口时,一定要对用户输入的字符串做甄别,消除潜在威胁。
难度-Medium
过程
Medium难度的服务器源码增加了黑名单,将用户输入的字符串进行甄别,除去或替换可能攻击服务器的字符串
源码:
<?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 );
// 注意这里str_replace()的用法,str_replace(A,B,C),函数将C中的A替换成B,此处的A位置,使用了array_keys()函数,为关联数组$substitutions中的keys值另外创建一个数组,此处的替换,是使用了映射替换,两个数组之间元素关系一一对应。
// 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
echo "<pre>{$cmd}</pre>";
}
?>
因为medium难度黑名单不全,所以可以通过其他管道符来攻击该漏洞:
总结
-
攻方:
在针对命令执行漏洞的攻击中,我们可以多尝试管道符,或许服务器的黑名单不全,从而导致出现漏洞。 -
守方:
在编写有关命令执行类接口时,我们要注意黑名单是否齐备,天网恢恢,疏而不漏。
难度-High
过程
High难度相比于Medium的变化是,对黑名单做了进一步补全,让我们看一下源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
// 黑名单进一步补全了可能造成威胁的字符
);
// Remove any of the characters 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
echo "<pre>{$cmd}</pre>";
}
?>
一般的添加管道符+指令无法攻击漏洞:
然而,在关联数组的第三对元素里,这个后端程序员犯了一个小错误,'| ' => ''
,|
符号后多写了一个空格,导致黑名单只能识别|
,竖杠和空格的组合,从而我们仍可以使用管道符|
来破解,只需少写一个空格即可绕过黑名单:
总结:
-
攻方:
High难度的漏洞是由于程序员的疏忽导致的,我们不得不承认,程序编写习惯也可能导致一些出乎意料的错误,所以我们往往可以在攻击时在将这一部分考量进去,这次的演练中,在攻击时就可以多试试留出空格和不留出空格两种执行方式。 -
守方:
这提醒我们在编写后端白名单时需要留意字符的正确书写方式,事无巨细,要多在步骤的细节处把控好,防止漏洞的出现。
难度-Impossible
过程
源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 该行和代码最后一行都时为了预防CSRF跨站请求伪造攻击
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// 首先删除用户可能输入的反斜杠“\”
// Split the IP into 4 octects
$octet = explode( ".", $target );
// 由于正常情况用户输入的是ip地址,使用explode()函数将字符串打散,以“.”为准,分成若干个元素,每个元素对应一个八位字节,这些元素重新组成数组,对数组的每个八位字节做进一步的检测
// 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.
// 如果所有4个八位字节都是数字类型或数字字符串类型,就保留到下一步操作
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// 将检查完的4个八位字节用“.”重新连接成一个字符串
// 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
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
// 检查显示这4个八位字节中有非正常的数据类新,则报错,不再进行下一步操作
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
总结
-
攻方:
如果后台使用了Impossible难度的代码,那么将无法被攻破,只能寻求其他途径攻击。 -
守方:
impossible的防御正如其名,不可能被攻击,是安全开发人员的典范,其他后端语言也可实现相应的算法。对于命令执行漏洞,我们需要对用户输入的字符串做严格的检查,检查通过后才可以送至后台做操作。针对IP检测的接口,我们需要先将字符串中的“\”删除,再以“.”为准,分隔字符串,分隔出若干元素组成数组,再将数组的每个元素进行检查,检查他们是否都为数字,若不是,则报错;若是,则将这些数字重新通过“.”连接,生成新的字符串。最后对这串字符串做服务器的操作。扩展开来,在其他命令执行操作接口的后端编写中,我们可以按照标准输入的特点来编写检查步骤,将不符合标准的可能输入内容筛选剔除,从而保证最后进入系统控制台的字符串不会引起多余的操作。