Bubbles~blog

用爱发电

基于svm的简单xss检测模型实践

走进SVM
因为最近在coursera的机器学习课程也结课了,说实话现在对于各种算法的具体实现上还是懵懵懂懂的,赶巧有事情要准备下支持向量机,就先行简单实践了一下

SVM是一种主要用于监督学习的分类算法,简单来说,当目标是只有二维的向量时,那么SVM要完成的就是尝试用一条直线将训练集中的两类数据分隔开,当然如果只是分隔开可能会找到很多直线,而SVM找到的这条直线距两个不同的类的距离是一样远的,也就是恰好在两个类的中间,更严格一点来说,这条直线距两个类里最近的元素的距离都是最远的。

当然,这只是最简单的情况,还有很多时候可能这两个类无法被一条直线分隔开,这时候就要引入更多的维度了,核函数开始发挥它的用场,在此我们就不展开讨论了,有兴趣的可以去深入研究,其实对具体的实现上自己也是半吊子

附上传送门 支持向量机导论

其实具体算法没看懂问题也不大,在工程上我们会用就行了,分类算法的非黑即白的划分方式也比较适合我们的这一任务

构建模型识别XSS

环境准备

我们要用到的是python的Scikit-Learn机器学习模块,里面包含了很多算法,使用起来非常方便,直接pip安装即可,同时需要的依赖还有numpy和Scipy,都是相关的工具库

搜集数据

在此我们需要大量的正常的web访问日志和包含xss攻击的web访问日志,比较直接的方法就是直接拿着扫描器像是awvs等之类的使用xss扫描去扫你的服务器得到payload样本,不过我试过以后发现数据集的大小并不是很尽如人意,可能也就差不多数千条这样,而且也没找到足够的正常web日志数据集,github上也有一些payloads的集合质量挺不错,不过数量也并不多,虽然也可以根据这些数据集写脚本自己继续制造payload,不过图方便我这里还是直接用了兜哥的数据集

附上传送门
含xss的web日志

正常web日志

这两个都包含这20万份记录,算是比较好的训练集了,也算是偷个懒吧,毕竟咱也没有环境去产生这么大量的数据

特征化

接下来就要将我们的数据进行特征化,也就是将它们变成向量的形式,同时也是数量化,毕竟这样才方便机器去识别理解我们的数据

比如说,我们来看几个典型的xss

<pre lang="python" line="0"><script>alert(123)</script>
 
<img src=x onerror=alert(xss)/>
 
<img src="javascript:alert('XSS');">
 
<SCRIPT a=">" SRC="http://hacker.gg/xss.js"></SCRIPT>

可以看到攻击语句中实际上包含着许多的特征,比如括号啊,标签啊,单双引号,script等等,我们现在要做的就是计量它们

import re
def get_len(url):
    return len(url)
 
def get_url_count(url):
    if re.search('(http://)|(https://)', url, re.IGNORECASE) :
        return 1
    else:
        return 0
 
def get_evil_char(url):
    return len(re.findall("[<>,\'\"()/]", url, re.IGNORECASE))
 
def get_evil_word_1(url):
    return len(re.findall("(alert)|(script=)|(eval)|(src=)|(prompt)",url,re.IGNORECASE))
 
def get_evil_word_2(url):
    return len(re.findall("(%3c)|(%3e)|(%20)",url,re.IGNORECASE))
 
def get_evil_word_3(url):
    return len(re.findall("(iframe)|(href)|(javascript)|(data)",url,re.IGNORECASE))
 
def get_evil_word_4(url):
    return len(re.findall("(onerror)|(onload)|(onfocus)|(onmouseover)",url,re.IGNORECASE))
 
def get_evil_word_5(url):
    return len(re.findall("(string.fromcharcode)|(document.cookie)",url,re.IGNORECASE))
 
def get_feature(url):
    return [get_len(url),get_url_count(url),get_evil_char(url),get_evil_word_1(url),get_evil_word_2(url),get_evil_word_3(url),get_evil_word_4(url),get_evil_word_5(url)]

当然,这里我们不可能将xss的所有可能方式都照顾到,在这挂个黑名单出来,这也是机器学习的方式跟传统的检测机制不同的地方,它只是去分析规律,在大量样本中不断学习罢了

在这里我将这些特征大致分类了一下,算是应对不同的payload,将很多可能的恶意字符串分开也是为了建立更多的维度,它们的选择大致也是对应相应的payload形式,但是也要注意不要建立太多特征以免造成过拟合

