基于机器学习的 waf 探究

独家号 安全分享小钻风 作者 Math1as 原文链接

小编寄语:

市面上的WAF大多都是基于规则匹配的,结合机器学习技术可以更好地完善WAF,保障应用业务安全。

投稿作者:Math1as@XDSEC

0X01 引言

目前市面上的大部分waf,都是基于正则表达式来实现的。

BlackHat 2005,,Hanson 和 Patterns 从理论上证明了,任何基于正则的输入验证系统,都存在以下两个情况至少有一个为真:

• 可以构造一个安全的正常请求,但是被验证系统标记为危险/不正常

• 可以构造一个不正常的攻击请求,但是验证系统标记为正常请求

也就是说,基于传统正则表达式的waf,总是无法避免误报或者错报的问题。或者说,它们往往会因为过于死板的规则而导致大量的误报情况,影响到正常的业务逻辑。

什么是机器学习,为什么我们需要机器学习?机器学习是一门基于有监督或无监督算法的学科,可以解决分类,回归,聚类等问题。而我们这里面临的恰好是一个典型的分类问题,需要对一个输入的http请求,输出其是否有害的信息。

0X02 核心问题分析

输入:网站每日的http请求信息

输出:是否包含恶意payload

训练模型前的准备:

数据:分别被标注好了有害和无害的,恶意payload和正常http请求

算法:基于监督学习的各种分类算法

这样来从海量的数据中,还原出其最根本的特征,建立起一个模型。以此为基础,就可以得到对于未知代码的分类器了。

训练过程:

参数算法(svm)等

非参数算法(KNN,naïvebayes)等

测试集:随机抽取的另一组标注后的数据,可以根据模型最终的输出,和我们已知其是否有害的标签,来确定算法的准确程度。

0X03 反向代理

作为一款云waf,我们首先需要实现的就是cdn节点的功能,这样我们才能在数据抵达目标服务器之前,进行防护工作。基本的思路是,配置waf的cdn服务器上的hosts表,指向真实的用户服务器地址,而用户则需要把对应域名的ip地址解析到cdn服务器。

大致的源码如下:

import BaseHTTPServer

hosts=open('/etc/hosts','r').read() #读取cdn服务器的hosts

notsetinfo=open('notset.html','r').read() #没有绑定的提示页面

classPHandler(BaseHTTPServer.BaseHTTPRequestHandler):

def do_GET(self):

flag=False

host=self.headers.getheader('Host')

#waf

if not host in hosts:

self.wfile.writelines(notsetinfo)

else:

m = re.search(".+?=(.+)",self.path) #获取提交的参数

try:

data = urllib2.urlopen(urllib2.Request("http://" +host+self.path)).readlines()

except:

print "fail to get http://" + host+self.path

else:

self.send_response(200)

self.end_headers()

try:

self.wfile.writelines(data)

except:

pass

0X04 预处理

对于我们的这个具有学习功能的waf来说预处理是非常重要的一部分。我们需要将可能进行了大量混淆的payload,进行一个转换,使得我们能够对转化后的数据进行有效的分析。如果不加以转换,那么最初的数据噪声过大不适合直接进行分类算法,这个阶段一般被称为预处理(pre-processing)。

一款在python下工作得比较好的库是sqlparse,使用它的方式也比较简单。

import sqlparse

sql = sqlparse.parse(unicode(raw_sql),'utf-8')

这样就得到了一个经过解析后的句子向量。它们看起来大概是这样的:

其原理类似于自然语言处理中的词性标注。这使我们了解到数据中的每个成分是关键字呢,int变量呢还是其他的一些东西。

同样的对于xss我们也有现有的解析工具可用。但是目前看来只有sql相应的混淆方法比较少能够达到一个比较好的效果。

0X05 分类算法

  1. KNN K近邻算法

基于某个新的输入计算其余弦相似度中最接近的K个邻居,以样本数量最多的类别来决定新输入的类别。简单的说就是如果它近邻的有害数据较多,那么就认为它应该在统计上也是属于有害数据的。


2.Naive Bayes 朴素贝叶斯

Navie Bayes 是一种基于计算先验概率的分类算法。

假设某个体有n项特征(Feature),分别为F1F2...Fn。现有m个类别(Category),分别为C1C2...Cm

P(C|F1F2...Fn)

=P(F1F2...Fn|C)P(C) / P(F1F2...Fn)

由于 P(F1F2...Fn) 对于所有的类别都是相同的,可以省略,问题就变成了求

P(F1F2...Fn|C)P(C)的最大值。也就是P(F1|C)P(F2|C)*..*P(Fn|C)*P(C)。

对应我们的问题这里的类别其实很简单只有有害和无害两个。对于一条标注的数据,在得到词向量后就可以计算其先验概率了。

根据P(F1F2...Fn|C)P(C)的最大值,就可以确定其属于有害还是无害类别的概率一般P(有害)>0.7,则输出该数据属于有害的分类。这个临界数值(0.7)可以根据每次确定后,在测试集上的准确性来决定。

