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

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

1.参数建模

常见的基于GET请求的XSS、SQL注入、RCE,其攻击载荷主要集中在请求参数中,以XSS为例:


/0_1/include/dialog/select_media.php?userid=%3Cscript%3Ealert(1)%3C/script%3E

正常的http请求中参数的取值范围都是确定的,这里说的确定是指可以用字母数字特殊字符来表示,并非说都可以用1~200这种数值范围来确定。以下面的几条日志为例:


/0_1/include/dialog/select_media.php?userid=admin123
/0_1/include/dialog/select_media.php?userid=root
/0_1/include/dialog/select_media.php?userid=maidou0806
/0_1/include/dialog/select_media.php?userid=52maidou
/0_1/include/dialog/select_media.php?userid=wjq_2014
/0_1/include/dialog/select_media.php?userid=mzc-cxy

肉眼观察可以归纳出userid字段由字母数字和特殊字符'-_'组成,如果你足够强大可以看完上万的正常样本,甚至都可以总结取值范围为由字母、数字、下划线、连字符组成,且长度大于等于4,使用正则表达式即为[0-9a-zA-Z-_]{4,}。如果有上亿的日志含上百万的参数,人工如何完成?这时候机器学习可以发挥作用了。

以uid字段为例,uid的取值作为观察序列,简化期间可以对uid的取值进行范化,整个模型为三阶HMM,隐藏序列的状态只有四个,S1、S2、S3、S4:

·[a-zA-Z]范化为A;

·[0-9]范化为N;

·[\-_]范化为C;

·其他字符范化为T。

以URL参数wjq_2014为例,转换过程如图12-5所示,观察序列是wjq_2014,对应的范化为AAAACNNN,对应的隐藏序列为S1 S1 S1 S2 S3 S3 S3 S3。

图12-5 URL参数建模

同理,URL参数admin 123和root范化结果为:

·admin123范化为AAAAANNN;

·root范化为AAAA。

图12-6 HMM状态迁移

隐藏序列就是S1~S4四个状态间循环转化(见图12-6),这个概率称为转移概率矩阵,同时四个状态都有确定的概率,以观察序列中的A、C、N、T 4个状态展现,这个转换的概率称为发射概率矩阵。HMM建模过程就是通过学习样本,生成这两个矩阵的过程。

典型的转移概率矩阵如表12-1所示。

表12-1 转移概念矩阵示例

典型的发射概率矩阵如表12-2所示。

表12-2 发射概念矩阵示例

在生产环境中范化需谨慎,至少域名、中文等特殊字符需要再单独范化。

2.数据处理与特征提取

由于每个域名的每个url的每个参数的范围都可能不一样,有的userid可能是[0-9]{4,},有的可能是[0-9a-zA-Z-_]{3,},所以需要按照不同域名的不同url的不同参数分别学习。范化过程如下:


def etl(str):
vers=[]
for i, c in enumerate(str):
c=c.lower()
if ord(c) >= ord('a') and ord(c) <= ord('z'):
vers.append([ord('A')])
elif ord(c) >= ord('0') and ord(c) <= ord('9'):
vers.append([ord('N')])
else:
vers.append([ord('C')])
return np.array(vers)

友情提示: 为了避免中文等字符的干扰,ASCII值大于127或者小于32的可以不处理直接跳过。

从weblog中提取url参数,需要解决url编码、参数抽取等严峻问题,还好Python有现成的接口:


with open(filename) as f:
for line in f:
#切割参数
result = urlparse.urlparse(line)
# url解码
query=urllib.unquote(result.query)
params = urlparse.parse_qsl(query, True)
for k, v in params:
#k为参数名,v为参数值

友情提示: urlparse.parse_qsl解析url请求切割参数时,遇到';'会截断,导致获取的参数值缺失';'后面的内容,这是个大坑,在生产环境中一定要注意这个问题。

3.训练模型

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


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

训练样本得分为:


score:16 query param:admin123
score:9 query param:root
score:21 query param:maidou0806
score:16 query param:52maidou
score:15 query param:wjq_2014
score:12 query param:mzc-cxy

4.模型验证

HMM模型完成训练后通常可以解决3大类问题:一是输入观察序列获取概率最大的隐藏序列,最典型的应用就是语音解码以及词性标注;二是输入部分观察序列预测概率最大的下一个值,比如搜索词猜想补齐等;三是输入观察序列获取概率,从而判断观察序列的合法性。参数异常检测就输入第三种。

我们定义T为阈值,概率低于T的参数识别为异常,通常会定义T比训练集最小值略大,在此例中可以取10:


with open(filename) as f:
for line in f:
# 切割参数
    result = urlparse.urlparse(line)
# url解码
    query = urllib.unquote(result.query)
    params = urlparse.parse_qsl(query, True)
for k, v in params:
    if ischeck(v) and len(v) >=N :
    vers = etl(v)
    pro = remodel.score(vers)
    if pro <= T:
        print "PRO:%d V:%s LINE:%s " % (pro,v,line)

以userid=%3Cscript%3Ealert(1)%3C/script%3E为例子,经过解码后为<script>alert(1)</script>,范化后为TAAAAAATAAAAATNTTTAAAAAAT,score为-13945,识别为异常。这种HMM的使用方式是通过学习正常来识别异常,即通常说的“以白找黑”。“以白找黑”的优点非常明显,就是理论上可以发现全部基于参数的异常访问,但是,缺点是扫描器访问、代码异常、用户的错误操作、业务代码的升级等,都会产生大量误报;所以目前另外一种开始流行的方法是,通过学习攻击报文,训练攻击模型,然后“以黑找黑”。这种方法虽然理论上可能会遗漏真实攻击,但是结果更加可控,可以达到可运维状态。下面我们将介绍基于HMM的“以黑找黑”。