如何精确查找PHP WEBSHELL木马? 上篇提到了关于网上流传查找PHP webshell的python脚本中,不严谨的代码,并且给出了一个python的检测代码,同时,下文里也提到不能检测到反引号的命令执行的地方。今天,我想了下,现在把思路发出来。 先来看下反引号可以成功执行命名的代码片段。代码如下: 正则表达式该如何写? 分析: 对于可移植性的部分共同点是什么?与其他正常的包含反引号的部分,区别是什么? 他们前面可以有空格,tab键等空白字符。也可以有程序代码,前提是如果有引号(单双)必须是闭合的。才是危险有隐患的。遂CFC4N给出的正则如下:【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))`(?P<shell>[^`]+)`】。 解释一下: 【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))】匹配开始位置或者开始位置之后有空白字符或者前面有代码,且代码有闭合的单双引号。(这段PYTHON的正则中用了捕获命名以及反向引用) 【`(?P<shell>[^`]+)`】这个就比较简单了,匹配反引号中间的字符串。 然后我将这段代码写入程序中,测试跑了一下discuz的程序。结果有一个误报。误报的位置为“config.inc.php”中的“define('UC_DBTABLEPRE', '`ucenter`.uc_');”,什么原因造成的?这行代码符合了前面有闭合的引号,也有反引号的使用,所以,符合要求,被检测到了。如何再排除这种情况呢?这个有什么特殊的?前面有逗号“,”?如果是字符串连接的点号“.”呢?再排除逗号? 好吧,我错了,我不该用我的思维来误导你。换个思路。找下反引号可执行的代码的前面字符串的情况,他们只能是行的开始,或者有空白字符(包括空格,tab键等),再前面也可以有代码的结束标识分号“;”,其他的情况,都是不可以执行的吧?嗯,应该是这样。(如有错误,欢迎斧正)既然思路有了,那正则代码更好写了。如下【(^|(?<=;))\s*`[^`]+`】,解释一下,【(^|(?<=;))】匹配位置,是行的开始,或者前面有分号“;”。【\s*`[^`]+`】空白字符任一个,然后是....(你懂的)。OK,写好之后,检测,又发现一个问题。 匹配引入文件的正则也匹配了“require_once './include/db_'.$database.'.class.php';”这种代码,什么原因造成的,您自己分析吧。 给出修复之后的python代码,如下: 稍微检测了一下Discuz7.2的代码,还是有误报的,误报的为这种包含sql的代码: 由于这个脚本是按照一行一行的代码来处理的,所以,有这种误报。您自己去修复吧。相对网上流传的脚本来说,还是比较准确的。 欢迎转载。转载请注明来源,以及留下博客链接,同时,不能用于商业用途。(已经修复,增加了反引号后面【\s*;】的判断。2010-07-27 17:06) PS:如果说上传文件也算是危险的、值得注意的操作的话,建议加上move_uploaded_file函数的检测。你知道在哪里加的。^_^ 2010-12-17 关于这些代码,已经放到google 的代码托管上了。SVN地址为 https://code.google.com/p/cnxct/ 大家个获得最新版。 我是一个PHPer,写的python有点憋,有点懒,还请各位安全界的大牛,程序界的前辈不要鄙视,要给建议,谢谢。php版的以后在写吧。同时,也欢迎各位安全爱好者反馈最新的web shell特征代码,我尽力增加到程序中区。 2013-09-13更新: PHP版基于语法扫描,语义分析的扫描器,已经实现,详情见: Pecker Scanner php编写的webshell扫描器 2010-07-27 +16 20 0
关于网上流传查找PHP webshell的python脚本中,不严谨的代码 不是闲着蛋疼,也不是批评谁,只是不忍心看到不严谨的代码在网上被疯传,误导初学者。以下引用的代码以及思路来自网络,只针对代码,不针对人。如有雷同,纯属巧合。 来自某大牛python版本的检测服务器上PHP代码中webshell的脚本代码片段如下: 然后,接着是python处理上面列表(数组),把每个列表的第一个元素作为正则表达式内容,然后进行匹配处理的。先看第一个检测“PHP一句话木马”的这个正则。【eval\(\$\_POST】这里转义了字符【_】,字符“_”不是元字符,不需要转义的,其实,转义也无妨,聪明的正则表达式引擎会识别修正的。来说下这个思路的遗漏之处。这里仅仅匹配字符串“eval(”后面的变量“$_POST”,如果是“$_GET”呢?如果是“$_REQUEST”呢?如果程序先把$_POST/$_GET/$REQUEST的key的变量先赋值给一个自定义的变量名,然后在用PHP的“eval”函数执行这个变量呢?是不是就查找不到了?其实,并不需要知道“eval”函数执行的变量名是什么,只要查找“eval(”即可,当然,还要注意“eval”函数后面可以有空格,tab键之类空白字符。上面的代码片段中,也没考虑到这一点。 再看看下一个列表的第一个元素。【(system|shell_exec|exec|popen)】,这个正则的意思是只要字符串里包含“system”、“shell_exec”、“exec”、“popen”这四组字符串即判定为危险字符。很明显,这个方法太不严谨。如果程序员写的代码中,包含了这四组字符,即可被判定为危险函数。很不准确,误报率极高。见下图 到底什么样的代码是可疑的代码?关键词是什么? 可疑的代码肯定是由可以执行危险操作的函数构成,可以执行危险操作的PHP函数最重要的就是“eval”函数了,对于加密的PHP代码(仅变形字符串,非zend等方式加密),肯定要用到“eval”函数,所以,对于不管是用哪种加密方法的代码,肯定要用到“eval”函数。其次就是可以执行系统命令的函数了,比如上面某牛的代码中提到的四个“system”、“shell_exec”、“exec”、“popen”。当然还有其他的,比如passthru等。PHP还支持“·”字符(ESC键下面那个)直接执行系统命令。我们可以把正则写成这样【\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(】。 解释一下: 大家都知道【\b\b】用来匹配单词两边的位置的。要保证【\b\b】中间的是单词,即使函数名前面加特殊字符,也一样通过匹配,比如加@来屏蔽错误。后面的【\s*】用来匹配空白字符的,包括空格,tab键,次数为0到无数次。前面的【(?P)】是捕获命名组。用来当作python代码直接引用匹配结果的key。 还有的网友提到了,如果我把代码放到图片拓展名的文件里呢?那你只检测.php,.inc的文件,还是找不到我的呀。嗯,是的,如果恶意代码在gif、jpg、png、aaa等乱七八糟的拓展名文件里,是不能被apache、IIS等web Services解析的,必须通过include/require(_once)来引入。那么,我们只要匹配include/require(_once)后面的文件名是不是常规的“.php”、“.inc”文件。如果不是,则为可疑文件。正则如下【(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']】。 解释一下: 先看【(?P<function>\b(?:include|require)(?:_once)?\b)】,【(?P<name>)】为正则表达式的“命名捕获”,PHP中有同样的用法。也就是说,在这括号内的捕获的数据,会分配到结果数组的key为“name”的value中。再看里面的【\b(?:include|require)(?:_once)?\b】,【\b\b】不解释了,为单词边界位置。里面的【(?:include|require)】匹配字符串“include”、“require”两个单词,其中前面的【(?:)】未不分配组,用于提高效率,可以去掉【?:】变成【(include|require)】。在后面一个【(?:_once)】也是做不分配组的操作,便于提高正则表达式效率。同样,后面的量词是“?”代表这个组可有可无。就满足了“include”、“include_once”、“require”、“require_once”四种情况。有的朋友可能这样写【(include|include_once|require|require_once)】也能实现目的。但是,为了更搞的效率,我们对这个正则做优化,针对部分字符串做分支更改,改成上面那个【\b(?:include|require)(?:_once)?\b】。 再看下面的【\s*\(?\s*["'](?P<filename>.+?(?<!\.(?:php|inc)))["']】中,【\s*】匹配空白字符,包括空格,tab键等。后面的【\(?】,匹配字符“(”,后面的量词“?”表示这半个小酷括号可有可无。防止“incude "123.php"”这种没有括号的情况。再后面【["']】匹配双引号,单引号的。最后的也是。再看看这个【(?P<filename>.+?(?<!\.(?:php|inc)))】,其中【(?P<filename>)】上面介绍了,为命名捕获,把结果放到match.group("filename")里。【.*?】为任意字符,后面的量词是“忽略优先量词”,也就是平常说的“非贪婪”。这里最少匹配零个,(防止.aa、.htaccess这种没有文件名,只有文件拓展名的文件被引入)。后面的【(?<!\.(?:php|inc))】,这里用到了反向零宽断言(环视)的非操作(只匹配位置,不匹配字符串,跟【^$\b】等一样)。这个表达式是针对这个位置的后面字符起作用的,也就是说后面的【["']】的前面不能是“.php”、“.inc”,这里也就是取了文件名的最后的拓展名。(正则里,可以用【^】对字符取非,但是不能对“字符串组”取非,这里用了零宽断言来实现。) 综上所述,最后,鄙人给出的python代码如下: 仅供参考,欢迎斧正。 下面截图为扫描Discuz7.2的效果图,当然,也有误报。相对网上流传的python脚本,误报更少,更精确了。 问:这个方法完美了吗?可以查找目前已知的所有危险函数文件了吗? 答:不能,如果include等引入的文件没有拓展名,这里就匹配不到了。 问:如何解决? 答:留给你解决,聪明的你,肯定可以搞定。 PS:“`”反引号 执行命令的还没写,暂时没好的办法。容易跟SQL语句中的反引号混淆。不太好匹配。如果光匹配反引号就提示的话,那误报太大了。待定吧。(术业有专攻,请勿因为一处不好的代码,否定一个人的能力。你懂的。再次重申,此文只针对代码,不针对人。其次,鄙人给出的python代码随便复制,随便传播,爱留版权就留版权,不爱留就删了相关字符,也就是您爱干吗干吗。) 我先休息一会,明天再说。(前半句为三国杀曹仁的台词,哈。) =============================我是万恶的分割线====================================== 最新代码在这里给出了。 2010-07-26 3 0