12.4 示例:使用隐式马尔可夫算法识别XSS攻击(二)

完整演示代码请见本书GitHub上的12-3.py。

1.参数建模

这次我们换个思路,如图12-7所示,把机器当作一个刚入行的白帽子,我们训练他学会XSS的攻击语法,然后再让机器从访问日志中寻找符合攻击语法的疑似攻击日志。

通过词法分割,可以把攻击载荷序列化成观察序列,如图12-8所示。

2.词集/词袋模型

词集和词袋模型是机器学习中极常用的数据处理模型,它们用于特征化字符串型数据。一般思路是将样本分词后,统计每个词的频率,即词频,根据需要选择全部或者部分词作为哈希表键值,并依次对该哈希表编号,这样就可以使用该哈希表对字符串进行编码。

图12-7 检测思路原理图

图12-8 攻击载荷序列化

·词集模型:单词构成的集合,集合中自然每个元素都只有一个,即词集中的每个单词都只有一个。

·词袋模型:如果一个单词在文档中出现不止一次,统计其出现的次数。

本章使用词集模型即可。

假设存在如下数据集合:


dataset = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], ['stop', 'posting', 'stupid', 'worthless', 'garbage'], ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]

首先生成词汇表:


vocabSet = set()
for doc in dataset:
    vocabSet |= set(doc)
    vocabList = list(vocabSet)

根据词汇表生成词集:


# 词集模型
SOW = [] 
for doc in dataset:
    vec = [0]*len(vocabList)
    for i, word in enumerate(vocabList):
        if word in doc:
            vec[i] = 1
    SOW.append(doc)

简化后的词集模型的核心代码如下:


fredist = nltk.FreqDist(tokens_list)  # 单文件词频
keys=fredist.keys()
keys=keys[:max] #只提取前N个频发使用的单词 其余泛化成0
for localkey in keys:  # 获取统计后的不重复词集
    if localkey in wordbag.keys():  # 判断该词是否已在词集中
        continue
    else:
        wordbag[localkey] = index_wordbag
        index_wordbag += 1

3.数据处理与特征提取

常见的XSS攻击载荷如下:


<script>alert('XSS')</script>
%3cscript%3ealert('XSS')%3c/script%3e
%22%3e%3cscript%3ealert('XSS')%3c/script%3e
<IMG SRC="javascript:alert('XSS');">
<IMG SRC=javascript:alert(&quot;XSS&quot;)>
<IMG SRC=javascript:alert('XSS')>
<img src=xss onerror=alert(1)>
<IMG """><SCRIPT>alert("XSS")</SCRIPT>">
<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
<IMG SRC="jav ascript:alert('XSS');">
<IMG SRC="jav&#x09;ascript:alert('XSS');">
<BODY BACKGROUND="javascript:alert('XSS')">
<BODY ONLOAD=alert('XSS')>

需要支持的词法切分原则为:

·单双引号包含的内容;


'XSS'

·http/https链接;


http://xi.baidu.com/xss.js

·<>标签;


<script>

·<>标签开头;


<BODY

·属性标签;


ONLOAD=

·<>标签结尾;


>

·函数体;


"javascript:alert('XSS');"

·字符数字标量。

代码实现举例如下:


tokens_pattern = r'''(?x)
 "[^"]+"
|http://\S+
|</\w+>
|<\w+>
|<\w+
|\w+=
|>
|\w+\([^<]+\) # 函数 比如alert(String.fromCharCode(88,83,83))
|\w+
'''
words=nltk.regexp_tokenize(line, tokens_pattern)

另外,为了减少向量空间,需要把数字、字符以及超链接范化,具体原则为:


#数字常量替换成8
line, number = re.subn(r'\d+', "8", line)
#ulr日换成http://u
line, number = re.subn(r'(http|https)://[a-zA-Z0-9\.@&/#!#\?]+', "http://u", line)
#干掉注释
line, number = re.subn(r'\/\*.?\*\/', "", line)

范化后分词效果示例为:


#原始参数值:"><img src=x onerror=prompt(0)>)
#分词后:
['>', '<img', 'src=', 'x', 'onerror=', 'prompt(8)', '>']
#原始参数值:<iframe src="x-javascript&colon;alert(document.domain);"></iframe>)
#分词后:
['<iframe', 'src=', '"x-javascript&colon;alert(document.domain);"', '>', '</iframe>']
#原始参数值:<marquee><h1>XSS by xss</h1></marquee> )
#分词后:
['<marquee>', '<h8>', 'XSS', 'by', 'xss', '</h8>', '</marquee>']
#原始参数值:<script>-=alert;-(1)</script> "onmouseover="confirm(document.domain);"" </script>)
#分词后:
['<script>', 'alert', '8', '</script>', '"onmouseover="', 'confirm(document.domain)', '</script>']
#原始参数值:<script>alert(2)</script> "><img src=x onerror=prompt(document.domain)>)
#分词后:
['<script>', 'alert(8)', '</script>', '>', '<img', 'src=', 'x', 'onerror=', 'prompt(document.domain)', '>']

结合词集模型,完整的处理流程如图12-9所示。

图12-9 词集模型处理流程

4.训练模型

将范化后的向量X以及对应的长度矩阵X_lens输入即可。需要X_lens的原因是参数样本的长度可能不一致,所以需要单独输入。


remodel = hmm.GaussianHMM(n_components=3, covariance_type=”full”, n_iter=100)
remodel.fit(X,X_lens)

5.验证模型

整个系统运行过程如图12-10所示。

图12-10 系统运行流程

验证阶段利用训练出来的HMM模型,输入观察序列获取概率,从而判断观察序列的合法性。训练样本是1000条典型的XSS攻击日志,通过分词、计算词集,提炼出200个特征,全部样本就用这200个特征进行编码并序列化。使用20000条正常日志和20000条XSS攻击识别(类似JSFUCK这类编码暂时不支持),准确率达到90%以上,其中验证环节的核心代码如下:


with open(filename) as f:
    for line in f:
        line = line.strip('\n')
        line = urllib.unquote(line)
        h = HTMLParser.HTMLParser()
        line = h.unescape(line)
        if len(line) >= MIN_LEN:
            line, number = re.subn(r'\d+', "8", line)
            line, number = re.subn(r'(http|https)://[a-zA-Z0-9\.@&/#!#\?:]+', "http://u", line)
            line, number = re.subn(r'\/\*.?\*\/', "", line)
            words = do_str(line)
            vers = []
            for word in words:
                if word in wordbag.keys():
                    vers.append([wordbag[word]])
                else:
                    vers.append([-1])
            np_vers = np.array(vers)
            pro = remodel.score(np_vers)
            if pro >= T:
                print  "SCORE:(%d) XSS_URL:(%s) " % (pro,line)