时序模型研究

一、研究原因

最近一段时间都在做对时序的研究,而这源于在做对攻击payload向量化的时候遇到的一些存在的问题。机器学习中黑白集都必须最终实例化为数组的形式,才能进行训练,而这些数组的元素又必须是数字的,因此,在对一个字符串+数字+符号构成的payload进行向量化的时候,就会遇到很多的问题。

比如:

http://www.baidu.com/index.php?id='"><script>document.write(123);alert(666);</script>

在对这个语句进行向量化的时候,我们能从什么角度将其转化为数字矩阵呢?这其实是个很重要的问题,前期我采用了一种较为简单的方式,即统计语句中的如 script 、alert这种关键字出现的次数,构成如[0,2,4,6,1]这样的矩阵,再将矩阵正则化后供给训练器训练。 这样得到的模型其实对于样本中的分类来说,效果已经相当不错,基本可以达到99.97%的准确率,但由于安全数据是一种变化很多,且存在语义和次序规则的存在,因此,这种纯靠提取特征个数的建模方式并不能有效提取到安全数据中语序或者说语义的特征,而且由于是对特征个数的统计,一旦普通句子中出现了大量敏感词汇,但本身并不是攻击语句时,就有可能造成误报。

比如:http://www.baidu.com/index.php?context=alert&type=script&userid=1&trans=("7777")

因此,为了能够减小误报,更加精确的提取到无论是xss、sql注入还是其它类型的攻击的语法特征,我开始尝试在向量化过程中,能够让向量中具有记录词汇之间关系的值存在,而恰好数据科学家们也提出了解决这类问题的模型——word2vec。

二、word2vec简单解析

word2vec是一个谷歌的开源项目,简单地说,矩阵刻画了每个词和其上下文的词的集合的相关情况。对这个矩阵进行分解,只取每个词对应在隐含空间的向量。而我所考虑的将其应用的方式就是通过训练模型直接将整个语句向量化,来代替原有的粗暴的特征个数提取向量化方式,过程大概如下图: []() 在python中,gensim模块提供了word2vec,使用起来也非常简单

from gensim.models import word2vec

#加载文档,文档中包含多行攻击payload,并使用空格将每个payload中的每个词进行了分隔
raw_sentences = word2vec.Text8Corpus("veclib.csv")
#训练模型
model = word2vec.Word2Vec(raw_sentences, min_count=1,size=5)

print model['select']
>>>[-1.47539139  0.62246555 -3.04840636  2.49685478 -4.52369213]

print model.similarity('all','union')
>>>0.974170553492

从上面代码中可以看到训练模型其实是非常简单的,只需要准备好格式化好之后的语料库,就可以生成模型了,其中min_count代表了词汇在整个语料库中出现的最小次数,而size则代表了将词汇向量化后的数组大小。

这里在模型训练完成之后,我对select进行了向量化,输出了一个长度为5的数组,然后将sql注入中常出现的组合union all做了单词相似度匹配,得到了97.4%的相似度,可见模型已经发现了这两个词的关系。

三、句向量所遇困难

有了这个模型,对句子经行向量化看起来就是手到擒来的事了,但实际上并非如此,在进行句子向量化的过程中,遇到了以下的三个困难:

  1. 模型只能向量化语料库中出现过的词汇,对于语料库中未出现的词汇,使用model['xxx']的方式会直接报错。
  2. 维度不固定,对于一个url,长度可长可短,切分后词汇的数量也不相同,这样要是对每个词向量化再拼接会使得每个url向量化后维度不一致,造成无法训练。
  3. 词汇切分也会遇到很大的问题,攻击语句中字母、数字、符号混杂,很容易在切分的时候就破坏了原本的结构。

为了解决这两个问题,我尝试了几种办法:

  • 固定维度,无论什么长度的URL,均以相同维度表示
  • 对于语料库中不存在的词汇,以类似[0,0,0,0,0]这样的方式传值

整个函数大概是:

def veclize(model, words,veclen=20, modelsize=5):
    words = re.sub(r"\d+\.?\d*",'0',words)
    wordslice  = nltk.word_tokenize(words)
    new_wordslice = []
    cf.read("w2v.vec")
    common_vec = cf.get("common", "vec").split(",")
    sqli_vec = cf.get("sqli", "vec").split(",")
    xss_vec = cf.get("xss", "vec").split(",")
    all_vec = np.concatenate([common_vec, sqli_vec, xss_vec])
    for i in range(veclen):
        try:
            if wordslice[i] in all_vec:
                new_wordslice.append(model[wordslice[i]])
            elif isinstance(wordslice[i], str):
                if wordslice[i] == "0":
                    new_wordslice.append([-2] * modelsize)
                else:
                    new_wordslice.append([-1] * modelsize)
        except:
            new_wordslice.append([0] * modelsize)

    print wordslice
    print new_wordslice

