php不严谨,关于网上流传查找PHP webshell的python脚本中,不严谨的代码

不是闲着蛋疼,也不是批评谁,只是不忍心看到不严谨的代码在网上被疯传,误导初学者。以下引用的代码以及思路来自网络,只针对代码,不针对人。如有雷同,纯属巧合。

来自某大牛python版本的检测服务器上PHP代码中webshell的脚本代码片段如下:

findtype=['.php','.inc'] #要检查的文件后缀类型

keywords=[ ["eval\(\$\_POST","发现PHP一句话木马!"], ["(system|shell_exec|exec|popen)","发现PHP命令执行函数!"]]

然后,接着是python处理上面列表(数组),把每个列表的第一个元素作为正则表达式内容,然后进行匹配处理的。先看第一个检测“PHP一句话木马”的这个正则。【eval\(\$\_POST】这里转义了字符【_】,字符“_”不是元字符,不需要转义的,其实,转义也无妨,聪明的正则表达式引擎会识别修正的。来说下这个思路的遗漏之处。这里仅仅匹配字符串“eval(”后面的变量“$_POST”,如果是“$_GET”呢?如果是“$_REQUEST”呢?如果程序先把$_POST/$_GET/$REQUEST的key的变量先赋值给一个自定义的变量名,然后在用PHP的“eval”函数执行这个变量呢?是不是就查找不到了?其实,并不需要知道“eval”函数执行的变量名是什么,只要查找“eval(”即可,当然,还要注意“eval”函数后面可以有空格,tab键之类空白字符。上面的代码片段中,也没考虑到这一点。

4778187e851e1d78de4f6cea12931526.png

某检测PHP webshell的python脚本考虑欠佳。

再看看下一个列表的第一个元素。【(system|shell_exec|exec|popen)】,这个正则的意思是只要字符串里包含“system”、“shell_exec”、“exec”、“popen”这四组字符串即判定为危险字符。很明显,这个方法太不严谨。如果程序员写的代码中,包含了这四组字符,即可被判定为危险函数。很不准确,误报率极高。见下图

28de6e86185899110a05aa55a943c04e.png

某检测PHP webshell的python脚本考虑欠佳。

到底什么样的代码是可疑的代码?关键词是什么?

可疑的代码肯定是由可以执行危险操作的函数构成,可以执行危险操作的PHP函数最重要的就是“eval”函数了,对于加密的PHP代码(仅变形字符串,非zend等方式加密),肯定要用到“eval”函数,所以,对于不管是用哪种加密方法的代码,肯定要用到“eval”函数。其次就是可以执行系统命令的函数了,比如上面某牛的代码中提到的四个“system”、“shell_exec”、“exec”、“popen”。当然还有其他的,比如passthru等。PHP还支持“·”字符(ESC键下面那个)直接执行系统命令。我们可以把正则写成这样【\b(?Peval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(】。

0614e1dabb714714699f094a83c7fd68.png

检测PHP webshell的python脚本相对较为严谨的匹配

解释一下:

大家都知道【\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\b(?:include|require)(?:_once)?\b)\s*\(?\s*[“‘](?P.*?(?

4a083cbb2bc36431ed3e018407810de0.png

检测PHP WEBSHELL的python脚本较为严谨做法

解释一下:

先看【(?P\b(?:include|require)(?:_once)?\b)】,【(?P)】为正则表达式的“命名捕获”,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.+?(?.+?(?)】上面介绍了,为命名捕获,把结果放到match.group(“filename”)里。【.*?】为任意字符,后面的量词是“忽略优先量词”,也就是平常说的“非贪婪”。这里最少匹配零个,(防止.aa、.htaccess这种没有文件名,只有文件拓展名的文件被引入)。后面的【(?

综上所述,最后,鄙人给出的python代码如下:

#!/usr/bin/python

#-*- encoding:UTF-8 -*-

###

## @package

##

## @author CFC4N

## @copyright copyright (c) Www.cnxct.Com

## @Version $Id: check_php_shell.py 37 2010-07-22 09:56:28Z cfc4n $

###

import os

import sys

import re

import time

def listdir(dirs,liston='0'):

flog = open(os.getcwd()+"/check_php_shell.log","a+")

if not os.path.isdir(dirs):

print "directory %s is not exist"% (dirs)

return

lists = os.listdir(dirs)

for list in lists:

filepath = os.path.join(dirs,list)

if os.path.isdir(filepath):

if liston == '1':

listdir(filepath,'1')

elif os.path.isfile(filepath):

filename = os.path.basename(filepath)

if re.search(r"\.(?:php|inc|html?)$", filename, re.IGNORECASE):

i = 0

iname = 0

f = open(filepath)

while f:

file_contents = f.readline()

if not file_contents:

break

i += 1

match = re.search(r'''(?P\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P.*?(?

if match:

function = match.group("function")

filename = match.group("filename")

if iname == 0:

info = '\n[%s] :\n'% (filepath)

else:

info = ''

info += '\t|-- [%s] - [%s] line [%d] \n'% (function,filename,i)

flog.write(info)

print info

iname += 1

match = re.search(r'\b(?Peval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(', file_contents, re.IGNORECASE| re.MULTILINE)

if match:

function = match.group("function")

if iname == 0:

info = '\n[%s] :\n'% (filepath)

else:

info = ''

info += '\t|-- [%s] line [%d] \n'% (function,i)

flog.write(info)

print info

iname += 1

f.close()

flog.close()

if '__main__' == __name__:

argvnum = len(sys.argv)

liston = '0'

if argvnum == 1:

action = os.path.basename(sys.argv[0])

print "Command is like:\n%s D:\wwwroot\ \n%s D:\wwwroot\ 1-- recurse subfolders"% (action,action)

quit()

elif argvnum == 2:

path = os.path.realpath(sys.argv[1])

listdir(path,liston)

else:

liston = sys.argv[2]

path = os.path.realpath(sys.argv[1])

listdir(path,liston)

flog = open(os.getcwd()+"/check_php_shell.log","a+")

ISOTIMEFORMAT='%Y-%m-%d %X'

now_time = time.strftime(ISOTIMEFORMAT,time.localtime())

flog.write("\n----------------------%s checked ---------------------\n"% (now_time))

flog.close()

## 最新代码在文章结尾的链接里给出了。2010/07/31 更新。

仅供参考,欢迎斧正。

下面截图为扫描Discuz7.2的效果图,当然,也有误报。相对网上流传的python脚本,误报更少,更精确了。

0c540adcc9cacb1e243b00f17cd0a4a3.png

检测PHP WEBSHELL的python脚本的检测结果

问:这个方法完美了吗?可以查找目前已知的所有危险函数文件了吗?

答:不能,如果include等引入的文件没有拓展名,这里就匹配不到了。

问:如何解决?

答:留给你解决,聪明的你,肯定可以搞定。

PS:“`”反引号 执行命令的还没写,暂时没好的办法。容易跟SQL语句中的反引号混淆。不太好匹配。如果光匹配反引号就提示的话,那误报太大了。待定吧。(术业有专攻,请勿因为一处不好的代码,否定一个人的能力。你懂的。再次重申,此文只针对代码,不针对人。其次,鄙人给出的python代码随便复制,随便传播,爱留版权就留版权,不爱留就删了相关字符,也就是您爱干吗干吗。)

我先休息一会,明天再说。(前半句为三国杀曹仁的台词,哈。)

=============================我是万恶的分割线======================================

最新代码在这里给出了。

No related posts.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值