代码执行漏洞与命令执行漏洞也是常见的危险Web漏洞,两者名字听上去差不多,但是却有着本质上的不同。代码执行漏洞是把代码注入到Web服务器中执行,而命令执行漏洞执行的系统命令 ,这篇博客会对这两种漏洞的产生原因与修复方式进行介绍。
0x01 代码执行漏洞的产生原因
代码执行漏洞主要有两种产生原因:代码执行函数和动态函数执行,在php中还存在双引号执行的情况。常见的导致代码执行的函数有:eval()、assert()、preg_replace()、call_user_func()、array_map()等,它们造成漏洞的原因也给不一致。
eval()、assert()
eval() 函数把字符串按照 PHP 代码来计算,assert是用来判断一个表达式是否成立的函数。它们的参数就是php代码,所以可以直接执行php。我们写如下的测试代码:
1 <?php 2 $a = '123'; 3 $b = '456'; 4 eval('$a=$b;'); 5 echo ($a); 6 ?>
访问后得到的结果为456,说明eval直接执行了代码。
preg_replace()
这个函数就是我们常用到的对字符串进行正则匹配的函数,直接看一个例子,在Web目录下创建preg.php:
<?php preg_replace("/[(.*)]/e",'\1',$_GET['str']); ?>
浏览器访问http://localhost/preg.php?str=[phpinfo()],程序会用"/[(.*)]/e"对我们输入的参数str进行替换,根据正则的匹配规则,我们输入的参数[phpinfo()]实际被执行的代码为phpinfo(),进而激活了漏洞。
调用函数过滤不严
array_map()和call_user_func()就是最典型也是最常见的案例,这些函数有调用其他函数的功能,被调用的函数名作为参数出现在这些函数里,如果这些参数可控,那么就可以执行我们期望执行的代码,也就存在代码执行漏洞,举一个例子,在Web目录下创建call.php:
<?php $b="phpinfo()" call_user_func($_GET['a'],$b); ?>
在浏览器里发送请求:http://localhost/call.php?a=eval,这条请求会调用eval函数并把phpinfo()作为参数执行,从而达到命令执行的效果。
动态函数执行
动态函数执行的思路与call_user_func()有点相似,在Web目录下建一个test.php:
<?php $_GET['a']($_GET['b']); ?>
可以看出,这段程序首先以GET形式获取一个参数a,然后再将a作为函数,把b作为参数获取,我们在浏览器中提交请求:http://localhost/test.php?a=eval&b=phpinfo(),即可完成代码执行。
php双引号执行
在php中,如果代码在赋值的时候使用双引号,那么双引号中间的变量会被当做php代码进行解析,例如Web目录下的1.php:
<?php $str=$_GET['a']; eval("strtolower('$str');"); ?>
在浏览器中提交请求:http://localhost/1.php?a=%27);phpinfo();命令执行漏洞会被激发。
0x02 代码执行漏洞的挖掘与防范
绝大多数的代码执行漏洞都是由函数引起的,我们可以全局搜索相关函数,回溯变量观察用户是否可控,有无过滤规则进行漏洞挖掘。在防范的时候往往对参数采用白名单的形式进行过滤,固定参数的难度比较大,一般采用正则表达式进行匹配。
0x03 命令执行漏洞的产生原因
相比较代码执行漏洞,命令执行漏洞的危害更大,它所执行的命令会继承WebServer的权限,也就是说可以任意读取、修改、执行Web目录下的任何文件。能够进行命令执行的php函数一共有七个,分别是:system()、exec()、shell_exec()、passthru()、pcntl_exec()、popen()、proc_open(),除函数外,倒引号( ` ` )内的内容也可以进行命令执行
直接返回执行结果
在这七种函数中,直接返回执行结果的有system()、exec()、shell_exec()、passthru(),例如
<?php system('whoami'); ?>
程序加载后,直接把当前WebServer的用户名输出。
返回文件指针
popen()和proc_open()不会直接返回执行结果,而是返回文件指针。例如:
<?php popen('whoami >>C:/1.txt','r') ?>
这段程序执行后,我们可以在C盘根目录下看到1.txt文件,里面的内容是WebServer的用户名。在popen函数中,需要两个参数,一个是执行的命令,另一个是指针文件的连接模式,rw表示读写。
pcntl拓展
pcntl是linux下的一个扩展,可以支持php的多线程操作,pcntl_exec()函数必须在该拓展安装的情况下才能执行,平时比较少见
倒引号
倒引号也可以执行命令,原理是调用shell_exec函数,一个简单的例子:
<?php echo `whoami`; ?>
程序执行后会输出当前WebServer的用户名。
0x04 命令执行漏洞的挖掘与防范
绝大多数的命令执行漏洞都是由函数引起的,所以在挖掘中可以首先采用全局搜索的方式定位函数确认漏洞。除此之外我们还可以根据程序的功能进行挖掘,常会引发代码执行的功能有:自带Web服务与数据库的程序、操作数据库或日志等的功能、调用外部程序的功能等。
现在流行的命令执行漏洞防范主要有两种:白名单、函数防御
白名单
命令执行漏洞的出现通常是因为对参数的过滤不严格所致,因此可以对命令执行函数的参数设置白名单,在使用的时候匹配以下参数是否在白名单内即可。
函数防御
命令执行漏洞的防御函数有两种:escapeshellcmd()、escapeshellarg()。前者的作用是过滤整条命令,后者的作用是过滤参数。
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。反斜线()会在以下字符之前插入: #&;`|*?~<>^()[]{}$, x0A 和 xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 ^都会被空格代替。而在Linux平台上会在这些字符前加/,例如
<?php echo(escapeshellcmd($_GET['cmd'])); ?>
提交请求http://localhost/test.php?cmd=whoami,得到的输出结果是whoami^.
escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。例如:
<?php echo 'ls '.escapeshellarg('a"'); ?>
这个函数把参数限制在引号内,确保参数是一个字符串,因此他会把双引号替换为空格,我们得到的最终结果为ls "a "。