在高并发场景下,最让运维和开发人员头疼的,往往不是服务器资源不够,而是你根本分不清涌入的流量到底是等着抢茅台的真用户,还是一波试图把你服务打瘫的CC攻击。这两种流量在表象上极其相似,都是高频率的请求,都会瞬间占满服务器的连接数和带宽。但它们的本质区别在于“意图”和“行为逻辑”。一个是要完成交易闭环,另一个纯粹是为了耗尽你的资源。要区分它们,不能只看请求次数,得从请求的“指纹”特征、业务上下文和流量模式三个维度去深挖。

行为路径的完整性是最大的破绽

正常的抢购用户,无论他手速多快,他的行为一定遵循一个线性的业务逻辑。一个典型的电商抢购流程通常是:浏览商品详情页(读请求) -> 点击加入购物车(写请求/会话保持) -> 进入结算页 -> 提交订单 -> 支付。这个链条是完整的,有状态的。而CC攻击,尤其是应用层CC攻击,为了追求效率,绝大多数是“无头苍蝇”。攻击脚本通常只瞄准一两个高消耗的URL,比如直接高频请求“提交订单”接口或者“验证码生成”接口,根本不会去请求首页的CSS、JS文件,也不会去浏览商品详情页。你可以通过实时分析访问日志,构建用户会话序列。如果一个IP或者一个设备指纹在极短时间内反复请求“下单”接口,却从未加载过商品详情页或静态资源,这基本可以判定是攻击行为。正常的浏览器出于渲染需求,会自动请求页面关联的静态资源,而攻击脚本通常是一个裸奔的HTTP Client,它不会解析DOM,也不会请求图片和样式表。

请求头特征的“诚实”泄露

真实的浏览器和编程语言发出的HTTP请求,在Header细节上有天壤之别。很多低级的CC攻击工具为了追求发包速度,往往忽略了请求头的伪装。你可以重点检查以下几个字段:首先是User-Agent,正常抢购用户的UA虽然千奇百怪,但集中在主流浏览器和手机型号上,而攻击脚本的UA要么是空值,要么是极其古老的浏览器版本,或者是Python-requests、Go-http-client这类直接自报家门的默认UA。其次是Referer字段,正常从商品页跳转到下单页,Referer一定是商品页的URL,而直接攻击下单接口的请求,Referer要么是空,要么是错误的值。最后是Accept-Language、Accept-Encoding等协商头,真实的浏览器会携带丰富且一致的协商头,而脚本往往缺失这些细节。在Nginx或Apache层,直接针对这些畸形请求头做第一轮清洗,能过滤掉60%以上的低水平CC攻击。

Cookie与Session的深度博弈

抢购用户因为需要登录和保持购物车状态,一定会携带合法的Cookie,并且服务端能对应到有效的Session。而CC攻击分为两种,一种是不带Cookie的纯暴力请求,这种最好处理,直接封禁即可。另一种是带Cookie的CC攻击,攻击者会先通过注册机批量生成海量僵尸账号,然后拿着这些账号的合法Cookie来发起攻击。这时候,区分的关键在于Session的深度行为。正常用户的Session虽然活跃,但在抢购倒计时结束后,会有明显的“浏览-点击-支付”的状态流转。僵尸账号的Session往往是“登录-下单-登录-下单”的死循环,且下单失败后没有任何后续的查看订单、咨询客服等自然行为。你可以在服务端埋点,记录Session的生命周期。如果一个Session在1分钟内尝试下单50次且全部失败,随后立即断开,这大概率是脚本。而正常用户在下单失败后,会有犹豫期,会刷新页面看库存,行为曲线是波动的,不是机械的直线。

流量速率与并发连接的物理特征

人的手速是有生理极限的。即便是使用了抢购脚本的真实用户,其脚本为了模拟人类行为,通常也会设置随机的延迟。而CC攻击为了打死服务,追求的是瞬时高并发。从流量模型上看,正常抢购流量的增长是一条在倒计时结束后陡然上升,但斜率依然受限于用户网络延迟的曲线;而CC攻击的流量模型通常是瞬间拉满,呈现出一个近乎90度的直角波峰。此外,关注TCP连接的状态。正常的HTTP短连接在完成请求后会经历四次挥手进入TIME_WAIT状态,而很多CC攻击工具为了压榨性能,会采用不规范的半开连接或者无视close信号,导致服务端出现大量的ESTABLISHED或者CLOSE_WAIT状态的连接。通过netstat或ss命令查看单IP的连接数,如果单个IP对服务器的并发连接数超过50甚至100,而正常用户的浏览器通常只维持6-8个并发连接,这绝对是非人类行为。

