php代码不开源下的一种漏洞检测思路
来源:安全焦点
作者:T_Torchidy (jnchaha_at_163.com)
现在很多的php软件厂商为了一些目的都开始将自己的产品用Zend加密起来,当客户使用的时候根本不知道程序里面的细节,的确给那些搞安全的人很大的难度,所以一些厂商就开始认为只要是Zend过的产品都是安全的。但是其实不然,即使在不知道原代码并且不考虑Zend破解的情况下,我们一样可以利用 Php本身的一些特性来到达获取程序的一些商业逻辑以及信息以到达探测程序的漏洞等等。
测试程序的安全性方法一般有三种,第一是完全的不知道源代码,不知道程序内部处理的细节,完全靠输入的参数来猜测程序内部的细节以及可能出现的问题,另外一种就是能得到程序的代码知道代码的处理流程,通过对代码的分析来得到程序的问题。还有另外一种方式就是处于中间的灰盒测试,可以知道程序的一部分细节,这里我们要说的可能就属于这种方式了,因为我们知道Zend所谓的加密其实是将PHP代码转成了一个中间态的代码,在支持Zend加密的主机上最终都是跟正常的PHP文件一样被翻译成底层代码执行的,不过是源代码不可见罢了。但是要注意一点就是,这个加密代码是在我们提供的环境之中运行的,他用的语言环境是我们机器上的PHP,他所选择的数据库等存储系统是我们提供的文件系统或者Mysql等数据库,这就比单纯的黑盒测试多了很多可以利用的地方,因为我们完全可以控制他的运行环境,甚至大部分的处理结果对我们也是可见的,我们就有空子可钻了。
然后说说PHP代码里的常见漏洞,一般的漏洞包括有Sql注射,文件包含,代码执行,Xss注射,逻辑性错误……等等。在知道代码的情况下我们可以比较容易检测出大部分的漏洞,但是由于代码加密变的不可见,只采用一般的黑盒测试方法只能检测一些常见的漏洞,但是对于一些变量没初始化和一些逻辑错误无能为力。下面就说说在应用层如何探测程序的一些安全问题。
首先就是如何“调试”加密的PHP脚本的问题,这里的调试实际上是指如何让加密的PHP脚本在我们可以控制的环境下运行,我这里用的方法就是创建一个php文件来include包含执行需要调试的加密过的PHP文件,在include之前我们可以设置调试代码,在include执行完毕后我们还可以利用后面提到的方法查看脚本的一些信息。譬如我们要调试zend.php这个被加密的文件,就可以利用下面的方式:
eg <?php
//一些运行前的代码
echo "Just a example";
include 'zend.php';
echo "OK";
//一些运行后的代码
?>
这样zend.php里的东西就受我们的影响了。
然后要做的就是对代码的刺探了,很幸运的是PHP提供了很多的函数给我们使用,譬如比较有用的有:
get_defined_vars 返回由所有已定义变量所组成的数组 (PHP 4 >= 4.0.4, PHP 5)
get_declared_classes 返回由已定义类的名字所组成的数组 (PHP 4, PHP 5)
get_defined_constants 返回由已定义常量的名字所组成的数组
get_defined_functions 返回由已定义的函数
get_included_files 返回已经包含的文件名
……
大家可以去php手册里查下,利用这些函数我们就能做很多事情了,譬如我们知道某个加密的文件zend.php里面有我们很想要的东西,譬如数据库帐户密码,譬如某个加密的Key,譬如……那么我们就可以将这个文件利用上面的方法include出来,然后用 get_defined_vars函数看信息,例子如下:
eg <?php
include 'zend.php';
print_r(get_defined_vars);
?>
怎么样,出来了吧?同样,一些程序员通常把一些函数放到一个php文件里,我们就可以通过同样的方法得到这个php文件里定义了的函数了。
在一些程序破解里,经常有一个说法叫做Hook,其实我们在进行我们的测试的时候也可以进行简单的Hook。一个站点的SQL查询里经常蕴涵了很多的信息,如果知道了进行某个操作要用的SQL查询对我们的测试是很有用处的。我们有好几个方法,譬如更改php的mysql_query函数,在 Mysql方面做手脚,这里我们还是简单点看看应用层能做什么吧!一般的PHP代码都喜欢将Mysql操作封装起来放到一个文件里,我们就可以将这个文件替换掉,自己实现他需要实现的函数,在其中将他运行的SQL语句断下来保存(Thx Saiy),然后我们就可以分析这些SQL查询了,同样的道理,我们完全可以做出假的Function来到达猜测某些函数的功能的目的,在假的函数里用如下func_get_args函数就可以截获传入的参数。当然,这对于函数内部的细节还是不清楚的,但是很多时候已经足够了,起码把黑盒由文件级别降低到函数级别了,呵呵。
而对于程序本身我们能做什么呢?对于一个程序的安全性我们通常关注的有他处理参数的方式,他如何对待危险函数的,他的变量有不有正确的初始化……等等,通过上面的思想我们就大概能做这些事了:)
1 利用PHP自身的设置猜测程序处理参数的方式
一个PHP程序是要处理参数的,在代码中程序如何处理我们提交的变量是很重要的。譬如一些程序为了兼容所有的PHP环境,都会在开始释放变量以实现在register globals = off的时候程序一样能运行,并且为了在GPC = off 的时候程序不出问题,一般都会做addslashes处理,但是也并不是所有的程序都能正确处理好,所以我们通过更改PHP.ini的设置就可以大致推测出程序是如何处理输入的参数的。譬如,我们将register globals 设置为 off,我们然后include某个文件(譬如index.php),然后在代码的最后print_r($GLOBALS)来查看当前符号表里是不是有我们提交的参数,如果有的话就证明程序是将变量释放的,然后我们提交一些危险的变量如php?_POST=aaaaa然后看看$_POST的内容就可以知道是不是有变量覆盖的问题了,代码流程如下:
eg <?php
include 'index.php';
print_r($GLOBALS);
?>
当然,之前需要设置register global = off
2 利用PHP自身的出错机制
在PHP运行过程中,可以根据自定义的错误级别捕捉程序中的一些信息,默认情况下对未初始化这些信息并不做判断,但是没有初始化的变量确能给我们有用的信息。一些常见的文件包含漏洞就是都是由变量未初始化造成的。当我们把运行级别错误设置为NOTICE时就可以看到这些信息了,但是也不排除有程序人为地在运行过程中修改运行级别,后面我会说说对付这种的方法。找到未初始化变量之后我们就可以试着提交它,来看程序的反映,结合功能以及变量名猜测程序的写法,通常可以挖掘一些漏洞。
3 结合php扩展
这一来就有点向底层靠了,PHP自己是提供了一些查询系统状态的函数如上面提供的get_defined_vars之类,但是这些是远远不够的,但是PHP本身又不像js那样覆盖系统原有的函数就是不允许函数重定义,这就给我们带来了麻烦,譬如我们想跟踪程序有不有调用preg_replace函数,并且想知道里面的参数形式,我们是没有办法的,但是利用PHP扩展就变得可能了,我们可以类似于一种Hook的方法来实现这些。譬如想调查 preg_replace的话,我们先将preg_replace这个函数从函数表里Dump成preg_replace_bak一份,然后 create_function建立我们自己的函数,我们自己的函数目的很简单,先保存参数然后调用原来的preg_replace_bak函数并返回结果,这样就可以监视脚本里这个函数的调用情况了。我这里有个简单的从函数表里删除函数的代码:
删除函数的一个php扩展代码
PHP_FUNCTION(fuck_fun)
{
char *function;
int function_len;
zval **findfun;
if (ZEND_NUM_ARGS() != 1 || zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s", &function, &function_len) == FAILURE) {
WRONG_PARAM_COUNT;
}
TSRMLS_FETCH();
if (zend_hash_find(EG(function_table), function, strlen(function)+1,(void**)&findfun)==SUCCESS) {
php_printf("Find function %s OK\r\n",function);
}
else {
php_printf("Cann't find function %s\r\n",function);
}
if (zend_hash_del(EG(function_table), function, strlen(function)+1)==SUCCESS) {
php_printf("%s Unregister OK\r\n",function);
}
else {
php_printf("%s Unregister Faild\r\n",function);
}
RETVAL_LONG(42);
return;
}
4 再下去呢?再下去就是修改PHP内核了,就有点超出本文的范围了。文章要说明的是加密不是一切,通过修改程序运行环境以及一些小技巧然后再结合黑盒测试的方法,找出一些加密过的程序的安全漏洞还是可能的,本人就检测过国内一些加密程序,比较大的漏洞还是有的:)