从完整的规则整理,代码开发到最后的代码测试,大致可以分为如下几个部分:
webshell样本收集和普通样本收集。
阿里云与腾讯云各自的规则检测率,错检率和检测效率
规则的整理选择和优化
python解释器跨操作系统移植
webshell检测的两个阶段:初始化主动扫描,inotify修改被动扫描
扫描参数的确定与检测实验
实验数据的处理和结论
所有的jsp和jspx的样本都是从github上收集, 主要的jsp木马还是以jsp小马和jspspy之类的老牌木马偏多,并没有特殊的混淆,所以jsp木马的检测相对于php木马来说简单很多,当然了,既然我们也有了阿里云和腾讯云关于PHP木马的检测规则,再进行同样的步骤得到PHP webshell检测模块也不是复杂的事。
最终我们通过github收集到的webshell样本大概有300个,而正常的jsp文件样本大概有500个。为了尽量避免相同的样本,我们使用文件内容的MD5sum来作为文件名,这样保证不会存在完全相同的两个文件,但很多相似的文件还是没有简单的方法去除(密码不同的同一种jsp小马)。以下是样本的预处理代码,需要注意的是文件名中可能存在特殊字符,所有需要用\ 来转义。
jsp_gather.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import re
import time
import hashlib
import tencent
import ali
import sys
def md5(string):
return hashlib.md5(string).hexdigest()
#pathdir = '/root/webshell_sample'
pathdir = sys.argv[1]
destdir = sys.argv[2]
wis = 'jsp|jspx'
filepaths = []
for fpathe,dirs,fs in os.walk(pathdir):
for f in fs:
ppp = os.path.join(fpathe,f)
if os.path.isfile(ppp) and re.match(r'^\.('+wis+')$',os.path.splitext(ppp)[1]):
filepaths.append(ppp)
i = 1
for f in filepaths:
filename = md5(open(f).read())
f = f.replace(" ","\\ ")
f = f.replace("(","\\(")
f = f.replace(")","\\)")
f = f.replace("$","\\$")
f = f.replace("'","\\'")
print str(i) + " => " + f
postfix = "." + f.split(".")[-1]
copy = os.popen("cp " + f + " " + destdir + "/" + filename + postfix)
copy.close()
i += 1
腾讯云的规则是使用json格式存储的,而阿里云的规则是使用xml格式的存储的,为保证二者的规则能在之后的webshell检测中能够通用,而不需要额外的代码进行格式转换,我们需要事先将这些规则的格式统一化,我们选取json格式作为我们最终的存储格式。以下是将阿里云xml规则转换成json的代码:
ali_xml.py
#!/usr/bin/env python
import xml.dom.minidom
res = []
root = xml.dom.minidom.parse("ali.xml").documentElement
for elm in root.childNodes:
if elm.nodeType == elm.ELEMENT_NODE:
rules = elm.getElementsByTagName('Rule')
res_2 = []
for rule in rules:
tmp = {}
tmp['type'] = rule.getElementsByTagName('Type')[0].childNodes[0].data
tmp['data'] = rule.getElementsByTagName('Data')[0].childNodes[0].data
res_2.append(tmp)
res.append(res_2)
print res
最终经过处理的阿里云websehll规则如下所示:
腾讯云的webshell规则是以json格式存储的,如下所示:
此处的检测规则具体内容格式仍没有统一,所以我们需要使用不同的驱动程序去加载这两个配置文件,并为顶层应用提供封装好的is_webshell函数。
阿里云的webshell检测函数如下所示:
ali.py
#!/usr/bin/env python
import hashlib
import re
import sys
from config import *
def is_webshell(filepath,filetype):
if filetype=='php':
ali_webshell_rule = ali_webshell_rule_php
elif filetype=='jsp':
ali_webshell_rule = ali_webshell_rule_jsp
content = open(filepath).read()
if len(content) > max_file_size:
print "[*]File Skip => " + filepath
return ("Skip",0)
for rulelist in ali_webshell_rule:
hash_id = hashlib.md5(str(rulelist)).hexdigest()
res1[hash_id] = rulelist
flag = 1
for rule in rulelist:
if int(rule['type']) == 2: # type=2 string match
if content.find(rule['data']) < 0:
flag = 0
break
elif int(rule['type']) == 1: # type=1 regrex
if not re.search(rule['data'],content):
flag = 0
break
if flag:
print "[!]webshell find => " + filepath + "; hash => " + hash_id
if rule_hit.has_key(hash_id):
rule_hit[hash_id] += 1
else:
rule_hit[hash_id] = 1
if file_match_rule.has_key(filepath):
file_match_rule[filepath].append(hash_id)
else:
file_match_rule[filepath] = [hash_id,]
#return ("unknown shell ",1) # if not return, the webshell would be checked by all rules
print "[*]File OK => " + filepath
return ("OK",0)
腾讯云的webshell检测函数如下所示:
tencent.py
#!/usr/bin/env python
import hashlib
import re
from config import *
def is_webshell(filepath,filetype):
if filetype=='php':
tencent_webshell_rule = tencent_webshell_rule_php
elif filetype=='jsp':
tencent_webshell_rule = tencent_webshell_rule_jsp
content = open(filepath).read()
for rule in tencent_webshell_rule:
hash_id = hashlib.md5(str(rule[3])).hexdigest()
shell_type = rule[0]
shell_regrex = rule[3]
if re.findall(shell_regrex,content):
print "[!]webshell find => " + filepath + " shell_type => " + shell_type + " hash => " + hash_id
if rule_hit.has_key(hash_id):
rule_hit[hash_id] += 1
else:
rule_hit[hash_id] = 1
if file_match_rule.has_key(filepath):
file_match_rule[filepath].append(hash_id)
else:
file_match_rule[filepath] = [hash_id,]
#return (shell_type,1)
print "[*]File OK => " + filepath
return ("OK",0)
通过这两个函数我们对webshell样本与正常的jsp样本进行测试,可以得到如下结果:
阿里云:
阿里云的webshell规则hit的次数如下所示:
{'38e2135f2ab85d315c7c5a81662d7167': 3, '2f02be11a401d90bab21733c699ea3bb': 19, '14c5932fe3de433a3f02f6ed4d190d45': 189, 'c78557ba9b5a274416f733a4d5b8a00a': 7, 'cc2ed3c634877bf77ab563a576a645b7': 3, '3831555ec43643056b2bc0c90772a8c6': 14, 'fa0d6ae87b688eb1986b753edb6533e1': 37, '4626a35a053002518fd540b3505c861e': 219, 'abdc071f6ac076ffd4a69bcc3ce61c67': 32, '7ea9ca2ccc7ad81bb856dd7ca743ea02': 23, '473f7ffaf345d097256a927eccaf6a28': 2, '5870ee0500793546c4861958c9b71a69': 16}
其中的hash值为规则的hash值。很显然,有两个规则在webshell检测过程中出现的概率非常高。还有很多规则并没有用上(0 hit)。
所有的296个webshell样本,检测出246个,未检出50个,检测率83%,运行耗时约为5.33s。
所有的496个正常文件样本,不存在误检。
腾讯云:
腾讯云的webshell规则hit次数如下所示:
{'1a80400a03c19c34fdeda28c024eaf39': 47, 'c553ba9d158478f0a8617a0eeef8d24a': 152, '7e06b788274b10f96010073344aff5ee': 2, 'a77a3344ffdff81e1bdf356275f5cdbf': 37, 'b021a7713d6eb2499aabb17820c483ec': 77, 'd3044224040076e98bd5654f942c232f': 115, 'd6fcb63fe4b4b6afb18e7d4166531ba9': 78, '4e08b978297e7d567d55b1a389414b24': 112, '357781c5a932ca2628811a6d946ea14e': 89, '1101f9a1764d38a079c560b12831ccff': 11, '22fb7da32fecf8391492e8c03d5e8771': 190, 'b08b00aad4568a8149591df47c75aa8a': 244, '6eb8dace678bfa25242153288bcee3b3': 66}
所有的296个webshell样本,检测出283个,未检出13个,检测率95.6%,耗时约为33.01s。
所有的496个正常文件样本中,误检出2个,误检率0.4%。
我们可以很清晰的看到,阿里云的webshell检测效果并不算太好,但是它将检测对资源的消耗压缩到了最低的层次,而且不存在错检。
那么阿里云的算法为什么效率这么高呢,在下一章节中我们将重点介绍。
我们先看看腾讯云的检测规则:
('exec_cmd', 1, 3001, '(?:(?:java\\.lang\\.)?Runtime)?\\s*(\\w+)\\s*=\\s*(?:java\\.lang\\.)?Runtime\\.getRuntime\\(\\)[\\s\\S]*\\1\\.exec\\(.*?\\)', 0)
腾讯云所有的检测规则都是正则表达式。
如下是从阿里云的webshell检测规则中摘取下来的一条规则:
[
{'data': 'allowTypes=newString[]{"jpg","jpeg","gif","ico","bmp","png"}\n\n',
'type': '2'},
{'data': 'Util.null2String(request.getParameter("dir"));',
'type': '2'},
{'data': 'saveFile.substring(0,saveFile.indexOf(',
'type': '2'},
{'data':'newFileOutputStream(fileName);',
'type':'2'}
]
显然阿里云的每一条规则都不是单一的规则,而是一个rulelist,当且仅当rulelist中的所有rule满足条件时,这个文件才会被判为webshell。而这些rule可以分为两种,即type=1和type=2的情况,type=1意味着此时rule中的数据是一个正则表达式,需要通过正则的相关模块去匹配上下文;type=2则意味着此时的rule仅仅是一个字符串,只需要在文件内容中去找匹配的字符串即可,由于字符串匹配的效率远远高于正则匹配,而且阿里云的检测规则以type=2为主,所以阿里云的检测速度要远远快于腾讯云。
既然已经有了二者的规则,也对二者规则进行了一个基础的分类和整理,那么在我们的webshell检测模块中,需要用到的规则有哪些呢?我们不难猜测,虽然阿里云和腾讯云在规则的内容上不尽相同,但是在检测的语义上有相似的地方,如果我们直接将所有的规则组合在一起,这一部分的重复会带来不必要的消耗。此外还有很多较为生僻的webshell检测规则,对于这一部分的webshell规则,我们究竟保不保留?
规则的优化很重要的一部分就是判断两个规则是否相似,而判断两个规则是否相似最佳的方法就是去观察两个规则所匹配的webshell是否相同。但是这种方法的并不是最佳的算法,因为在两个规则有多少的相似时,我们除去其中的一个规则?此时,我们应该除去的又是哪一个规则。
另一种可行的算法是多目标遗传算法,最后两个目标是运行时间和检测率。某条规是否选择(0/1)作为编码的基因,从理论上来说该算法完全可行。但是在实际操作的过程中遇到的问题是样本数量不够,不具有说服力。从前面阿里云和腾讯云hit的结果可以看出,大部分的规则都是没有使用的,经过遗传算法后收敛的解,应该是不会包括这些“无用”的规则,造成了检测率的虚高。下次找到足够的样本,保证大部分规则都能hit时,才能使用遗传算法进行优化。
最终我们阿里云和腾讯云的所有规则合在一起,不做任何的修改,唯一的优化是,如果文件的大小超过某个值时,使用字符串匹配,而不是用字符串正则。所有的可以分离的配置我们都将其存储在config.py中。我们可以通过config.py中关于规则hash的选择来确定最终规则的选择,一次来增加程序的可扩展性:
Python解释器的跨操作系统移植
Python语法简明,功能强大,还是比较适合作为我们的webshell检测模块的支撑语言。最终该模块会被OSSEC客户端调用,提供给OSSEC客户端的应该是一个可执行文件或者源代码与相关的解释器。由于业务机子的主要操作系统环境是centos5 centos6 以及centos7。在这几个版本的操作系统上,通过yum默认安装的python版本分别为2.4 ,2.6 以及2.7 ,如果使用默认的解释器,那么由于python不同版本支持的语法不同,会出现很严重的兼容性问题。而如果我们直接用python的源码去编译最新版本的python环境,那么在每台业务机上会有很大的性能损耗。因此,我们采取的解决方案是在一台centos的机子上直接将python编译成可执行文件,补充上缺失的函数库,然后移植到其他的操作系统上。
编译的机子,我选的是centos6,在configure的阶段使用--prefix参数将程序安装到指定的目录下。移植测试的过程中,除了使用的部分python模块会有问题,比如说sqlite3,就需要把_sqlite3.so替换成合适版本的。在替换动态链接库的过程中,有一个很重要的问题就是替换版本后,动态链接库对系统的库函数的依赖可能不满足,这种情况下,阿里是自带了一个libc内置在安骑士的客户端中。当然你可以使用这种方法,然后用LD_PRELOAD去加载相应的库函数。但幸运的是,在centos5 和centos7 的移植中,并没有发生函数库版本问题。
但是,在使用centos5的docker测试过程中,出现了一个小问题大概是说,hashlib无法加载成功,因为openssled库不存在。一般来说都会预先安装的一个软件包,因为docker的极度精简的策略被删除,导致无法运行成功,但是一般的业务机子一定会有openssh的服务端,而openssh依赖着openssl提供加密,也就是说,一般的centos5的业务机子跑这个程序是没问题的。而且md5函数还可以用python自行实现一般,当然性能会有一定的损耗。
(未完待续)