理工渣眼中的HMM及安全应用

虽然是理工妹子,但仍是数学渣。症状之一就是每次学习算法都能把自己绕成鸡窝头。所以尝试写一篇数学渣眼中的HMM。
我们先看一个让人头疼的HMM定义式(喜欢从公式下手是我多年来应付考试养成的不良习性)
一、HMM五元素
HMM简介 - 碳基体 - 碳基体
N:隐藏状态数 hidden states
M:观测状态数 observed states
A: 状态转移矩阵 transition matrix
B:发射矩阵  emission matrix
pi:初始隐状态向量 initial state vector
好了,接下来我们用数学渣可以理解的语言来解释上面都是些什么鬼
女主:小红  用食物丈量心情
           心情状态有三种: 开心、正常、崩溃
           上面三种状态的时候吃的食物也有三种: 汉堡、 西瓜、啤酒
男主:小明 面部表情识别障碍
因此对男主而言,
隐藏状态:女主的心情状态
观测状态:女主吃的食物
隐藏状态数 :N=3
观测状态数:    M=3
初始隐状态向量pi:
对照下表看:女主处于各种心情状态的概率,例如女主51%的概率是正常的,36%的概率是开心的,13%的概率是崩溃的
 开心  正常  崩溃
 0.36  0.51  0.13
状态转移矩阵A:
上一个隐状态到下一个隐状态的转化概率矩阵
对照下表看:在女主上一个状态是开心的条件下,则此刻状态是开心的概率为36.5%,正常的概率为50%,崩溃的概率为13.5%
  开心    正常  崩溃
  开心  0.365  0.500  0.135
  正常  0.250  0.125  0.625
 崩溃  0.365  0.265  0.370
 发射矩阵B:
隐状态对应的观测状态的概率
对照下表看:在女主是开心的状态下,她吃汉堡的概率是10%,西瓜的概率是20%,啤酒的概率是70%
 汉堡  西瓜  啤酒
 开心  0.1  0.2  0.7
 正常  0.5  0.25  0.25
 崩溃  0.8  0.1  0.1

 

一个HMM模型就由上面描述的隐藏状态数N,观测状态数M,初始隐状态向量pi,状态转移矩阵A,混淆矩阵B五个要素组成。

我们知道了什么是HMM,接下来看HMM是干嘛的,用mahout的HMM库来示例HMM解决哪三类问题,仍然用小红和小明的场景

二、HMM解决的三类问题-mahout示例

现在男主小明开始做任务了,我们用现成的工具mahout来示例

安装指南(仅介绍local版)

修改


任务一:学习(本例中根据女主吃的食物序列,推断一个合适的HMM模型)

输入:观测状态序列——女主吃的食物序列,我们用数字表示对应的食物与心情
           0:汉堡
           1:西瓜
           2:啤酒
           0 :崩溃
           1:开心
           2:   正常
输出:生成一个合适的HMM模型
算法:BaumWelch

输入观测序列后,开始生成HMM模型


我们看结果

Initial probabilities: 初始隐状态向量pi
0 1 2
0.062295949769082204 0.22250521455286396 0.7151988356780538
Transition matrix:状态转移矩阵A:
0 1 2
0 0.3765444789556002 0.5583673988903969 0.06508812215400292
1 0.3759312048603327 0.2560959620304218 0.36797283310924545
2 0.5383787685979908 0.24752553248847228 0.21409569891353694
Emission matrix: 发射矩阵
0 1 2
0 0.4419117509334424 0.3106990713267408 0.2473891777398168
1 0.20948851558479514 0.2830936761513362 0.5074178082638686
2 0.34341499252552676 0.40310175949497634 0.2534832479794969

任务二:预测(根据上一个任务生成的HMM模型来预测女主后续会吃的东西)
输入:HMM模型
输出:预测后续的观测状态,或者计算给定规则状态序列的概率(这个我们在下一个场景中描述)
算法: ForwardBackward

我们看结果


预测女主后续会吃的东西依次为 :啤酒,啤酒,汉堡,汉堡,西瓜,啤酒,西瓜,啤酒,啤酒、西瓜

任务三:编码(根据女主吃的东西,判断女主当前的心情,这个也是男主最关心的任务,女孩的心思你别猜。。。)
输入:HMM模型,观测状态序列
输出:观测状态序列对应的隐藏状态序列
算法viterbi
输入观测状态序列,本例中女主吃的食物

判断观测状态序列对应的隐状态序列


我们看结果


可以看到女主