veclize(model,") AS Xzvm WHERE 7955=7955 AND 3012=(SELECT 3012 FROM PG_SLEEP(5))")

>>>0.98434144245
[-3.63616395  1.65906143 -2.31633949  2.75220418 -5.27639389]
[')', 'AS', 'Xzvm', 'WHERE', '0=0', 'AND', '0=', '(', 'SELECT', '0', 'FROM', 'PG_SLEEP', '(', '0', ')', ')']
[array([ 1.24165082,  0.9568541 , -1.73806798, -1.12680495, -4.77154875], dtype=float32), [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], array([ 2.16253328, -0.85003948, -1.98651695, -2.55953217, -6.86938763], dtype=float32), [-1, -1, -1, -1, -1], [-2, -2, -2, -2, -2], [-1, -1, -1, -1, -1], [-1, -1, -1, -1, -1], array([ 2.16253328, -0.85003948, -1.98651695, -2.55953217, -6.86938763], dtype=float32), [-2, -2, -2, -2, -2], array([ 1.24165082,  0.9568541 , -1.73806798, -1.12680495, -4.77154875], dtype=float32), array([ 1.24165082,  0.9568541 , -1.73806798, -1.12680495, -4.77154875], dtype=float32), [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

最后形成了长度为20的list,list中的每个值又是一个长度为5的list,这样保证了数据维度的一致性。

但实际上,效果却并不好,在尝试过程中发现这样做的话,一是强行规定维度数量会增加或减小原本URL的长度,使得向量不能完整反映URL的真实情况。二是增加的维度是由我自己定义的,很可能对模型所生成的向量产生干扰,影响最终判定结果。

在word2vec尝试失败之后,偶然发现了doc2vec,这个是基于word2vec衍生出来的专门用于句向量的一个库,使用方式和word2vec差不多。

sentence= doc2vec.TaggedLineDocument("veclib.csv")
model = doc2vec.Doc2Vec(sentence,min_count=1,size=10,window=5)

print model["all"]
print model.docvecs[1]

>>>[ 0.83986223  3.20403194 -0.77950877  4.29560566 -6.13165426  1.11592257
  3.05850005  2.41243076  1.35171485 -3.05976152]
[ 0.01543117 -0.03566927 -0.02367479 -0.02821171  0.02317566 -0.00354424
  0.0414261  -0.06460942 -0.01002934  0.00274491]

model['all']是对all这个字符的向量化,而 model.docvecs[1]是输出语料库中第一条payload的向量化结果,看起来非常让人满意,doc2vec自动将语句转化成了指定维度的数组,就省去了我们人工干预。

但同样存在问题的是,doc2vec也无法把直接传递过来的句子进行向量化,只能向量化语料库中的句子,但如果这样的话,我们无法对新来的url进行处理,也就没什么意义了。

四、总结

时序模型的研究到这里就告一段落了,做个总结:

时序模型是一种用于挖掘语义和语序关系的模型,其主要是通过将词语以向量化的表示方式来进行关系间的记录和描述,建立时序模型需要一个巨大的语料库,模型通过复杂的计算公式来分析语料库中词与词间的关系,比如某些词汇经常同时出现,而且靠的很近,那么这两个词的相似度就高。

但其中存在的一个最大问题就是:模型只能对在语料库中存在的词汇进行向量化。

从设计者的角度来想确实可以理解,毕竟没出现过的词汇都不知道它有什么特点,也就无法描述空间位置了。但这同时也给我们的向量化造成了困难,毕竟对一个细分领域的模型(比如sql攻击),无法涵盖到所有词汇。

所以在应用于攻击语句时,时序的向量化很难做,当然也可能是我个人知识不够,实际上有好的办法解决这个问题,但目前来说,个人觉得时序模型的应用需要以下几个特点:

1.维度固定,即限定了被分析文本所提取的向量数

2.语料库完整,被向量化的词汇基本在语料库中都有出现,否则会面临处理异常字符的情况。

3.良好的分词机制,能对语句进行较好的分词处理,才能提取到关键词汇。

虽然有这么多的限制,但时序模型在安全上可能能应用到的场景还是很多的,尤其是对一些存在次序关系的场景,比如持续性的攻击检测模型,或一些关联性强的比如恶意代码检测等。

本站部分资源收集于网络,纯个人收藏,无商业用途,如有侵权请及时告知!