前言:看不见的对手
11-28更新:浏览新闻发现 WhatsApp 存在同样的安全问题,导致 35 亿个账户数据泄露。信息来自 SBA 文章:《Researchers discover security vulnerability in WhatsApp》。
在做应用安全防御时,我们经常会遇到一种棘手的情况:对手不再是傻乎乎的脚本小子,而是经过精心伪装的高级爬虫。它们的 IP 是干净的,设备指纹看起来也是正常的,甚至请求频率都控制在人类操作的范围内。面对这种“隐形”的流量,传统的封 IP 或封设备 ID 往往显得力不从心——要么封不住,要么误伤一片。
今天我想分享一个真实的对抗案例,聊聊在常规手段失效后,我是如何利用 信息熵(Information Entropy) 结合 随机森林(Random Forest) 算法,从数据规律的层面揪出这批“内鬼”的。
场景复现:通讯录里的猫腻
先交代一下背景。本次被攻击的接口是一个典型的“手机通讯录上传/匹配”场景。客户端会上传一个包含若干手机号的 phones 数组,后端负责返回这些号码对应的用户信息。
请求报文大概长这样:
POST /api/search_phones HTTP/1.1
Host: www.example.com
{
"phones": [
"13800138001",
"13800138002",
"13800138003",
"13800138004",
"13800138005",
"...",
"13800138010"
]
}
作为安全工程师,当你盯着这段日志看时,直觉会告诉你哪里不对劲。 没错,是“秩序”。
正常用户的通讯录,号码通常是杂乱无章的(你的朋友不可能恰好都连号)。而爬虫为了遍历数据,往往会生成连续、递增或者具备某种数学规律的号码列表。
这就引出了我们今天的核心解题思路:既然坏人的特征是“有规律”,那我们将“规律性”转化为计算机可理解的数值,问题不就解决了吗?
特征提取:用数学量化“混乱”
如何判断一组数字是“有规律”还是“乱序”?简单的做法是统计前缀重复率,比如“如果前7位相同的号码超过50%,就判黑”。但这太容易被绕过了,攻击者稍微打乱一下顺序就能骗过规则。
为了追求更强的鲁棒性,我们需要引入信息论中的经典概念:熵(Entropy)。
📝 知识点:信息熵 源于香农信息论,用于量化系统的“混乱程度”或“不确定性”。
- 熵值越高:系统越混乱,越不可预测(正常用户特征)。
- 熵值越低:系统越有序,越有规律(爬虫特征)。
数学公式如下:
1. 第一层过滤:号码分布信息熵
首先,我们可以计算这组号码分布的熵值。当一个存在规律的号码数组输入到公式中时,其计算出的熵值会明显低于随机号码。
为了验证这个思路,我选取了100个号码作为样本,模拟了不同程度的“前缀聚集”情况,计算结果如下表:
| 相同前缀占比 : 随机占比 | 相同前缀数量 | 随机前缀数量 | 前缀总种类数 | 计算出的熵值 (bit) |
|---|---|---|---|---|
| 100% : 0% | 100 | 0 | 1 | 0.00 (完全有序) |
| 90% : 10% | 90 | 10 | 11 | 0.80 |
| 50% : 50% | 50 | 50 | 51 | 3.82 |
| 10% : 90% | 10 | 90 | 91 | 6.31 |
| 0% : 100% | 0 | 100 | 100 | 6.64 (完全随机) |
结论显而易见: 熵值越低,恶意攻击的嫌疑越大。
2. 进阶特征:差值(步长)信息熵
虽然上面的方法有效,但狡猾的爬虫可能会对生成的连续号码进行随机打乱(Shuffle)。这时候,单纯看号码分布可能就不够准了。
于是我引入了第二个核心特征:号码间差值的信息熵。
逻辑是这样的: 先将数组内的号码进行排序,然后计算相邻号码之间的差值(步长),最后计算这些差值的熵。
这样一来,无论爬虫怎么打乱顺序,只要它生成的号码本质上是基于某种步长(比如 +1, +3)覆盖的,排序后的差值就会呈现出极高的重复性,从而导致差值熵极低。
这不仅能识别连号,还能覆盖各种等差数列变种:
...001, ...002, ...003(步长为1)...001, ...004, ...007(步长为3)1234... , 1235... , 1236...(中间号段递增)
经过实测,号码间差值信息熵在描述号码组的“相似性”上,比单纯的号码分布熵更加敏锐。
3. 构建全维度的特征工程
为了不给对手留死角,我并没有只依赖一个指标,而是组合了一套“特征全家桶”。基于前面的思路,我们可以提取出以下 8 个关键维度:
| 序号 | 指标名称 | 爬虫样本典型值 | 正常样本典型值 | 业务含义 |
|---|---|---|---|---|
| 1 | 前缀相同比例 Top N 和 | 0.79 | 0.05 | 头部最集中的几个前缀占比之和 |
| 2 | 前缀频率最高值 | 0.50 | 0.02 | 出现次数最多的前缀占比 |
| 3 | 前缀熵归一化值 | 0.34 | 0.95 | 核心指标:值越小越可疑 |
| 4 | 差值相等比例最高值 | 0.71 | 0.01 | 排序后,也就是步长最一致的概率 |
| 5 | Top 1 差值数值 | 1 | 3 | 攻击者通常步长很小(如1) |
| 6 | 差值概率 Top N 和 | 0.74 | 0.02 | 步长分布的集中度 |
| 7 | 小步长出现频率 | 0.73 | 0.01 | 比如步长<10的占比,反映遍历意图 |
| 8 | 步长熵归一化值 | 0.32 | 0.99 | 核心指标:差值维度的混乱度 |
算法落地:为什么选择随机森林?
特征提取出来了,最后一步就是分类:是人,还是鬼?
考虑到为了保障用户体验,我们需要极高的准确率(Precision)以避免误杀,同时需要不错的召回率(Recall)。既然我们已经能够通过日志比较容易地标记出“黑样本”(有规律的)和“白样本”(正常的),这显然是一个典型的有监督学习场景。
我选择了 随机森林 (Random Forest) 算法,理由很简单:
- 抗过拟合:它由多棵决策树构成,泛化能力强。
- 解释性强:我们可以知道哪个特征(比如是步长熵还是前缀熵)起了决定性作用。
- 处理非线性:它能很好地处理这些复杂的概率特征。
Python 训练示例
使用 sklearn 库,几行代码即可完成模型的训练:
from sklearn.ensemble import RandomForestClassifier
# 初始化模型
# n_estimators=100: 构建100棵决策树
# max_depth=5: 限制树深,防止过拟合
rf = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
# 开始训练
# X_train: 我们提取的那8个维度的特征矩阵
# y_train: 0(正常) 或 1(爬虫) 的标签
rf.fit(X_train, y_train)
# 验证效果
print("Accuracy on training set: {:.3f}".format(rf.score(X_train, y_train)))
输出结果:
Accuracy on training set: 0.997
模型在训练集上达到了接近 99.7% 的准确率。这说明我们提取的“熵特征”非常强效,能够清晰地将爬虫流量与正常流量在多维空间中切割开来。
总结与思考
这次实战不仅成功遏制了恶意爬虫,更验证了一个观点:在应用安全领域,数学往往比复杂的规则更接近本质。
- 透过现象看本质:爬虫无论如何伪装 IP 和 UA,其“批量获取数据”的贪婪本性决定了其请求必然带有某种“低熵”的规律。
- 多维度打击:从“号码分布”深入到“差值分布”,再结合随机森林算法,我们构建了一个立体的防御模型,极大地提高了攻击者的伪造成本。
安全对抗是一场永无止境的博弈,希望今天的“信息熵”思路能为你提供一些新的灵感。如果你也有类似的反爬经验,欢迎在评论区交流!