3.SVM 支持向量机

一种经典的监督学习算法,主要的原理是利用kernel function,将在低维度上比较难以进行分类的数据,投影到高维度上。

我们会得到一个更容易分类的数据结果,这里使用了sckit-learn开源库。

fromsklearn import svm

import numpy as np

svm.LinearSVC() 返回一个线性svm,svm.SVC() 返回一个普通svm,可以指定其核函数,比如svm.SVC(kernel='linear')。可以使用自定义核函数,只要自己def就行了。

自带的函数有四种:

  • Linear 线性

  • polynomial 多项式

  • sigmoid S函数

  • rbf rbf函数

其中obj.fit(INPUT)用于训练SVM分类器,输入为INPUT矩阵。

obj.predict(TEST)则对于数据集TEST输出分类的结果

obj.support_vectors_属性返回了支持向量

如果使用基于sckit-learn的库,实际实现的代码大概如下:

X_train, X_test, y_train, y_test, index_train,index_test = train_test_split(x, y, dataframe.index, test_size=0.3)

#开始训练

clf=clf.fit(X_train, y_train)

X_pred = clf.predict(X_test)

cm = confusion_matrix(X_pred,y_test)

print "训练的结果矩阵:"

print cm

plt.matshow(cm)

plt.title('Confusion matrix')

plt.colorbar()

plt.ylabel('True label')

plt.xlabel('Predicted label')

plt.show()

joblib.dump(clf, os.getcwd()+'/model/clf.model')

return (clf,df_list)

0X06 分布式并行

如果当需要进行训练的数据集量过大的时候性能负担就是一个很大的问题。我们的一些算法是可以借助分布式算法,来解决TB级的日志量问题的,例如朴素贝叶斯算法。

使用hadoop框架,在mapreduce下可以这样表示:


对于每台Slave机和加以控制的Master使用ssh-keygen –t rsa来生成公钥

并且用Scp分发到Slave节点后完成我们的集群配置。在计算各语句先验概率之前,我们先用hadoop集群执行统计词频的程序。


可以直接导出jar,也可以采用eclipsehadoop插件来调试。


在这里如果要获得任务追踪结果,访问Master:8099

0X07 实际测试

对于测试集上的表现,使用混淆矩阵(confusion matrix)来进行准确度评估。


如图,我们得到了一个比较好的结果。

对于比较容易出现误报的数据,Select some of my friends from your house,我们的waf避免了这个问题。


同样的waf产品:


云盾等waf都会产生误报。

0x08 技术对比

1. Libinjection:引入词法分析

追踪关键实现


其实就是将关键字用o,F等替换,实现了特征的提取。



最后将其token化后,5个特征与已知的sql注入进行对比。仍然存在误报,而且手工设定规则总是有遗漏的地方。

弊端虽然相对于原始正则表达式有所进步,但是仍然过于粗暴。

长亭科技:sqlchop 词法分析->语法分析

优化:

1. 结合 tokenize 之前的原始结果进行分析

这也就是说,原始结果中的一些混淆字符和关键信息可能由于粗暴的替换而遗漏了。

因为仅仅是tokenize方法,会导致转化后的结果不能很好的反映语句特征。

2. 使用打分机制,寻找合理算法

相对于仅仅是取5token进行对比的方法,细化了很多。

弊端:曾出现过的一些短接问题(如在wooyun上通报过的or = or)

http://www.wooyun.org/bugs/wooyun-2011-0141263

在旧版中未能给出好的解决方案,我们的WAF:基于机器学习

优点:在足够多的数据集上,实现的效果很好,代码不会冗杂,和需要人工添加规则。

弊端:训练模型->调优的过程过于占用时间和性能。对于不同的业务,不能泛化的训练分类器,而要分开对待(sqli和xss)。

0X09 总结

这里简单的介绍了几种常用的算法和整个waf的实现思路。

在我们未来的安全体系中,机器学习能发挥更加重要的作用。

参考:

http://dongxicheng.org/data-mining/naive-bayes-in-hadoop/

http://drops.wooyun.org/tips/5118

©本文章为《安全智库》的原创作者投稿,转载请注明版权。

—————我是分割线  —————

最后,微信转发该文章到朋友圈并发送截图到安全智库小编可领取红包,先到先得呦~啦啦啦~\(^o^)/~同时也欢迎关注我们的微博,搜索安全智库就可以搜素到啦~

o( ̄▽ ̄)d~小编微信账号在这里~戳戳戳~长按可扫码加好友~

—————  我是另一个分割线  —————

关于安全智库

安全智库,致力于推进技术分享及创新。集思广益,发扬共享精神,将行业内有价值的技术沉淀下来,帮助更多安全从业者。

网址:http://tt.aisec.com

投稿:tt@aisec.com

长按二维码轻松关注!

开发者头条

程序员分享平台