吃的东西 :   啤酒,啤酒,汉堡,汉堡,西瓜,啤酒,西瓜,啤酒,啤酒、西瓜
对应的心情: 正常,开心,正常,崩溃,崩溃,开心,正常,崩溃,开心,正常
三、安全上的应用—— jahmm示例
现在我们知道了什么是HMM以及HMM能做什么,最关键的时刻到了,现实场景的应用。我们先提出
需要解决的问题—HTTP异常请求的检测(弥补基于签名检测的不足)
解决方案猜想——HTTP请求内容实际是一系列字符串,而正常字符串出现的概率远大于异常字符串。我们可以靠概率来划分正常请求与异常请求。
有了这个前提,再结合HMM的学习功能与预测功能,学习功能能根据观测序列(HTTP请求内容)生成最适合的HMM模型,而预测功能能在HMM模型下能计算指定观测序列(HTTP请求内容)的概率 。我们得出下面这个
解决方案——两个阶段解决问题:
阶段一: 训练; 使用正常日志生成HMM模型,并设置正常请求的概率范围
阶段二: 检测; 使用待检测的请求来计算该请求在上一步所生成的HMM模型中的概率,不在正常请求概率范围内的则判定为异常请求。
原理:我们可以根据概率分布来区分正常与异常请求的原理是正常请求远远多于异常请求;正常请求是相似的,异常请求各异
接下来是代码实现,我们先了解一下jahmm——java实现的hmm算法库。我们从命令行使用来了解其功能,接受能力强的,可以直接看第3部分——实现 HTTP协议异常检测的关键部分
1. jahmm 命令行
(1) 安装指南

我们从命令行熟悉这款工具

(2) 选项说明

1)生成hmm模型


2)打印这个hmm模型


也可以直接打开文本看


3)使用k-means算法生成HMM模型

输入:观测状态序列
输出:HMM模型

使用k-means算法生成HMM模型, 隐状态数为3,观测状态数为3

查看模型

4)使用BaumWelch算法生成HMM模型

输入:观测状态序列
输出:HMM模型
使用BaumWelch算法生成HMM模型,隐状态数为3,观测状态数为3,算法迭代次数为10

查看模型

5)按指定的HMM模型生成观测序列

输入:HMM模型
输出:观测序列

 6)计算两个HMM模型之间的距离 


结果为


(3) 文件格式说明

jahmm定义了两种文件格式
a. 观测序列文件
obser1; obser2; obser3;
观测序列之间用分号+空格隔开
多个观测序列用换行符号隔开
b. HMM模型文件

2. jahmm java 接口

在使用命令行熟悉了jahmm的功能后,我们先熟悉一下我们会用到的jahmm 关键类
(1)HMM类(最重要的类)
属性:
初始变量pi      double pi[];
隐状态转移矩阵double a[][];
发射矩阵B   ArrayList<Opdf<O>> opdfs;
构造函数  
Hmm(int nbStates, OpdfFactory<? extends Opdf<O>> opdfFactory) 初始向量,状态转移函数,发射矩阵 皆取平均值
Hmm(double[] pi, double[][] a, List<? extends Opdf<O>> opdfs) 用指定初始向量,状态转移函数,发射矩阵生成HMM对象
Hmm(int nbStates) 初始向量,状态转移函数,发射矩阵 皆取空
getter
初始向量矩阵:double getPi(int stateNb)
发散矩阵:Opdf<O> getOpdf(int stateNb)
状态转移矩阵:double getAij(int i, int j)
setter
初始向量矩阵:setPi(int stateNb, double value)
发散矩阵:setOpdf(int stateNb, Opdf<O> opdf)
状态转移矩阵:setAij(int i, int j, double value)
方法:
获得隐状态数:int nbStates()
获得指定观测状态序列对应的最有可能的隐状态序列,使用viterbi算法 int[]mostLikelyStateSequence(List<? extends O> oseq)
获得指定观测状态序列的概率,使用ForwardBackward算法   doubleprobability(List<? extends O> oseq)
获得指定观测状态序列的概率(用自然对数来表示),使用ForwardBackward算法 double lnProbability(List<? extends O> oseq)
获得指定观测状态序列的P[oseq,sseq|H]概率 double probability(List<?extends O> oseq, int[] sseq)
获得HMM文本描述 toString()  
获得HMM文本描述String toString(NumberFormat nf)

(2)learn 训练算法类

BaumWelchLearner

setter
设置迭代次数 setNbIterations(int nb) 默认为9
getter
获得迭代次数  getNbIterations()
方法
 训练  learn(Hmm<O> initialHmm, List<? extends List<? extends O>>sequences)