标准化

当然仅仅特征化是不够的,我们可以看到我们的特征向量大的大小的小,跨度比较大,url长度可能长达五六十而我们的特征词可能也就几个甚至0,所以我们要对他们进行一个放缩处理,在这里我们直接将所有的特征值放缩到0到1之间,当然你也可以选择其他的方法来完成这一过程,在sklearn的preprocessing模块里有很多方法

因为我们的特征值里可能会有很多值为0的项,所有选择这种方法比较有利于维持我们的特征

因为是放缩到0到1,所以此处选择MinMaxScaler函数

from sklearn import preprocessing
 
min_max_scaler = preprocessing.MinMaxScaler()
 
x_min_max=min_max_scaler.fit_transform(x)

现在我们的特征值就全部处于0和1的区间内了,其实这也算是归一化了

有人可能也想尝试使用正则化处理,不过个人觉得在这个模型里的效果应该不是太好,而且也加大了复杂度,如果是在逻辑回归里倒是会有不错的表现

标记数据

这一步一般在机器学习的任务里算是比较乏味的,特别是如果你搜集的数据是散乱的时候,这里因为我们拿到的数据集已经是分割好的,所以也就省去了我们的劳动,现在还有很多专门做这种给数据打标签的生意的

def labels(filename,data,label):
        with open(filename) as f:
            for line in f:
                data.append(get_feature(line))
                if label:
                    y.append(1)
                else:
                    y.append(0)
        return data
 
labels('xss-200000.txt',x,1)
labels('good-xss-200000.txt',x,0)

划分train与test

现在我们需要把数据划分为训练集与测试集,一般默认的比例是6比4,当然还有交叉验证集也可选,我们暂且先这么用着

此处用到的是sklearn的cross_validation模块,从名字我们也可以看出它的用途了
它包含很多划分数据集的方法,我们一般常用的是train_test_split,它是用来从数据集中随机地按比例选取训练集和验证集,其他的方式根据不同的需求自然也有不同的作用

用法是

from sklearn import cross_validation
 
x_train, x_test, y_train, y_test = cross_validation.train_test_split(x,y, test_size=0.4, random_state=0)

test_size就是验证集所占比例大小,random_state是生成随机数的种子,不同的种子采样结果就不同,相同的种子则一样

训练模型

这里也很简单,我们直接调用了sklearn的svm模块,核函数我们就选择最典型而简单的linear核也就是线性核,当然你也可以选择其他的像是高斯核RBF,然而事实上我们此处的模型使用线性核的训练效果已经很不错了,如果你选用RBF核可能对于结果也没有多大的提升,毕竟我们这里的模型维数还是比较低的,而且使用RBF还需要花费更多的时间去尝试参数,就针对这里而言我们还是选用简单的线性核

from sklearn import svm
 
clf = svm.SVC(kernel='linear', C=1).fit(x_train, y_train)

模型评估

现在就到了我们最后的环节了,对于模型的训练结果进行评估,在sklearn中也给我们提供了相关的模块,这里我们使用的是它的metric模块

就直观效果而言,我们可以选择accuracy_score函数,它会使用我们训练的模型对test集进行验证,最终输出predict结果的准确率

from sklearn import metrics
 
y_pred = clf.predict(x_test)
 
print metrics.accuracy_score(y_test, y_pred)

当然,准确率(precision)和召回率(recall)也是我们衡量一个模型的重要标准,通常我们都希望这两个值越大越好

这些也在metric模块里得到了很好的支持

print metrics.precision_score(y_test, y_pred)
 
print metrics.recall_score(y_test, y_pred)

一般测试的情况下准确率还是能达到90%以上的,表现还算尚佳

保存模型

最后如果模型的训练结果让你还满意的话你就可以将它保存下来了

from sklearn.externals import  joblib
 
joblib.dump(clf,"xss_svm_module.m")

取用的时候直接load即可

clf=joblib.load("xss_svm_module.m")

End

虽然乍看一下准确率貌似挺高,但实际上哪怕达到了99.99%也依然是不安全的,都是可以随便绕过的,而且它对于各种编码混淆后的payload的检测也不理想,毕竟这种payload特征提取是很困难的,所以我们这里的模型也没有针对它们进行检测,所以要说单单拿机器学习生成的模型去检测目前来看还是不现实的,跟其他的检测机制相结合还有一点实用性,不过机器学习在安全领域的应用应该还是有很大的发展空间的