精确查找PHP WEBSHELL木马的方法(1)


Posted in Python onApril 12, 2011

先来看下反引号可以成功执行命名的代码片段。代码如下:

`ls -al`; 
`ls -al`; 
echo "sss"; `ls -al`; 
$sql = "SELECT `username` FROM `table` WHERE 1"; 
$sql = 'SELECT `username` FROM `table` WHERE 1' 
/* 
无非是 前面有空白字符,或者在一行代码的结束之后,后面接着写,下面两行为意外情况,也就是SQL命令里的反引号,要排除的就是它。 
*/

正则表达式该如何写?
分析:
对于可移植性的部分共同点是什么?与其他正常的包含反引号的部分,区别是什么?
他们前面可以有空格,tab键等空白字符。也可以有程序代码,前提是如果有引号(单双)必须是闭合的。才是危险有隐患的。遂CFC4N给出的正则如下:【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))`(?P<shell>[^`]+)`】。
解释一下:
【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))】
匹配开始位置或者开始位置之后有空白字符或者前面有代码,且代码有闭合的单双引号。(这段PYTHON的正则中用了捕获命名以及反向引用)
【`(?P<shell>[^`]+)`】这个就比较简单了,匹配反引号中间的字符串。
精确查找PHP WEBSHELL木马的方法(1)
某检测PHP webshell的python脚本考虑欠佳。
再看看下一个列表的第一个元素。【(system|shell_exec|exec|popen)】,这个正则的意思是只要字符串里包含“system”、“shell_exec”、“exec”、“popen”这四组字符串即判定为危险字符。很明显,这个方法太不严谨。如果程序员写的代码中,包含了这四组字符,即可被判定为危险函数。很不准确,误报率极高。见下图
精确查找PHP WEBSHELL木马的方法(1)
某检测PHP webshell的python脚本考虑欠佳。
到底什么样的代码是可疑的代码?关键词是什么?

可疑的代码肯定是由可以执行危险操作的函数构成,可以执行危险操作的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*\(】。
精确查找PHP WEBSHELL木马的方法(1)
检测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<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']】。
精确查找PHP WEBSHELL木马的方法(1)
检测PHP WEBSHELL的python脚本较为严谨做法
解释一下:

先看【(?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代码如下:

#!/usr/bin/python 
#-*- encoding:UTF-8 -*- 
### 
## @package 
## 
## @author CFC4N <cfc4nphp@gmail.com> 
## @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<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']''', file_contents, re.IGNORECASE| re.MULTILINE) 
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(?P<function>eval|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脚本,误报更少,更精确了。 精确查找PHP WEBSHELL木马的方法(1)
检测PHP WEBSHELL的python脚本的检测结果
问:这个方法完美了吗?可以查找目前已知的所有危险函数文件了吗?
答:不能,如果include等引入的文件没有拓展名,这里就匹配不到了。
问:如何解决?
答:留给你解决,聪明的你,肯定可以搞定。
PS:“`”反引号 执行命令的还没写,暂时没好的办法。容易跟SQL语句中的反引号混淆。不太好匹配。如果光匹配反引号就提示的话,那误报太大了。待定吧。(术业有专攻,请勿因为一处不好的代码,否定一个人的能力。你懂的。再次重申,此文只针对代码,不针对人。其次,鄙人给出的python代码随便复制,随便传播,爱留版权就留版权,不爱留就删了相关字符,也就是您爱干吗干吗。)
我先休息一会,明天再说。(前半句为三国杀曹仁的台词,哈。)

Python 相关文章推荐
Python实现读取文件最后n行的方法
Feb 23 Python
python把数组中的数字每行打印3个并保存在文档中的方法
Jul 17 Python
将python图片转为二进制文本的实例
Jan 24 Python
Python两个字典键同值相加的几种方法
Mar 05 Python
简单了解python 邮件模块的使用方法
Jul 24 Python
Python操作Excel工作簿的示例代码(\*.xlsx)
Mar 23 Python
pycharm 关掉syntax检查操作
Jun 09 Python
python缩进长度是否统一
Aug 02 Python
Python如何将将模块分割成多个文件
Aug 04 Python
pandas map(),apply(),applymap()区别解析
Feb 24 Python
在Django中使用MQTT的方法
May 10 Python
Python Pandas 删除列操作
Mar 16 Python
Python中删除文件的程序代码
Mar 13 #Python
python 中文乱码问题深入分析
Mar 13 #Python
学习python处理python编码问题
Mar 13 #Python
布同 Python中文问题解决方法(总结了多位前人经验,初学者必看)
Mar 13 #Python
布同 统计英文单词的个数的python代码
Mar 13 #Python
python将多个文本文件合并为一个文本的代码(便于搜索)
Mar 13 #Python
布同自制Python函数帮助查询小工具
Mar 13 #Python
You might like
用php简单实现加减乘除计算器
2014/01/06 PHP
PHP利用imagick生成组合缩略图
2016/02/19 PHP
php使用escapeshellarg时中文被过滤的解决方法
2016/07/10 PHP
PHP实现的多进程控制demo示例
2019/07/22 PHP
YII2框架使用控制台命令的方法分析
2020/03/18 PHP
通过继承IHttpHandle实现JS插件的组织与管理
2010/07/13 Javascript
Easy.Ajax 部分源代码 支持文件上传功能, 兼容所有主流浏览器
2011/02/24 Javascript
微信小程序 JS动态修改样式的实现代码
2017/02/10 Javascript
vue异步加载高德地图的实现
2018/06/19 Javascript
jQuery轻量级表单模型验证插件
2018/10/15 jQuery
vue 关闭浏览器窗口的时候,清空localStorage的数据示例
2019/11/06 Javascript
24个ES6方法解决JS实际开发问题(小结)
2020/05/31 Javascript
解决vue bus.$emit触发第一次$on监听不到问题
2020/07/28 Javascript
vue 修改 data 数据问题并实时显示操作
2020/09/07 Javascript
JavaScript canvas实现雨滴特效
2021/01/10 Javascript
[01:34]DOTA2 7.22版本新增神杖效果一览(敏捷英雄篇)
2019/05/28 DOTA
Python开发常用的一些开源Package分享
2015/02/14 Python
在Python中使用成员运算符的示例
2015/05/13 Python
Python批量修改文本文件内容的方法
2016/04/29 Python
python使用turtle库绘制时钟
2020/03/25 Python
python实现逐个读取txt字符并修改
2018/12/24 Python
Python 测试框架unittest和pytest的优劣
2020/09/26 Python
python Matplotlib数据可视化(1):简单入门
2020/09/30 Python
浅谈关于html5中图片抛物线运动的一些心得
2018/01/09 HTML / CSS
世界上最大的各式箱包网络零售店:eBag
2016/07/21 全球购物
台湾旅游网站:雄狮旅游网
2017/08/16 全球购物
关于Java finally的面试题
2016/04/27 面试题
学期自我评价
2014/01/27 职场文书
机关工会开展学习雷锋活动总结
2014/03/01 职场文书
有关爱国演讲稿
2014/05/07 职场文书
运动会拉拉队口号
2014/06/09 职场文书
一份没有按时交货失信于客户的检讨书
2014/09/19 职场文书
2015年学校德育工作总结
2015/04/22 职场文书
农民工工资保障承诺书
2015/05/04 职场文书
银行服务理念口号
2015/12/25 职场文书
css中有哪些方式可以隐藏页面元素及区别
2022/06/16 HTML / CSS