BaumWelchScaledLearned (继承BaumWelchLearner,避免underflows
(3)HMM代码示例
接下来的代码用来示例生成HMM模型,完成HMM解决的三个问题,以及HMM模型可视化

注意

jahmm BaumWelch的观测状态序列必须超过1个,否则就会抛出Observation sequence too short异常
3. 实现 HTTP协议异常检测的关键部分
整个过程由三部分组成
(1)日志解析
输入: 正常的访问日志(可以混有少量攻击日志)
可以按以下条件来去重筛选
响应码 2xx, 3xx
动态页面
参数名符合规范: 字符,数字,数组,排除参数名注入
输出:参数名值对 (对url重写需要另行考虑)
(2)参数值泛化处理
输入:参数值
输出:观测序列
转换规则:
序列  符号  参数值类型
 0  U 若字符串为URI格式,标记为U
 1  N 若字符为非ASCII码,标记为N,字符包括\x00-\x07以外
2  W 若字符为word类型,标记为W,字符包括
数字\x30-\x39、字母\x41-\x5A \x61-\x7A、下划线\x5F  ,一共63个字符
3  S 若字符为空格类型,标记为S,字符包括\x00NUL \x09\t \x0A\n \x0B\v \x0C\f \x0D\r \x20space ,一共7个字符
 4  V  若字符为控制字符类型,标记为V,字符包括\x01-\x08、\x0E-\x1F、\x7F,一共27个字符
 5  保留字符  ! ” # $ % & ‘ ( ) * + , – . / : ;< = > ? @ [ \ ] ^ ` { | } ~,一共31个字符
(3) 训练阶段
指定隐状态数hidden与观测状态数初始化一个HMM对象

Hmm<ObservationInteger> hmm = new Hmm<ObservationInteger>(hidden, new OpdfIntegerFactory (observed));

使用上一步生成的观测序列,调用BaumWelch算法进行学习,生成合适的HMM的对象

BaumWelchScaledLearner bw = new BaumWelchScaledLearner();
bw.setNbIterations(20000);
Hmm<ObservationInteger> learnedHmm_bw = bw.learn(originHmm, observSequence);

获得每个观测序列的概率,指定合法值的概率的范围或阈值

double seq_i_prob = learnedHmm_bw.probability(Sequence_i);

我们可以将训练结果以key:value对的方式存储在数据库中,例如redis。
key为 /path?paramName
value为 val对应的HMM训练对象learnedHmm_bw、观测状态序列、概率阈值的序列化值

(4)检测阶段

将待检测日志,通过上面介绍的日志解析与参数值泛化处理生成观测序列,然后计算出该观测序列在第二步学习的HMM对象中的概率值

double seq_to_prob = learnedHmm_bw.probability(Sequence_to);

判断概率是否在合法范围内

最简单的方法是将阈值设置为所有观测序列中的最小值,

我们还可以按概率的统计分布特征(中位数,算术平均,方差)来选取范围

当然这些都需要根据实际情况进行参数调优

好了,关键步骤讲完了。在实际应用中,特别是日志量过大的情况下,我们需要做很多异常和优化处理,例如

(1)异常处理

测状态数不能超过36,否则抛出异常

学习时间花费不能超过XXX,否则抛出异常

(2)观测状态序列及序列集合的优化

训练阶段:观测序列数量过大的情况下,会影响训练时间与结果,所以需要做reduce处理,例如观测序列包含的字符相同,则忽略顺序认为是同一个序列

检测阶段:待检测观测序列的取样处理,例如相同观测状态的合并

(3)隐状态数的选择

最简单的是选取所有观测状态序列中去重后的观测状态数的平均值

4. 大数据处理

当日志量很大的情况下,我们需要引入数据的并行处理。于是hadoop上场了,我们将日志存储在HFDS里,采用Hadoop提供的MapReduce计算机制,来完成日志异常训练与检测,调用示例如下

hadoop jar HMM_Abnormal.jar HmmAbnormal.train /data/in/ /data/out/model/

hadoop jar HMM_Abnormal.jar HmmAbnormal.check /data/in/ /data/out/model/ /data/out/result/

5. 总结

HMM异常检测同朴素贝叶斯分类器也是白名单(异常)的思路,在第一学习阶段通过对正常的请求的学习,得出正常请求的模式,在第二检测阶段将不符合正常模式则判定为异常。

实时的HMM可以用于WAF或IPS系统实时检测,离线的HMM可以用于WAF或IPS误报漏报运维。

我们知道白名单(异常)最大的优点是弥补黑名单签名知识库更新滞后的漏报情况,但其在模型训练数据污染严重(攻击日志大量混入到学习日志中)、模型训练不充分模型训练不到(有些有漏洞的攻击请求不会出现在正常请求中)的情况下会丧失这一优点。因此需要其他模型来弥补缺陷,使用数据分析来解决问题,就是要多个模型(算法)综合使用,这是个有挑战的路线,与君共勉。

参考

http://www.52nlp.cn/hmm-learn-best-practices-one-introduction
HMM-Web: a framework for the detection off attacks against Web Application
【via@碳基体