lightless · 2015/07/24 11:24php
最近上(ri)网(zhan)上(ri)多了,各类狗啊盾啊看的好心烦,好多蜜汁shell都被杀了,搞的我本身也想开发这么一个斩马刀,顺便看成毕设来作了。web
未知攻,焉知防。咱们先来看看shell们都是怎么躲过查杀的:加密、变形、回调、隐藏关键字……总之就是一句话,让本身变得没有特征,这样就能够躲过狗和盾的查杀。可是万变不离其宗,不管怎么变形,最终都会回到相似这样的格式:shell
#!php
$_GET($_POST)
复制代码
分为执行数据部分($_GET
)和传递数据的部分($_POST
),也就是说,不管怎么变形,在执行的过程当中都会变成这个样子,接下来仍是去执行相似system()
, exec()
, eval()
等等函数,那么咱们就直接定位到这里,检测该脚本是否调用危险函数,或是在脚本调用这些函数的时候进行分析,判断该脚本是否为非法用户的shell,会取到很不错的防护效果。安全
既然分析出来了问题的根结,那么下一步就是要控制住这些危险函数的入口点,即system()
等函数的底层入口,就像Windows API
中hook
掉zw
系列函数同样,咱们也要hook
掉system()
这些函数。服务器
这里咱们须要用到PHP Extension
, 即PHP扩展,位于PHP内核zend和PHP应用代码之间,很明显,扩展能够监控应用层代码的执行细节,也能够调用内核提供的ZEND API接口,包括禁用类、禁用函数等等。图为PHP结合其余必要组件的基本结构。less
在ZEND中提供了接口供咱们进行这样的操做,经过zend_set_user_opcode_handler
就能够达到目的,根据某大牛所说,只须要hook
三个OPCODE
便可。函数
#!c
ZEND_INCLUDE_OR_EVAL 处理eval()、require等
ZEND_DO_FCALL 函数执行system()等
ZEND_DO_FCALL_BY_NAME 变量函数执行 $func = "system";$func();等
复制代码
以ZEND_DO_FCALL
为例,在MINIT
中将ZEND_DO_FCALL
替换掉:网站
#!c
zend_set_user_opcode_handler(ZEND_DO_FCALL, LIGHT_DO_FCALL);
复制代码
接着定义本身的处理函数,这里是LIGHT_DO_FCALL
。ui
#!c
static int LIGHT_DO_FCALL(ZEND_OPCODE_HANDLER_ARGS)
{
/* 检测是否为合法调用 */
if (/*非法调用*/)
{
/*拦截掉,不执行。*/
}
else
{
/*合法调用,继续执行*/
return ZEND_USER_OPCODE_DISPATHC;
}
}
复制代码
这样一个骨架就完成了,其他的内容能够自行添加。这样每当调用system()
等函数时,都会通过你本身的LIGHT_DO_FCALL
,因而就能够对上层的代码执行进行检查了。加密
上面的方法当然是好,可是误判会不少,甚至一个普通的函数调用都会直接被ban掉。这样的WAF放在业务中必定会被喷出翔的。咱们须要换个思路。
以前已经说过了,不管如何变形、加密、隐藏关键字等,最终都须要调用到system()
,eval()
等函数,终究是逃不过这些的。那么咱们把刚才的思路的变一下,不要直接hook那几个OPCODE
,向上走一层。
eval()
函数在底层是要调用zend_compile_string
函数的,那么咱们是否是能够在底层重载掉zend_compile_string
函数,或是用本身的函数替换掉呢?答案确定是能够的,咱们只须要重写本身的zend_compile_string
函数便可。
可是对于system
来讲有些不同,上面咱们hook掉了ZEND_DO_FCALL
,可是太粗暴了,咱们这里能够在function_table
中删除掉system
,并用咱们本身的函数注册system
函数,这样就能够作到了实时检查,若是为合法调用,能够调用旧的system
函数并继续执行。
很明显这样作的话,确实减小了误杀,可是也增长了风险,由于除了system()
以外还有不少能够用于执行命令的函数。除了执行命令,还有遍历目录、读敏感文件的问题,须要考虑不少的方面,偏僻的函数都要考虑在内,还有PHP SPL
、DirectoryIterator
等等各类方面。
咱们来看看hook三个OPCODE
的方式会产生什么样子的效果。首先咱们在MINIT
函数中将这三个OPCODE
的处理函数换成咱们本身的。
#!c
ZEND_MINIT_FUNCTION(lightWAF)
{
/*
* hook掉ZEND_DO_FCALL
* 处理system函数等
*/
zend_set_user_opcode_handler(ZEND_DO_FCALL, LIGHT_DO_FCALL);
/*
* hook掉ZEND_DO_FCALL_BY_NAME
* 处理$func='system';$func();等类型
*/
zend_set_user_opcode_handler(ZEND_DO_FCALL_BY_NAME, LIGHT_DO_FCALL_BY_NAME);
/*
* hook掉ZEND_INCLUDE_OR_EVAL
* 处理eval, require等
*/
zend_set_user_opcode_handler(ZEND_INCLUDE_OR_EVAL, LIGHT_INCLUDE_OR_EVAL);
return SUCCESS;
}
复制代码
LIGHT_DO_FCALL
, LIGHT_DO_FCALL_BY_NAME
,LIGHT_INCLUDE_OR_EVAL
的内容相似,均为判断脚本文件是否在upload
目录中,若是在的话就禁止危险函数的执行,不然放行。这里咱们用LIGHT_DO_FCALL
说明一下:
#!c
/* LIGHT_DO_FCALL */
static int LIGHT_DO_FCALL(ZEND_OPCODE_HANDLER_ARGS)
{
char *filePath;
filePath = zend_get_executed_filename(TSRMLS_C);
php_printf("[Debug] filePath: %s\n<br />", filePath);
if (strstr(filePath, "/upload/"))
{
/* 非法调用,拦截 */
php_printf("[Warning] Execute command via system() etc.\n<br />");
return ZEND_USER_OPCODE_RETURN;
}
else /* Not Found */
{
/* 合法调用,放行 */
return ZEND_USER_OPCODE_DISPATCH;
}
}
复制代码
这里咱们看到,一旦检测到在upload
目录中,就返回ZEND_USER_OPCODE_RETURN
,实际做用就是不继续执行危险函数了,若是不在upload
目录中,就返回ZEND_USER_OPCODE_DISPATCH
,实际做用就是继续执行该函数。
扩展编写完成后,编译便可获得so
文件,若是对PHP扩展开发以及编译不熟悉的同窗,请参考这个扩展开发教程的第五章:http://www.walu.cc/phpbook/ ,开发方面的知识超出了本文的内容,因此这里再也不赘述了。
编译完成后,将获得的lightWAF.so文件复制到php的扩展目录下,我这里是/usr/local/php/lib/php/extensions/
,根据本身的实际状况进行调整。接下来修改php.ini
,让PHP自动加载咱们的扩展,在其中加上一行:
extension=/path/to/our/lightWAF.so
复制代码
我这里是:
extension=/usr/local/php/lib/php/extensions/lightWAF.so
复制代码
根据以前so文件放的位置不一样,请自行修改路径。
最后咱们重启PHP
服务,让设置生效。PHP
重启完成后,在终端中执行php -m
,来验证扩展是否加载成功,若是在结果中看到了lightWAF
,则说明加载成功。(请无视图中的错误信息,那个是我本地环境的问题,对lightWAF
以及PHP
没什么影响)
成功加载后,咱们看一下实际效果,这里有一个上传文件的页面,能够上传任何类型的文件并将上传的文件保存在upload目录中。如今模拟一下getshell的过程。
咱们写个小马,内容以下:
#!php
<?php
system($_GET["cmd"]);
?>
复制代码
进行上传,并访问shell,结果以下:
能够看到被成功拦截了,好吧,咱们换只牛逼的马儿,试试反射型的。
#!php
<?php
$func = new ReflectionFunction("system");
echo $func->invokeArgs(array("$_GET[c][/c]"));
?>
复制代码
继续上传并访问,看看结果:
依然被拦截了,只不过此次触发的是ZEND_DO_FCALL_BY_NAME
这个OPCODE
。 接下来咱们看看正常的文件会不会被拦截,在lightWAF目录下写入一个test.php,用于模拟正常的业务文件,内容以下:
#!php
<?php
system('ls');
?>
复制代码
访问一下看看结果:
能够看到ls
命令成功的执行了,也就是说咱们的正常文件是不会被拦截的,而只有upload
目录中的文件会被拦截,这样作又会引起另外一个弊端,假若攻击者经过某种方法将shell写入正常的文件中,或是与业务结合起来,那么这种防护手段就很难生效了。具体如何防护还要结合其余的特征进行检测,并非没有办法了,实际应用中不能只依靠检测文件路径这一条规则,须要结合业务进行部署防护方案。
另外一种方法与这个hook三个OPCODE
的方法相似,无非就是麻烦一点,感兴趣的同窗能够围观下面的参考文献:http://security.tencent.com/index.php/blog/msg/19 ,这里描述了hook函数的比较详细的思路。
总结一下,拦截的方法大概就是上面两种,可是拦截的依据尚未决定,如何判断一个调用system()
的脚本是否为webshell
呢?若是咱们的WAF放到生产环境,啥都无论乱杀,颇有可能形成正常的业务没法工做。我本身总结了几个方法,可能联合起来使用效果更好一些。
根据目录判断 一般状况下,上传的文件通常都有专门的目录进行存放,例如upload/
等,正常的业务文件是不在这其中的,因而咱们能够简单的只对这个目录进行处理,其余目录的文件一概放行。
根据文件权限判断 这须要网站的维护者对网站的权限进行严格的控制,例如,全部的web
文件均是644
,且为root:root
全部。上传的文件为www:www
全部,根据权限的不一样进行查杀,对于默认的web
文件进行放行,对属于非root:root
的文件进行查杀。也能够根据w
标志判断,经过未知方式getshell
的文件不少是带有w
标志的,因此能够根据这些特征进行查杀。
变形检测 若是发现一个文件调用了assert
、system
、preg_replace /e
等等,可是在源文件中没有发现这些关键字,还等什么,这个文件很大的可能就是shell
了。(zend_get_executed_filename
能够得到文件的标志以及文件的路径,例如出现了assert
,说明该脚本使用了assert
执行代码。)
黑名单检测 就像传统的杀软同样,总会有那么一部分的特征病毒库,咱们也能够创建一部分的webshell
特征库,先依靠特征库杀掉一部分,再根据其余的状况进行判断。若是将动态检测和静态检测结合起来,查杀、拦截效果应当都会有显著的改善。
安全须要和业务结合起来进行,不谈业务的安全都是耍流氓。由于是扩展级别的WAF,在部署的时候可能须要从新编译,修改配置文件等等。批量式部署可能显得不是那么方便,并且要根据业务须要进行各类细微的调整。若是仅仅是几台服务器,相信这种WAF仍是十分棒的,调整起来也十分方便。
若是能开发出适合批量部署的基于扩展的WAF,那么可能会比较容易普及,毕竟在业务上部署WAF不是一个简单的事情。
http://security.tencent.com/index.php/blog/msg/57 http://security.tencent.com/index.php/blog/msg/19 http://www.walu.cc/phpbook/ http://www.php-internals.com/book/?p=index http://www.nowamagic.net/librarys/veda/detail/1543