学点算法搞安全之apriori

2017-12-20 16:45:599712人阅读

前言

在企业安全建设专题中偶尔有次提到算法的应用,不少同学想深入了解这块,所以我专门开了一个子专题用于介绍安全领域经常用到的机器学习模型,从入门级别的SVM、贝叶斯等到HMM、神经网络和深度学习(其实深度学习可以认为就是神经网络的加强版)。

23_1.jpg

关联规则挖掘

关联规则挖掘通常是无监督学习,通过分析数据集,挖掘出潜在的关联规则,最典型的一个例子就是尿布和啤酒的故事。相传沃尔玛的数据分析人员通过分析大量购物清单发现相当一部分消费者会同时购买尿布和啤酒,结果他们把尿布和啤酒赫然摆在一起出售,结果销量又双双增长。关联规则分析的结果是客观现象的体现,有的显然易见,比如同时购买三文鱼和芥末,有的勉强可以解释,比如尿布和啤酒,有的就匪夷所思,比如打火机和奶酪。关联算法中最著名的就是apriori算法。

apriori简介

首先介绍三个基本概念,支持度、置信度和频繁k项集。

支持度,P(A ∩ B),既有A又有B的概率,它表现的是A和B两个事件相对整个数据集合同时发生的频繁程度,比如尿布和啤酒的支持度是0.2,表明有20%的消费清单中,消费者同时购买了尿布和啤酒。

置信度,P(B|A),在A发生的事件中同时发生B的概率 P(AB)/P(A),它表现的是在AB两个事件的相关程度,和整个数据集合的大小没有关系,比如尿布和啤酒的置信度为0.8,表明在同时购买了两者的消费者中,购买尿布的80%又购买了啤酒。

需要特别说明的是,P(A ∩ B)=P(B ∩ A),但是P(B|A)和P(A|B)是两回事。

如果事件A中包含k个元素,那么称这个事件A为k项集事件A满足最小支持度阈值的事件称为频繁k项集。

apriori算法就是挖掘同时满足最小支持度阈值和最小置信度阈值的关联规则。

apriori基本原理

Apriori算法使用频繁项集的先验知识,使用一种称作逐层搜索的迭代方法,k项集用于探索(k+1)项集。首先,通过扫描事务(交易)记录,找出所有的频繁1项集,该集合记做L1,然后利用L1找频繁2项集的集合L2,L2找L3,如此下去,直到不能再找到任何频繁k项集。最后再在所有的频繁集中找出强规则,即产生用户感兴趣的关联规则。

其中,Apriori算法具有这样一条性质:任一频繁项集的所有非空子集也必须是频繁的。因为假如P(I)< 最小支持度阈值,当有元素A添加到I中时,结果项集(A∩I)不可能比I出现次数更多,因此A∩I也不是频繁的。

apriori的应用

在安全领域,aprioir的应用非常广泛,凡是需要挖掘潜在关联关系的都可以尝试使用,比如关联waf的accesslog与后端数据库的sqllog,识别ssh操作日志中异常操作等。我们这里以分析accesslog为例子。我们从xssed网站的样例以及waf的拦截日志中提取xss攻击日志作为样本,示例日志如下:

/0_1/?%22onmouseover='prompt(42873)'bad=%22%3E

/0_1/api.php?op=map&maptype=1&city=test%3Cscript%3Ealert%28/42873/%29%3C/script%3E

/0_1/api.php?op=map&maptype=1&defaultcity=%e5%22;alert%28/42873/%29;//

我们目标是分析出潜在的关联关系,然后作为SVM、KNN等分类算法的特征提取依据之一。机器没有办法直接识别日志,需要逐行将日志文本向量化,最简单的方式就是按照一定的分割符切割成单词向量,示例代码如下:

myDat=[]

with open("xss-train.txt") as f:

    for line in f:

        tokens=re.split('\=|&|\?|\%3e|\%3c|\%3E|\%3C|\%20|\%22|<|>|\\n|\(|\)|\'|\"|;|:|,',line)

        myDat.append(tokens)

    f.close()

切割后的向量示例如下:

['/0_1/', '', 'onmouseover', '', 'prompt', '42873', '', 'bad', '', '', '', '']

