PHP安全之慎用preg_replace的/e修饰符
PHP以其易用性和可移植性正被广泛应用于WEB开发。然而,在我们使用的过程中,也要十分小心,从随处可见的XSS(新浪微博发送大量垃圾信息事件)到前段时间爆出来的Hash冲突的DDOS攻击,最近,wooyun上面发布了一个关于ThinkPHP框架的漏洞(最新版已经修复),以前也是我用过的第一个框架,昨晚花时间重现了一下,查阅了下程序的原理。本文主要来重现该漏洞,然后分析代码,给出漏洞的原因,用这个漏洞去检验可能对系统造成的破坏,最后总结,防范的方法。
漏洞主要是由mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit]) 这个函数引起的,我们先看官方说明:
/e 修 正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错 误。
我们不妨先看一下这个示例
preg_replace( "/test/e" , $_GET [ "h" ], "jutst test" ); |
如果我们提交?h=phpinfo(),phpinfo()将会被执行(使用/e修饰符,preg_replace会将 replacement 参数当作 PHP 代码执行)。这个正则被正确的匹配到,在进行替换的过程中,需要将$_GET["h"]传入的String当作函数来运行,因此phpinfo()被成功执行。
代码如下:
1
2
3
|
<?php preg_replace( "/test/e" , $_GET [ "h" ], "jutst test" ); ?> |
访问的url : ?h=phpinfo()
进而,我们进入ThinkPHP的源码,下载2.1版(2.1以后已经被修复)。在/ThinikPHP/Lib/ThinkPHP/Util/Dispatcher.class.php的dispatch函数中,找到这句话:
在ThinkPHP的路由功能中,很多地方用到了preg_replace函数的/e参数,然而最严重的是这个文件中的这句话,因为几乎影响了所有使用Thinkphp的项目。
显然,这个使用了/e函数,会导致第二个参数当作函数使用。我们来分析一下这句话:
正则匹配的是 :字母开头,加上“/”分隔符,后面跟一个非”/”的元素,被替换成$var["分隔符前的字母"]=分隔符后的值;作者的本意是要将这么一对一对的参数/值的形式的url写入到 $var[$key] = $value的数组中。
例如:URL,index.php?s=/model/action/par1/value1
将被解析成 :model模块的action方法,参数的处理放在这句话中,$deprde的值是“/”,由于/par1/value1(剩余的URL参数)匹配到了正则表达式,则会用第二个参数去进行替换。那么第二个参数应该被当作一个php语句来执行,他是一个赋值语句,执行的结果为:$var["par1"] = “value1″,作者以此来达到分析url的目的,也实现了ThinkPHP的C层。
但是在赋值的时候,使用的是双引号。危险的地方实际上在这里,在 php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。可以先做如下测试:
$a = “{${phpinfo()}}”; phpinfo会被成功执行。因此,我们将第二个变量替换成我们需要的函数,构造出来的攻击url大致如下:
http://host/index.php/Model/Action/par/${@print(THINK_VERSION)}
我们使用ThinkPHP2.1_full_with_extend ,使用examlpe里面的Blog示例,快速搭建一个Thinkphp项目,然后访问http://host/index.php/Model/Action/par/${@print(THINK_VERSION)},可以在左上角打印出当前的TP版本,利用这个来执行php函数,
相当于开启了一个PHP的一句话后门。
具体分析是:这句话的/par /${@print(THINK_VERSION)}将匹配到正则表达式,将会执行第二个参数,具体语句是$var['par'] = “${@print(THINK_VERSION)}
“; 问题出在双引号上面,这个会执行里面的语句,包含很多危险的语句,包含phpinfo(),读取数据库配置等等。
截至目前,在Google上还可以找到许多这样的站点,很多站点都可以直接执行phpinfo(),在新版的ThinkPHP中已经修复了这个漏洞,将第二个参数的双引号改为单引号:
'$var[\'\\1\']='\\2';'
在我们以后使用preg_replace的/e修饰符的时候,需要注意这个双引号和单引号,像框架类的漏洞,一般影响的范围都非常的广。