鼠标轨迹与前端无感人机验证

后端分析终究有盲区,当攻击者完全模拟了Header和Cookie时,战场就转移到了前端。正常用户在与页面交互时,会产生鼠标移动、点击、滚动、触摸等事件,这些事件的轨迹和时间戳是极难伪造的。CC攻击要么是纯协议发包,完全没有这些前端事件;要么是通过模拟器执行JS,但模拟器产生的轨迹往往是直线、匀速的,缺乏人类手部肌肉震颤带来的微小抖动。你可以在页面上部署轻量级的无感人机验证SDK,它不一定要弹出滑块验证码,而是在后台静默收集用户的环境信息(如屏幕分辨率、GPU型号、字体列表)和交互行为。如果检测到环境信息异常(比如没有GPU,那很可能是无头浏览器),或者完全没有鼠标轨迹却成功触发了高权限接口,直接在后端丢弃该请求或弹出二次验证。这种基于生物行为特征的分析,是目前区分高级CC攻击和真实抢购最有效的手段之一。

基于访问频率的动态阶梯式限流

在搞清楚了特征之后,处置手段不能一刀切,否则极易误杀真实用户。你需要设计一套动态的阶梯式处罚策略。第一层是全网阈值限流,针对“提交订单”这类核心接口,设置一个全局的QPS上限,超过后请求进入排队队列,保证服务不被冲垮。第二层是单IP/设备指纹限流,比如规定单个用户每分钟最多只能提交10次订单请求。但这里有个陷阱,如果是大型小区的NAT出口,几百个真实用户共享一个公网IP,按IP限流会误杀。因此,必须结合设备指纹或用户ID进行限流。第三层是“挑战-响应”机制。当某个请求触发了可疑规则(如缺少Referer、Session行为异常),不要立即封禁,而是返回一个JS挑战页面(如302重定向到一个人机验证页面),或者要求客户端执行一段简单的计算任务(工作量证明)。正常的浏览器会自动处理重定向和执行JS,而攻击脚本如果无法解析JS,攻击链就断了。这套机制可以在不伤害用户体验的前提下,极大地提高攻击成本。

利用AI模型进行流量画像

如果业务量极大,规则引擎的维护会变得非常吃力。这时可以引入轻量级的机器学习模型来做在线流量画像。提取请求的特征向量,包括但不限于:请求间隔的标准差、请求深度的平均值、URL路径的熵值、Header中各字段的存在与否、四层和七层的包长分布等。正常抢购用户的请求间隔是不规则的,呈现泊松分布;而CC攻击工具的请求间隔往往是固定的,或者呈现均匀分布,其时间间隔的方差极小。通过离线训练一个二分类模型(如随机森林或XGBoost),将这些特征输入模型,可以在毫秒级内给出一个风险评分。将评分接入网关,对于高风险的请求直接拒绝,中风险的进入验证环节,低风险的放行。这种基于AI的动态防护,能够自动发现那些人工规则难以描述的、极其隐蔽的慢速CC攻击或分布式CC攻击。

业务逻辑层面的终极防线

最后,一定要在业务逻辑层做兜底。无论流量多么真实,最终都要落到业务上。对于抢购业务,库存扣减是核心。你可以在库存扣减环节加入强一致性的锁,但这会影响性能。更好的做法是,在用户点击“提交订单”时,服务端生成一个不可伪造的令牌(Token),这个令牌需要绑定用户的Session和当前的时间窗口。下单接口不仅要校验库存,还要校验这个令牌的合法性。攻击者即使模拟了所有请求,如果无法拿到这个动态生成且时效性极短的令牌,所有攻击请求在业务层都会被直接丢弃。同时,对于最终成功下单的用户,记录其完整的设备指纹和支付账号。如果发现大量订单集中在某个支付账号段或收货地址区域,即使流量行为再像人,也要进行事后风控拦截。毕竟,攻击的最终目的如果是刷单或锁库存,一定会在业务数据上露出马脚。把防线从单纯的流量层延伸到业务数据层,才能真正做到精准区分,既保住服务器,又不误伤任何一个真实的抢购用户。