第二届阿里云安全算法挑战赛经验分享
一、简介
我在2017年8月参加了这个比赛,比赛分两道子赛题,我和队友分别负责其中一道,我负责的是《扫描爆破拦截》,单题排名5/959,两题综合12/959。原本应该在比赛完就写博客记录的,但事情太多以至于拖延到了现在,找实习的时候翻回从前比赛时期零碎的笔记,才想起来自己原来做过这么些事情,经历过这样的一些思考,还发现了一些纰漏和可以改进的地方。现在重写一份经验分享,旨在记录过去,督促自己寻求创新。
二、赛题
2.1 赛题简介
详细的赛题可以看这里。显然,《扫描爆破拦截》的背景是用户通过不同的端口向阿里云主机发送连接请求,请求之中存在着部分恶意用户,他们的目的不是简单地连接主机,而是要通过端口进行扫描,利用密码本暴力爆破登录主机。赛题提供了一段时间内(一个月)的连接记录和恶意用户名单,要求我们预测接下来一段时间(一周)的恶意用户。
2.2 赛题数据
2.2.1 提供特征
样本量估计在2000w左右(线上赛没有留下记录,大致记得是这样的数量级)。
用户连接记录
序号 | 特征名 | 含义 |
---|---|---|
1 | client_ip | 云主机IP,AB段已hash,CD段保留 |
2 | client_port | 云主机端口 |
3 | source_ip | 访客IP,AB段已hash,CD段保留 |
4 | hashuserid | 云主机拥有者ID,已hash |
5 | hh | 连接请求的发起时间:小时 |
6 | mi | 连接请求的发起连接时间:分钟 |
7 | ss | 连接请求的发起连接时间:秒,精确到10秒 |
8 | counts | 10秒内连接次数 |
9 | ds | 日期 |
用户登录记录
序号 | 特征名 | 含义 |
---|---|---|
1 | client_ip | 云主机IP,AB段已hash,CD段保留 |
2 | client_port | 云主机端口 |
3 | source_ip | 成功登录者IP,AB段已hash,CD段保留 |
4 | hh | 登录时间:小时 |
5 | ds | 登录日期 |
攻击者名单
序号 | 特征名 | 含义 |
---|---|---|
1 | client_port | 恶意访问云主机端口 |
2 | source_ip | 恶意访问者IP,AB段已hash,CD段保留 |
3 | ds | 恶意访问日期 |
2.2.2 评估方式
比赛以F值的形式评估,针对日期
i
i
端口 ,设置F值
最后以整体平均的方式作为综合评分 Score=avg(Sij) S c o r e = a v g ( S i j )
2.2.3 关于赛题数据的思考
- 非实时系统
标签 Label 的格式是 source_ip : client_port : ds (下称 id ),因此在实际应用中,需要在这一天结束时统计完整的连接记录才能进行判断,不具有实时性,这一点与基于数据包解析的恶意攻击检测有本质的区别。 - 攻击者判定
标签 Label 的格式决定,只要 source_ip 在 ds 这一天的任意时刻对 client_port 进行过一次攻击行为,他在当天对该端口的所有访问行为都会被判定为恶意攻击。因此,可以预计有部分 id, 即使在某一天的大部分连接行为都与正常访问者无异,只需一次爆破行为,即足以判定为攻击者,可以根据这一点挖掘潜在攻击者。 - 样本采样的灵活性
虽然恶意攻击的判定单位为“天”,但 instance 是精确到10秒的,因此参与训练的 instance 不一定以“天”为单位,若以“小时”或“十秒”为单位进行采样,则可扩展样本数量。 - 测试集Label的复数性
每一个 id 都应有多个 Label 相同的样本,而模型对测试集的预测却不一定,可以预计大多会出现同一个 id 下的 Label 各异的情况,届时可制定规则来判定该 id 的 Label,比如voting或者规则等。 - 业务理解的重要性
最后,这道题的关键在于理解业务。多问自己一些问题,代入攻击者的角色,如果自己是黑客,怎样攻击能够达到目的,从而能够发现不同于正常访问的访问行为,提取相应的特征。
三、特征工程
在提供的三周的连接记录中,我线下选择用前两周训练,最后一周线下验证,线上则用全集三周进行训练。概括地来说,我提取的特征包括以 ds 和 hh 两种不同粒度的基本特征和组合特征,以及一项构造的特征。
然后,仔细回想了一下,我这种特征提取方式,提取的总是“当天”的统计信息,而没有像以往的比赛一样,将之前的某段时间作为特征提取区间,这有可能是一个缺漏,也可能是一个trick。说是缺漏的原因在于,我没有用到完整的历史特征,这其中可能包含了对决策有利的信息。说是trick的原因在于,题目Label的特性反映出它更关心当天内(不同时段)用户的行为。不论如何,这个想法已经无法验证了,非常可惜。
基本构成
以特定 id (例如sip:port:ds:hh)关于不同时间粒度的针对连接的统计特征。
- 记录总数
- 连接总次数
- 最大连接次数、平均连接密度(除以记录数、除以hh或mi个数)
- 主机网段种类(IP取AB段)
- 最大、平均网段个数(除以记录数、除以hh或mi)
- 云主机拥有者个数
- 最大、平均云主机拥有者个数(除以记录数、除以hh或mi种数)
这里解释一下,在平均值的计算中,有“除以hh个数”这一项,这是由于用户并不是在每个小时都有记录,应该在有记录的时间内取平均,例如当天只有12、13、14时内有记录,则应该除以3。
特定 Id 构成
根据 id 的物理意义,实际上可进一步细分为用户特征、端口特征、用户端口特征、网段特征、用户网段特征等。这里为了方便描述,分为“直接”与“cip_ab相关”,后者并不是直接统计的,经过了一次预处理。
直接
- sip : port : ds : hh
- sip : port : ds
- sip : ds : hh
- port : ds : hh
- port : ds
cip_ab 相关
- sip : port : ds : hh : cip_ab → → sip : port : ds : hh
- sip : port : ds : cip_ab → → sip : port : ds
- port : ds : hh : cip_ab → → port : ds : hh
- port : ds : cip_ab → → port : ds
这么看可能不容易看懂。
以“直接”中 sip : port : ds : hh 为例,具体做法是将训练集以 sip : port : ds : hh 进行 groupby,然后提取基本构成中的各项指标,旨在提取具有类似于“用户在ds通过port尝试访问的云主机个数”这等层次意义的特征。
以“cip_ab 相关”中第一项为例,先将训练集按 sip : port : ds : hh : cip_ab 进行 groupby,然后提取基本构成中的各项指标,旨在提取具有类似于“sip在ds-hh通过port访问特定网段cip_ab的最大记录数”这样的特征。然后,按 sip : port : ds : hh 进行groupby,对不同网段的统计量进行聚合,再次使统计量的关注点回归到sip : port : ds : hh上来。这样的特征描述了类似“sip在ds-hh通过port平均访问多少个网段”的特征。
构造特征
经过观察,发现我提取的特征里,攻击者的某些特征取值明显比普通用户高得多,为了扩大这个效果,构造了一个特征mul。这个特征最终使得线上成绩从0.73飙升到0.77,排名上升到排行榜第5。
关于聚合时间粒度
解释一下为什么增加了 hh h h 粒度效果会变好。下表为按时间聚合的 sip : port : ds : hh 连接量,其中A只有3个小时有记录,而B有20个小时有记录。
source_ip | port | ds | hh | counts_hh |
---|---|---|---|---|
A | 22 | 20170701 | 10 | 40 |
A | 22 | 20170701 | 11 | 900 |
A | 22 | 20170701 | 12 | 60 |
B | 22 | 20170701 | 0 | 50 |
… | … | … | … | … |
B | 22 | 20170701 | 19 | 50 |
下表为按天聚合的 sip : port : ds 连接量
source_ip | port | ds | counts_ds |
---|---|---|---|
A | 22 | 20170701 | 1000 |
B | 22 | 20170701 | 1000 |
可见按照 ds d s 粒度进行groupby时,A和B的统计量是一样的,是否就应该将他们视作同一label呢?答案是否定的,看按照 hh h h 粒度进行groupby时,A在11时明显在突发地大量发送连接请求,很有可能就是在扫描爆破。按照 ds d s 粒度分辨不出来的用户行为,按照 hh h h 粒度就能分辨出来。
四、模型与规则
最佳提交结果 = 双粒度特征 + 构造强特征 + 模型融合 + 前科者规则
4.1 模型选择
由于本次比赛完全是平台赛,因此只能使用PAI平台提供的工具。我选择了gbdt、rf和PS-SMART,它们都支持二分类任务。根据比赛中的测试,三个模型对于相同的特征,效果是差不多的。
4.2 模型融合
比赛中我尝试了两种模型融合。
两个gbdt,一个采用day粒度特征,另一个采用hour粒度特征,预测结果(概率)简单平均。效果在0.77的基础上有略微提升。
两个PS-SMART+一个RF,为PS-SMART、gbdt和RF各自训练三个模型,分别基于三个不同的特征子集:全特征、ds粒度特征和hh粒度特征,总共9个模型,分别进行模型调参,根据线下准确率,选出分数最高的三个模型:RF_day, PSSMART_all, PSSMART_day,然后将它们融合,融合权值是通过人肉网格搜索决定的。
4.3 前科者规则
对于测试集中的 sip-port ,若在训练集中曾经被标记为攻击者,无论模型对测试样本的预测结果如何,都将被标记是攻击者。事实上这个规则也可以转化为特征:之前被标记为攻击者的次数。
五、最重要的点
总结一下为什么能获得这么高的名次。
特征工程方面
第一,将特征扩充为以小时为单位。原因在于赛题对攻击者的标记是以天为单位的,也就是说也许访问者当天只在某一短时间进行了扫描。若以小时为单位,模型就能学习去判断访问者在当天的哪些时段进行了攻击,最后利用voting综合决策,从而提升准确率。第二,是构造的强特征,赛题提供的信息我并未完全利用, 我当时并没有用到赛题提供的访问者登录成功的信息,我私底下问过一个F1值跟我差不多的队伍,他们表示单模型下提取了登录特征也顶多跟我差不多,可见这个强特征有多重要。对赛题的理解和对数据的观察方面
出于对赛题的理解我改变了时间聚合粒度,并提出了规则,由于观察过数据,我构造出了强特征。更多地去观察数据,即使不能从中发现新的特征,也能增进对整个赛题的理解。思路方面
本次比赛中,我的时常将自己往攻击者靠拢,尝试从他们的角度分析,如果我是攻击者我会怎么做?怎样才能尽可能破解到更多的主机?我会对哪些主机更感兴趣?拥有者多的还是属于特定网段的?此外,我感觉我在比赛中的思路特别清晰,这部分特征提取的意义何在,特征要怎么拼接起来,要怎么给模型训练,模型要怎么融合起来,这些思路在观察数据的时候基本就有成型了。代码方面
写SQL的时候一定要给出明白的注释,有时候超长一段只是为了提取零星的几个特征而已,不加注释的话以后就不知道自己写了什么了。另外,对于SQL不能写循环的特性,可以利用Python进行文本替换生成代码。
六、不足之处
这次比赛我参加得不算早,好像是在中途参加的。在这之前我甚至还不会写SQL,基本上是边学边做。比赛的那段时间刚好被老板要求写论文,因此用来作比赛的时间不多。虽然队伍有两个人(简直不敢相信),分别负责一道题,虽然有所交流,但能获取的只有idea,实现基本还是自己完成的。我认为我的不足之处在于:
完全没有用到用户登录流水信息。不是我故意不用,是我一开始没想到要怎么利用,到最后直接给忘了。但是,我光使用连接信息就达到了接近顶尖的水平,如果用上login应该能更上一层。
没有做数据采样,实际上数据存在样本标签不均衡的问题。具体地,应该对负样本按照时间采样,例如将每小时分成四份,则可以做到25%的采样,进一步划分奇偶小时,可做到12.5%采样。
没有考虑到不同端口的恶意样本比例不同对模型带来的影响,对于恶意用户比例高的端口,其阈值应该调高以减少误判。
没有尝试其他模型。pssmart其实可以输出叶子节点,可以在这基础上做stacking。
没有进一步研究更多的trick。用ODPS SQL观察数据还是比不上画图直观,感觉数据中应该还有某些规律我没有抓到。
创新不足,感觉这类比赛的套路基本能摸清了,以后比赛应该多尝试新东西的。
七、收获
业务理解和思考产生提升
- 基础特征的构造是基于站在攻击者的角度思考产生的。
- 改变样本划分粒度的做法是基于对赛题和数据特点的思考产生的。
数据观察增进理解
- 强特征mul的提出是基于数据观察得到的。
- 在训练完树模型后,根据线下预测状况,发现模型基本上能将全部正例预测为正,但会把相当一部分反例预测为正,换言之,模型过于严格。由于时间关系我没有深入研究,如果还有时间,我应该会设置某些trick或专门提取某些特别能反映反例特色的特征,“纠正”哪些伪正例。
千万不要随便丢弃效果差的特征
单独使用day粒度的效果比不上单独使用hour粒度特征的效果,但两者放在一起,能达到更好的效果,最佳提交结果正是如此。在好特征中混入看似不好的特征,甚至有可能构造出特性相差较大的特征子集,从而训练出“好而不同”的基学习器,用来做融合再好不过了。
八、面试中曾经被问到过的问题
RF和GBDT各自的原理和区别?
RF是bagging,基函数使用不同的样本子集,各自训练时也有feature fraction,从候选特征中选择最佳特征和分裂点,最终平均或voting组合。GBDT是boosting,它由多棵CART树串行叠加,每一轮拟合前一轮模型与真实值的残差,梯度提升版本用梯度代替残差。你有什么创新点?
主要都是特征工程方面的,构造了强特征,提出了规则。你怎么做融合的?为什么要这样融合?
简单加权。因为这是最常用的做法,实际上还有stacking,但由于时间问题没有尝试。你的组合特征和基本特征有什么区别?为什么要特地这样提取?
特征之间的本质区别在于group by的id不一样,表征的物理意义不一样,反映某种特性的层次也不一样。这样提取是因为树模型不能直接学习到这样的交叉特征。你的构造特征有什么依据?你觉得为什么他管用?
依据是对攻击者和普通用户的特征值的观察,攻击者的特征值普遍偏高,不仅最大值高,均值也高。我认为之所以管用,是因为它反映了攻击者在短时间内希望尽可能遍历更多的主机、用户的特性。如你所言,原始特征里基本都是ID类,你怎么能直接扔进树模型?
我并没有把ID类特征直接扔进树模型,事实上我是针对不同的ID提取了统计特征,然后用统计特征代替ID本身。树模型一般不能接受ID类输入的。