['/0_1/api.php', 'op', 'map', 'maptype', '1', 'city', 'test', 'script', 'alert%28/42873/%29', '/script', '', '']

我们以十分严格的置信度来运行,试图找到关联关系接近100%的情况:

L, suppData = apriori(myDat, 0.1)

rules = generateRules(L, suppData, minConf=0.99)

有趣的现象出现了:

frozenset(['//', '1']) --> frozenset(['', 'alert']) conf: 1.0

frozenset(['1', 'script']) --> frozenset(['', '/script']) conf: 1.0

frozenset(['/', 'script']) --> frozenset(['', '/script']) conf: 0.997576736672

frozenset(['type', 'title']) --> frozenset(['a', '']) conf: 0.996108949416

frozenset(['a', 'title']) --> frozenset(['', 'type']) conf: 0.993210475267

frozenset(['a', 'c']) --> frozenset(['', 'm']) conf: 1.0

frozenset(['1', '/', 'script']) --> frozenset(['', '/script']) conf: 1.0

frozenset(['1', 'alert', 'script']) --> frozenset(['', '/script']) conf: 1.0

frozenset(['alert', '/', 'script']) --> frozenset(['', '/script']) conf: 0.997416020672

frozenset(['1', 'alert', '/', 'script']) --> frozenset(['', '/script']) conf: 1.0

有些结果容易理解,比如'script'和'1'出现的话会100%的概率导致'/script',有些结果匪夷所思,比如'a'和'c'出现的话会100%的概率导致'm'。

apriori的代码实现

网上apriori的代码实现很多,这里给出其中一种实现。

def createC1(dataSet):

   C1 = []

   for transaction in dataSet:

       for item in transaction:

           if [item] not in C1:

               C1.append([item])

   C1.sort()

   return map(frozenset, C1)

def scanD(D, Ck, minSupport):

   ssCnt = {}

   for tid in D:

       for can in Ck:

           if can.issubset(tid):

               ssCnt[can] = ssCnt.get(can, 0) + 1

   numItems = float(len(D))

   retList = []

   supportData = {}

   for key in ssCnt:

       support = ssCnt[key] / numItems

       if support >= minSupport:

           retList.insert(0, key)

       supportData[key] = support

   return retList, supportData

def aprioriGen(Lk, k):

   retList = []

   lenLk = len(Lk)

   for i in range(lenLk):

       for j in range(i + 1, lenLk):

           L1 = list(Lk[i])[: k - 2];

           L2 = list(Lk[j])[: k - 2];

           L1.sort();

           L2.sort()

           if L1 == L2:

               retList.append(Lk[i] | Lk[j])

   return retList

def apriori(dataSet, minSupport=0.5):

   C1 = createC1(dataSet)

   D = map(set, dataSet)

   L1, suppData = scanD(D, C1, minSupport)

   L = [L1]

   k = 2

   while (len(L[k - 2]) > 0):

       Ck = aprioriGen(L[k - 2], k)

       Lk, supK = scanD(D, Ck, minSupport)

       suppData.update(supK)

       L.append(Lk)

       k += 1

   return L, suppData

def calcConf(freqSet, H, supportData, brl, minConf=0.7):

   prunedH = []

   for conseq in H:

       conf = supportData[freqSet] / supportData[freqSet - conseq]

       if conf >= minConf:

           print freqSet - conseq, '-->', conseq, 'conf:', conf

           brl.append((freqSet - conseq, conseq, conf))

           prunedH.append(conseq)

   return prunedH

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):

   m = len(H[0])

   if len(freqSet) > m + 1:

       Hmp1 = aprioriGen(H, m + 1)

       Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)

       if len(Hmp1) > 1:

           rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

def generateRules(L, supportData, minConf=0.7):

   bigRuleList = []

   for i in range(1, len(L)):

       for freqSet in L[i]:

           H1 = [frozenset([item]) for item in freqSet]

           if i > 1:

               rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)

           else:

               calcConf(freqSet, H1, supportData, bigRuleList, minConf)

   return bigRuleList

本文来自兜哥带你学安全,原文链接:兜哥带你学安全

文章图片来源于网络,如有侵权请联系我们

0
现金券
0
兑换券
立即领取
领取成功