完整演示代码请见本书GitHub上的12-2.py。
1.参数建模
常见的基于GET请求的XSS、SQL注入、RCE,其攻击载荷主要集中在请求参数中,以XSS为例:
/0_1/include/dialog/select_media.php?userid=%3Cscript%3Ealert(1)%3C/script%3E
正常的http请求中参数的取值范围都是确定的,这里说的确定是指可以用字母数字特殊字符来表示,并非说都可以用1~200这种数值范围来确定。以下面的几条日志为例:
/0_1/include/dialog/select_media.php?userid=admin123 /0_1/include/dialog/select_media.php?userid=root /0_1/include/dialog/select_media.php?userid=maidou0806 /0_1/include/dialog/select_media.php?userid=52maidou /0_1/include/dialog/select_media.php?userid=wjq_2014 /0_1/include/dialog/select_media.php?userid=mzc-cxy
肉眼观察可以归纳出userid字段由字母数字和特殊字符'-_'组成,如果你足够强大可以看完上万的正常样本,甚至都可以总结取值范围为由字母、数字、下划线、连字符组成,且长度大于等于4,使用正则表达式即为[0-9a-zA-Z-_]{4,}。如果有上亿的日志含上百万的参数,人工如何完成?这时候机器学习可以发挥作用了。
以uid字段为例,uid的取值作为观察序列,简化期间可以对uid的取值进行范化,整个模型为三阶HMM,隐藏序列的状态只有四个,S1、S2、S3、S4:
·[a-zA-Z]范化为A;
·[0-9]范化为N;
·[\-_]范化为C;
·其他字符范化为T。
以URL参数wjq_2014为例,转换过程如图12-5所示,观察序列是wjq_2014,对应的范化为AAAACNNN,对应的隐藏序列为S1 S1 S1 S2 S3 S3 S3 S3。
图12-5 URL参数建模
同理,URL参数admin 123和root范化结果为:
·admin123范化为AAAAANNN;
·root范化为AAAA。
图12-6 HMM状态迁移
隐藏序列就是S1~S4四个状态间循环转化(见图12-6),这个概率称为转移概率矩阵,同时四个状态都有确定的概率,以观察序列中的A、C、N、T 4个状态展现,这个转换的概率称为发射概率矩阵。HMM建模过程就是通过学习样本,生成这两个矩阵的过程。
典型的转移概率矩阵如表12-1所示。
表12-1 转移概念矩阵示例
典型的发射概率矩阵如表12-2所示。
表12-2 发射概念矩阵示例
在生产环境中范化需谨慎,至少域名、中文等特殊字符需要再单独范化。
2.数据处理与特征提取
由于每个域名的每个url的每个参数的范围都可能不一样,有的userid可能是[0-9]{4,},有的可能是[0-9a-zA-Z-_]{3,},所以需要按照不同域名的不同url的不同参数分别学习。范化过程如下:
def etl(str): vers=[] for i, c in enumerate(str): c=c.lower() if ord(c) >= ord('a') and ord(c) <= ord('z'): vers.append([ord('A')]) elif ord(c) >= ord('0') and ord(c) <= ord('9'): vers.append([ord('N')]) else: vers.append([ord('C')]) return np.array(vers)
友情提示: 为了避免中文等字符的干扰,ASCII值大于127或者小于32的可以不处理直接跳过。
从weblog中提取url参数,需要解决url编码、参数抽取等严峻问题,还好Python有现成的接口:
with open(filename) as f: for line in f: #切割参数 result = urlparse.urlparse(line) # url解码 query=urllib.unquote(result.query) params = urlparse.parse_qsl(query, True) for k, v in params: #k为参数名,v为参数值
友情提示: urlparse.parse_qsl解析url请求切割参数时,遇到';'会截断,导致获取的参数值缺失';'后面的内容,这是个大坑,在生产环境中一定要注意这个问题。
3.训练模型
将范化后的向量X以及对应的长度矩阵X_lens输入即可。需要X_lens的原因是参数样本的长度可能不一致,所以需要单独输入:
remodel = hmm.GaussianHMM(n_components=3, covariance_type="full", n_iter=100) remodel.fit(X,X_lens)
训练样本得分为:
score:16 query param:admin123 score:9 query param:root score:21 query param:maidou0806 score:16 query param:52maidou score:15 query param:wjq_2014 score:12 query param:mzc-cxy
4.模型验证
HMM模型完成训练后通常可以解决3大类问题:一是输入观察序列获取概率最大的隐藏序列,最典型的应用就是语音解码以及词性标注;二是输入部分观察序列预测概率最大的下一个值,比如搜索词猜想补齐等;三是输入观察序列获取概率,从而判断观察序列的合法性。参数异常检测就输入第三种。
我们定义T为阈值,概率低于T的参数识别为异常,通常会定义T比训练集最小值略大,在此例中可以取10:
with open(filename) as f: for line in f: # 切割参数 result = urlparse.urlparse(line) # url解码 query = urllib.unquote(result.query) params = urlparse.parse_qsl(query, True) for k, v in params: if ischeck(v) and len(v) >=N : vers = etl(v) pro = remodel.score(vers) if pro <= T: print "PRO:%d V:%s LINE:%s " % (pro,v,line)
以userid=%3Cscript%3Ealert(1)%3C/script%3E为例子,经过解码后为<script>alert(1)</script>,范化后为TAAAAAATAAAAATNTTTAAAAAAT,score为-13945,识别为异常。这种HMM的使用方式是通过学习正常来识别异常,即通常说的“以白找黑”。“以白找黑”的优点非常明显,就是理论上可以发现全部基于参数的异常访问,但是,缺点是扫描器访问、代码异常、用户的错误操作、业务代码的升级等,都会产生大量误报;所以目前另外一种开始流行的方法是,通过学习攻击报文,训练攻击模型,然后“以黑找黑”。这种方法虽然理论上可能会遗漏真实攻击,但是结果更加可控,可以达到可运维状态。下面我们将介绍基于HMM的“以黑找黑”。