一种新的分词方法在机器学习中的应用

两年前,从兜哥的《Web安全机器学习入门》这本书开始与机器学习结缘,慢慢了解了什么是贝叶斯、HMM、SVM,以及它们应用的场景。

关于算法公式,在这里就不班门弄斧了,说实话很多都看不懂,“咱也不敢说,咱也不敢问”。

当前我们对于恶意请求的自动检测主要是基于黑规则的检测,众所周知,这种基于规则库的安全防御被动、滞后,无法检测未知的攻击,造成的影响就是漏报挺多,这也是引入机器学习的重要原因。

本文主要分享一下,在做基于HMM的SQL注入检测时遇到的一些问题及解决方法,至于HMM的原理及概念,有兴趣的同学可以看看 《学点算法搞安全之HMM(上篇)》 这篇文章。 https://mp.weixin.qq.com/s/Cc15keu4quPBZc1N4bS58g

建模思路

00

此次项目建模的整体思路如下:

1. 收集数据集

常见的SQL注入攻击载荷主要集中在请求参数中,解析GET、POST请求的参数值作为样本数据。此次项目收集了大约20万白样本数据。

2. 数据处理与特征提取

  • 数据降噪,如去掉注释、urldecode等;

  • 特征提取:包括分词、泛化、特征向量转化,本文采用自研的分词器,分词、特征转化二合一。

3. 使用HMM训练检测模型

4. 最后模型调优,并使用检测模型检测未知 URL

特征提取

01

正则表达式分词与泛化

01

关于特征提取,刚开始使用的是正则分词+泛化的处理方式。这种方式也是目前使用较多的一种方式,毕竟正则简单易上手。以下列字符串为例:

使用正则 r e.split(r'( |,)’, ss) 分词,结果为:

观察结果,不符合预期,效果不是很理想。对于sql来说,concat是个函数,属于关键字,需要单独分出来,否则后续不好泛化。

在实际场景中,黑客们为了达成目的,一般都不走寻常路,不要奢望他们按常理出牌。也就是说payload会千奇百怪,出现各种变种,分隔符不固定,而为了能尽可能多的覆盖,正则也会变得越来越复杂,相应的执行效率也会直线下降。

02

分词后,对分词结果进行泛化,依据:

  • [a-zA-Z]泛化为A

  • [0-9]泛化为N

  • [\-_]泛化为C

  • 其他字符泛化为T

泛化后的结果为:

  • 99999泛化为N

  • union泛化为AAAAA

03

正则分词遇到的问题:

1>分词不精准,分隔符不固定,为了能尽可能多的覆盖,需要不断完善正则表达式

2>覆盖范围越广,正则越复杂、效率越低

3>采用单字符的特征提取,弱化了SQL Keyword的影响,字符串长度对预测结果的影响大于SQL关键字

4>没有突出SQL解析的特点

基于SQL解析的词法分词

基于SQL解析的词法分词是一种新的分词方法,也是本次项目中使用的方法, 此方法将正则分词和特征向量提取合二为一,即分词器在返回分词结果的同时,也将特征向量值定义好

基于SQL解析的词法分词逻辑很简单,分词器模仿sql解析器进行分词,逐个扫描字符串,遇到符号即返回,遇到字符即继续,以及检测解析出的字符串是否是keyword。部分代码如下:

01

定义SQL解析器可以识别的符号、运算符、关键字并预设特征向量值:

02

字符验证函数定义:

这个函数是用来判断字符是否可以被SQL标识器识别的。这里当初卡了很久,原因是因为结果不准确。对于ASCII字符集,高位Bit集合的字母都能被SQL标识器识别,对于7-bit的字符,则sqlIsEbcdicIdChar必须为1。后来又分析了SQL99, SQL/MM, and SQLJ: An SQL99, SQL/MM, and SQLJ: An Overview of the SQL Overview of the SQL Standards SQL : 1999, Formerly known as SQL3的语法定义, 其中比较值得注意的是对于char的识别鉴定, 根据分析sql各个标准中char类型的定义,首先进行了ascii码的判断方式,但返回结果并不理想;在查询了相关资料(sql的各种标准)后, 又引入了ebcdic这个字符集,做双重校验,结果终于准确了! 关于ebcdic的定义:http://www.astrodigital.org/digital/ebcdic.html,有兴趣的可以去研究下。

03

字符串扫描:

如果字符是合法的SQL运算符则返回对应的状态值,如果字符是字母数字则继续扫描,直到指针越界或者IdChar为False。

04

SQL 关键词识别

SQL标识器可以识别的关键字组成的字符串:

关键字对应的字符串长度:

关键字在zText中的索引位置(offset):

举列说明:

  • 字符串:str = “INDEX”

  • 首字母大写为:I,

  • 长度:5

如果zText[aOffset[1]:aOffset[1]+5] == str.upper() 为True,代表此字符串为Keyword,否则为普通字符串。

zText  aOffset aLen这些数组都是提前计算出来,目的是为了提高检索效率。 特殊说明: 为了区别关键字和普通字符串,对关键字的特征向量值加了权重50000。

05

性能对比:

06

总结:

1>针对简单的字符串性能优势不明显,但是放在复杂的日志环境中优势就能慢慢体现出来了,日志请求越长,效果越明显。

2>比简单正则覆盖范围广,比复杂正则速度快。

3>分词精准度高。

4>具有针对性,sql解析器识别的keyword能重点标记。

训练HMM检测模型

02

HMM进行文本异常检测,有两种方式:

  1. 以白找黑:相当于找不同

  2. 以黑找黑:相当于找相似

以白找黑,是给HMM输入纯白样本,让其记住正常url参数的模型转化概率,然后找不同。本文采用的是以白找黑模型训练,部分代码如下:

预测结果为:

定义T为阈值,概率小于T的参数识别为异常。

阈值T过小,误报会少,但是漏报会高;阈值T过大,误报会多,但是漏报会少。 本人的观点是接受误报,尽量做到不漏报。 误报可以后续进行二次处理,如注入验证或者其他维度检测。

预测结果二次验证

03

事实证明,每天都会检测到大量结果为失败或者404的类似告警,导致系统存在大量误报。偶有成功的也会埋没在误报当中,久而久之报警处理人就不想再去查看,或者查看的不够仔细,从而导致被黑。因此就需要对检测结果进行二次验证。

为了完成这个目标,基于sqlmap改了一个简化版,用于验证GET方式的SQL注入是否成功。

  • 第一步:先验证请求参数是否是动态的,如果不是,则忽略;

  • 第二步:如果是动态参数,进行SQL注入检测,检测为True,则进入报警队列。

http://localhost/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#

目前此开源版本为初版,只支持GET方式的部分注入类型验证。

结束语

03

我们在进步,黑产也在进步,而且是飞速发展。见识了黑产的群控和各种高级的设备,才知道我们在使用小米步枪和飞机大炮在战斗,这是一项艰巨的任务。技术无分善恶,但落后就要挨打!雄关漫道真如铁,而今迈步从头越!加油吧,少年!。

代码传送门:

  1. 分词器:https://github.com/skskevin/UrlDetect/tree/master/tool/SQLTokenizer

  2. 注入验证程序:https://github.com/skskevin/UrlDetect/tree/master/tool/SimpleSqlmap