13.3 示例:使用有向图识别WebShell

完整演示代码请见本书GitHub上的13-2.py以及13-3.py。

WebShell具有很多访问特征,其中和有向图相关的为:

·入度出度均为0;

·入度出度均为1且自己指向自己。

完整处理流程如图13-8所示。

图13-8 HTTP日志数据处理流程

1.数据整理

我们在新安装的WordPress网站下面安装一个简单的后门1.php,如图13-9所示,内容为phpinfo。

图13-9 测试环境中放置的后门文件

Apache默认不记录refer字段,需要修改默认配置,开启HTTPD自定义日志格式,记录User-Agen以及Referer:


<IfModule logio_module>
# You need to enable mod_logio.c to use %I and %O
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
CustomLog "logs/access_log" combined

针对1.php的访问日志为:


[root@instance-8lp4smgv logs]# cat access_log | grep 'wp-admin/1.php'
125.33.206.140 - - [26/Feb/2017:13:09:47 +0800] "GET /wordpress/wp-admin/1.php HTTP/1.1" 200 17 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
125.33.206.140 - - [26/Feb/2017:13:11:19 +0800] "GET /wordpress/wp-admin/1.php HTTP/1.1" 200 17 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"

逐行处理日志,生成原始页面请求的URL和该页面refer指向URL的对应关系,聚合结果举例为:


- -> http://180.76.190.79/wordpress/wp-admin/1.php
- -> http://180.76.190.79/wordpress/wp-admin/admin-ajax.php
- -> http://180.76.190.79/wordpress/wp-admin/customize.php
- -> http://180.76.190.79/wordpress/wp-admin/load-styles.php
- -> http://180.76.190.79/wordpress/wp-admin/post-new.php
- -> http://180.76.190.79/wordpress/wp-login.php
http://180.76.190.79/wordpress/ -> http://180.76.190.79/wordpress/wp-admin/edit-comments.php
http://180.76.190.79/wordpress/ -> http://180.76.190.79/wordpress/wp-admin/profile.php
http://180.76.190.79/wordpress/ -> http://180.76.190.79/wordpress/wp-login.php
http://180.76.190.79/wordpress/ -> http://180.76.190.79/wordpress/xmlrpc.php
http://180.76.190.79/wordpress/wp-admin/ -> http://180.76.190.79/wordpress/wp

2.数据导入

连接数据库:


driver = GraphDatabase.driver("bolt://localhost:7687",auth=basic_auth("neo4j","maidou"))
session = driver.session()

逐行读取,生成节点以及关联关系:


for line in file_object:
    matchObj = re.match( r'(\S+) -> (\S+)', line, re.M|re.I)
if matchObj:
    path = matchObj.group(1);
    ref = matchObj.group(2);
if path in nodes.keys():
    path_node = nodes[path]
else:
    path_node = "Page%d" % index
    nodes[path]=path_node
sql = "create (%s:Page {url:\"%s\" , id:\"%d\",in:0,out:0})" %(path_node,path,index)
index=index+1
session.run(sql)

把入度出度作为节点的属性,更新节点的出度入度属性:


if ref in nodes.keys():
    ref_node = nodes[ref]
else:
    ref_node = "Page%d" % index
    nodes[ref]=ref_node
sql = "create (%s:Page {url:\"%s\",id:\"%d\",in:0,out:0})" %(ref_node,ref,index)
index=index+1
session.run(sql)
sql = "create (%s)-[:IN]->(%s)" %(path_node,ref_node)
session.run(sql)
sql = "match (n:Page {url:\"%s\"}) SET n.out=n.out+1" % path
session.run(sql)
sql = "match (n:Page {url:\"%s\"}) SET n.in=n.in+1" % ref
session.run(sql)

3.查询结果

网页关联关系原始数据如图13-10所示。

图13-10 网页关联关系原始数据

网页关联关系可视化结果如图13-11所示。

图13-11 网页关联关系可视化图

查询入度为1出度均为0的节点或者查询入度出度均为1且指向自己的节点,由于把ref为空的情况也识别为"-"节点,所以入度为1出度均为0。查询满足条件的疑似WebShell链接,如图13-12所示。

图13-12 查询疑似WebShell的链接

在生产环境实际使用中,我们遇到的误报分为以下几种:

·主页,各种index页面;

·Phpmyadmin、Zabbix等运维管理后台;

·Hadoop、ELK等开源软件的控制台;

·API接口。

这些通过短期加白可以有效解决,比较麻烦的是扫描器对结果的影响,这部分需要通过扫描器指纹或者使用高大上的人机算法来去掉干扰。