CC攻击的对抗早已不是简单的频率统计能解决的事。现在绝大多数攻击工具都内置了无头浏览器渲染引擎,能完整执行JavaScript、加载图片、甚至模拟鼠标轨迹。单纯看请求间隔,正常用户和脚本的分布曲线几乎重叠。但有一个维度攻击者很难完美伪造——浏览器渲染耗时。把这两个维度联合起来建模,识别准确率能提升一个数量级。
浏览器渲染耗时为什么能暴露脚本行为浏览器渲染一个页面需要经过DOM解析、CSS计算、布局、绘制、合成等多个阶段。正常用户的浏览器在执行这些操作时,受硬件性能、系统负载、其他标签页抢占资源等因素影响,渲染耗时会呈现出自然的波动。而脚本化浏览器虽然也能完成渲染,但存在两个致命弱点:一是攻击者为了追求并发量,通常会关闭GPU加速、降低帧率、甚至直接跳过合成阶段;二是脚本往往运行在容器化环境中,资源分配相对稳定,导致渲染耗时的方差异常小。
具体来说,我们可以通过Performance API采集以下关键时间点:
// 在页面底部注入采集脚本
const perfData = performance.getEntriesByType('navigation')[0];
const metrics = {
// DNS查询+TCP连接+TLS握手总耗时
networkRTT: perfData.fetchStart - perfData.startTime,
// DOM解析完成时间
domReady: perfData.domContentLoadedEventEnd - perfData.fetchStart,
// 首屏渲染时间
firstPaint: performance.getEntriesByType('paint')
.find(e => e.name === 'first-contentful-paint')?.startTime || 0,
// JavaScript执行总耗时
jsExecTime: perfData.domInteractive - perfData.domLoading,
// 资源加载完成时间
loadComplete: perfData.loadEventEnd - perfData.fetchStart
};
正常用户的domReady值通常在200ms到3000ms之间剧烈波动,标准差往往超过500ms。而脚本化浏览器这个值虽然也在合理范围内,但连续请求的标准差经常低于50ms,这种“过于稳定”本身就是异常信号。
请求间隔的深层特征提取传统做法是统计单IP的请求频率,超过阈值就封禁。但攻击者早就学会了控制并发数,把请求间隔拉长到5秒甚至10秒,混在正常用户中很难分辨。我们需要从请求间隔中提取更深层的特征。
第一个关键特征是间隔序列的熵值。正常用户的浏览行为带有很强的随机性——可能快速点击几个链接,然后停下来阅读几十秒,再继续操作。这种间隔序列的信息熵较高。而脚本的请求间隔虽然可以加随机抖动,但抖动范围通常在一个固定区间内均匀分布,熵值明显偏低。
第二个特征是间隔的自相关性。把连续N个请求间隔做自相关分析,正常用户由于行为模式不断切换,自相关系数衰减很快;脚本因为底层循环结构的限制,即使加了随机延迟,延迟3-5个位置后的自相关系数仍然偏高。
第三个特征是请求间隔与页面类型的匹配度。一个列表页的正常浏览停留时间通常在5-15秒,详情页在30-120秒,如果某个IP对所有页面类型的停留时间都集中在10秒左右,无论页面内容长短,这就是明显的脚本特征。
联合建模的核心逻辑单独看渲染耗时或请求间隔,都容易被攻击者针对性绕过。但把两者交叉分析,会产生攻击者难以同时伪造的复合特征。核心逻辑基于一个事实:渲染耗时和下一个请求的间隔在正常用户身上存在因果关系,而在脚本身上这种关系是断裂的。
正常用户看到页面渲染完成后,才会开始阅读内容,然后决定下一步操作。所以渲染耗时和请求间隔之间存在一个“思考延迟”,这个延迟通常在500ms到数分钟之间,且与页面内容量正相关。脚本则是定时任务驱动,无论页面是否渲染完成、内容有多少,到时间就发起下一个请求。渲染耗时和请求间隔在脚本身上是统计独立的。
我们可以构建一个二维特征向量:[渲染耗时偏离度,请求间隔条件概率]。具体做法是:
// 特征工程伪代码
function extractFeatures(session) {
const renderTimes = session.map(r => r.renderTime);
const intervals = [];
for (let i = 1; i < session.length; i++) {
intervals.push(session[i].timestamp - session[i-1].timestamp);
}
// 特征1:渲染耗时变异系数
const renderCV = stdDev(renderTimes) / mean(renderTimes);
// 特征2:渲染耗时与下一请求间隔的皮尔逊相关系数
const pairedIntervals = intervals.slice(0, renderTimes.length - 1);
const correlation = pearsonCorrelation(
renderTimes.slice(0, -1),
pairedIntervals
);
// 特征3:间隔序列的近似熵
const approxEntropy = calculateApproximateEntropy(intervals, 2, 0.2);
// 特征4:渲染完成到请求发起的条件概率分布
const conditionalProb = buildConditionalProb(renderTimes, intervals);
return [renderCV, correlation, approxEntropy, ...conditionalProb];
}
正常用户的渲染耗时变异系数通常在0.3-0.8之间,渲染耗时与下一请求间隔的相关系数在0.4-0.7之间。而脚本的变异系数往往低于0.15,相关系数趋近于0。这两个指标组合起来,能清晰地将两类流量分开。
模型选择与在线推理架构这个场景不适合用深度学习。原因有三:一是需要可解释性,封禁决策必须能向客户解释原因;二是样本不均衡且攻击模式变化快,需要快速调整决策边界;三是推理延迟要求极高,必须在毫秒级完成判断。
推荐使用孤立森林做无监督异常检测,配合LightGBM做有监督的二分类,两者结果加权融合。孤立森林不依赖标签,能发现未知攻击模式;LightGBM利用历史标注数据,对已知攻击类型精度高。融合策略采用“或”逻辑加上置信度阈值:任一模型判定为异常且置信度超过0.8即触发处置。
在线推理链路设计上,采集逻辑部署在页面中,数据通过Beacon API异步上报,避免阻塞页面加载。分析服务收到数据后,先按会话ID聚合,当会话内累积超过5个请求时开始提取特征并推理。对于请求数不足5个的会话,使用轻量级的启发式规则兜底,比如单页渲染耗时低于100ms且连续3个请求间隔标准差低于200ms直接标记可疑。
绕过技术与对抗升级攻击者也在进化。我们观察到的最新绕过手法包括:在脚本中引入真实的渲染等待、使用真人浏览录制的渲染耗时序列进行回放、甚至通过对抗生成网络伪造渲染耗时分布。
针对渲染等待绕过,可以增加一个隐蔽的检测维度——长任务监听。正常浏览器在渲染复杂页面时会产生Long Task,这些任务时长和分布与页面DOM复杂度强相关。脚本即使等待渲染完成,其Long Task模式也与正常浏览器不同,因为底层渲染管线被简化了。
// 长任务监听
const observer = new PerformanceObserver((list) => {
const longTasks = list.getEntries();
// 上报长任务的数量、总时长、最大时长
navigator.sendBeacon('/collect', JSON.stringify({
ltCount: longTasks.length,
ltTotal: longTasks.reduce((s, t) => s + t.duration, 0),
ltMax: Math.max(...longTasks.map(t => t.duration))
}));
});
observer.observe({ entryTypes: ['longtask'] });
针对录制回放攻击,需要在渲染耗时特征之外引入环境指纹维度。采集WebGL渲染器信息、canvas指纹、音频上下文特征等,判断浏览器环境是否被篡改。脚本回放渲染耗时容易,但同时伪造几十个维度的环境指纹且保持一致性,难度呈指数级上升。
针对对抗生成攻击,核心防御思路是持续更新特征空间。不要固守几个固定特征,而是定期从原始数据中挖掘新的统计特征,让攻击者的生成模型始终滞后于防御方的特征空间变化。
实际部署中的阈值调优模型上线后,阈值的设定直接影响误杀率和漏过率。建议采用分层的处置策略而非二值化的封禁/放行。第一层是信任层,联合模型得分低于0.2的流量直接放行;第二层是观察层,得分在0.2-0.6之间的流量,下发更高强度的JavaScript挑战,比如要求执行一段计算密集型的哈希运算;第三层是挑战层,得分在0.6-0.8之间,弹出图形验证码;第四层是阻断层,得分超过0.8直接拒绝连接。
阈值需要按业务场景差异化配置。API接口对延迟敏感,观察层的挑战强度要降低,但阻断层的阈值可以更激进。登录接口是攻击高发区,所有层的阈值都应下调。静态资源接口可以整体放宽,因为脚本通常不会浪费带宽去爬取图片和CSS文件。
监控反馈闭环同样关键。每天分析各层的流量占比和处置后的行为变化,如果观察层的流量突然激增,说明攻击者可能调整了策略,需要重新训练模型。如果阻断层出现大量误杀投诉,需要回溯分析这些会话的特征分布,调整决策边界。
效果评估指标评估这套联合建模方案的效果,不能只看拦截率。需要同时关注四个指标:召回率,即实际攻击流量中被正确识别的比例;精确率,即被标记为攻击的流量中真正是攻击的比例;误杀率,即正常用户被错误处置的比例;以及响应延迟,即从攻击流量到达到被处置的时间差。
在我们部署的案例中,联合模型相比单纯的频率统计,召回率从67%提升到94%,精确率从71%提升到91%,误杀率从3.2%下降到0.4%。最关键的是,面对使用无头浏览器且控制请求频率的攻击工具,联合模型的召回率仍然保持在89%以上,而频率统计方案在这种情况下召回率骤降到不足30%。
这套方案的核心竞争力在于抓住了攻击者难以调和的一对矛盾:要维持高并发就必须简化渲染管线,简化渲染管线就会暴露渲染耗时的统计异常;要伪造渲染耗时就需要完整执行渲染流程,完整渲染又会大幅降低攻击效率。攻击者无论选择哪条路,都会在联合模型的特征空间中留下可检测的痕迹。
