CC攻击和压力测试怎么区分?压测防护指南

网站突然被打慢,第一反应往往是「被 CC攻击 了」,查了半天却发现是自家同事在跑压测。CC攻击和压力测试在流量形态上几乎一模一样,处置方向却完全相反:一个要加防护,一个要放行。本文用 9 个问答讲清怎么快速区分、压测通道怎么配、白名单放行哪些规则、以及怎么用压测结果反推限流阈值。

Q:CC攻击和压力测试为什么这么像?

本质上两者都是「短时间内对同一批接口发起大量请求」,从服务器视角看请求特征几乎没有区别:

  • 相同点:QPS 突增、集中在动态接口、响应时间上升、并发连接数暴涨。
  • 不同点:压测是有计划、有明确来源、有目标容量的;CC 是分布式、来路不明、藏在正常流量里的。

所以区分的核心不是「像不像攻击」,而是能不能追溯到明确的发起方和计划。

Q:第一分钟该看哪几个指标?

  • 来源集中度:压测通常来自少数几台机器、几个 C 段;CC 是大量分散 IP。
  • UA 是否单一:压测工具 UA 固定,CC 会伪装成浏览器且 UA 五花八门。
  • 请求路径:压测只打脚本里写死的接口,一般不拉图片和 CSS、JS。
  • 是否存在压测特征头:部分框架会带 X-Load-Test、固定 Referer,或明显来自 CI 流水线。
  • 时间点:压测多半落在发布窗口或大促前的测试计划内;CC 偏爱凌晨等无人值守时段。
# 来源 IP 是否高度集中
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

# UA 分布是否单一(combined 格式下 UA 是第 6 个引号字段)
awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head -10

# 压测工具常见的 UA 指纹
grep -iE 'apachebench|jmeter|wrk/|k6/|locust|gatling|go-http-client|python-requests' access.log | head

Q:压测流量有哪些典型特征?

  • 并发数固定:JMeter、wrk 这类工具按设定并发持续施压,流量曲线呈平台状,而 CC 常见忽高忽低的锯齿形。
  • 参数可预测:压测脚本里的用户 ID、商品 ID 是写死的,反复出现同一批参数;CC 会不断换参数绕开缓存。
  • 不拉静态资源:工具只请求目标 URL,不会解析 HTML 后去取图片和脚本。
  • 来源网段固定:要么是公司出口 IP,要么是压测机所在的云厂商网段。
  • 可沟通:问一句「今天有人在压测吗」就能确认,这是最快的一条路,别硬扛着分析日志,先问人。

Q:确认真是自家压测后,第一件事做什么?

不是加防护,而是找发起方确认边界:测多久、多少并发、目标是什么、打的是哪个环境。

  • 确认三件事:压测机 IP 段、会不会影响线上真实用户、结束时间。
  • 如果误打到线上环境,优先让它改去预发环境跑,而不是在生产上不停升级防护。
  • 确实必须打线上时,至少要求:限定并发上限、限定时间窗口、安排人盯盘、随时能停。
  • 反向提醒:如果压测流量触发了防护规则被限速,测出来的数据全部作废,等于白跑一场。

Q:怎么在 Nginx 里给压测流量开一条通道?

核心思路是把压测流量的限流 key 单独拆出来,不让它占用真实用户的令牌桶:

# http 块:识别压测来源网段
geo $is_loadtest {
    default        0;
    203.0.113.0/24 1;   # 压测机所在网段
}

# 压测流量共用一个独立桶,普通流量仍按单 IP 计
map $is_loadtest $cc_key {
    1  "loadtest";
    0  $binary_remote_addr;
}

limit_req_zone $cc_key zone=cc:10m rate=20r/s;
limit_req_status 429;

如果压测来源 IP 不固定,改为按专用请求头识别:

map $http_x_load_test_key $is_loadtest {
    default        0;
    "a7f3c1d9e2b4" 1;   # 压测专用密钥,从 CI 环境变量注入
}

两条纪律:密钥不要明文写死在压测脚本里,走环境变量或密钥管理;用完立刻轮换,别让一个长期有效的密钥变成后门。

Q:压测期间要不要干脆关掉限流和防护?

不建议全关,两个理由:第一,关掉测出来的是裸机上限,而真实用户始终跑在限流规则之下,数据没有参考价值;第二,关闭期间如果真有 CC 打进来,站点会没有任何缓冲。

正确做法是放大而不是关闭:

# 给压测单独一个宽松档
limit_req_zone $binary_remote_addr zone=lt:10m rate=2000r/s;

location /api/ {
    limit_req zone=cc burst=40 nodelay;
    if ($is_loadtest) {
        limit_req zone=lt burst=4000 nodelay;
    }
}
  • 注意:同一个 location 内 if 与 limit_req 的生效顺序容易踩坑,生产上更稳的是给压测单独写一个 location 块。
  • 最推荐的做法是给压测一个独立入口,比如 test-api.example.com 指向同一后端,入口上不带公开防护规则,正式域名完全不受影响。

Q:压测会不会把 WAF 拦截数和告警阈值打爆?

会,而且非常常见:压测会触发大量规则命中,告警阈值被瞬间打满,值班同事会以为真的在被攻击。

  • 把压测机 IP 段或压测密钥加入防护白名单,只放行限速类规则,SQL 注入、命令执行这类硬规则不要放——否则压测会顺手把漏洞一起打出来。
  • 在监控上给压测时段打标,事后分析时把这批数据剔除,避免污染容量基线。
  • 提前通知值班同事压测时间窗口,避免半夜被一通电话叫起来。
  • 压测结束当天检查一遍白名单是否已回收,临时放行最容易变成永久放行。

Q:怎么用压测结果反推防护阈值?

  • 先拿到三个数:正常并发下的 p95 响应时间、错误率拐点、资源拐点(CPU、连接数、数据库先到哪个)。
  • 限流阈值不要按压测峰值定,要按「正常峰值乘安全系数」反推:rate 约为单 IP 正常峰值的 2 到 3 倍;burst 至少覆盖手机首屏 20 到 40 个请求的突发;limit_conn 约为单用户并发乘以 NAT 背后人数再乘 2。
  • 压测的意义是知道「拐点在哪」,而不是知道「最大能扛多少」,阈值应该设在拐点之前,而不是压到极限之后。
  • 把压测报告与防护配置一起归档,下次扩容或换机器时可以直接对比,不用重新摸黑。

Q:还有什么补充建议?

  • 建立压测报备流程:任何压测提前在群里报备,写清来源 IP、时间窗口、并发上限,这是成本最低的一条措施。
  • 压测环境与生产隔离:能压预发就不要压线上。
  • 不要拿真实用户当压测对象:压测要构造独立测试数据,别把生产订单表打爆。
  • 区分「像攻击」和「是攻击」:拿不准就先降速观察,用 429 而不是直接封 IP。
  • 留好时间线:压测报备记录、防护变更记录、告警记录三者对齐,是复盘时唯一说得清的凭据。

一句话总结:CC 和压测在流量形态上没有区别,区别在「来源能不能追溯、目标是不是已知」;先问人、再看集中度与 UA,然后给压测一条独立通道、只放大不关闭、白名单只放限速规则,才是既测得准又不误伤的干法。