加密流量不等于安全流量,DDoS 攻击者同样会盯着 TLS 握手打。本文用 9 个问答讲清 HTTPS 握手攻击的原理、怎么区分「握手耗尽」与「连接耗尽」、Nginx 与证书侧的加固配置,以及用 CDN 终结 TLS 的实战方案。
Q:什么是 HTTPS 握手攻击?它和普通 DDoS 有什么不同?
- TLS 握手是客户端与服务器建立加密连接的协商过程,其中包含非对称加密运算(RSA 私钥运算、DH 密钥交换),这是服务器 CPU 上最昂贵的操作之一。
- 握手攻击就是让服务器把 CPU 全花在握手计算上:大量建立连接但不完成握手,或在单条连接里反复发起完整握手。
- 与流量型 DDoS 最大的不同在于——攻击流量可能很小,带宽曲线几乎看不出异常,但 CPU 被打满、正常用户连握手都完不成。
Q:握手攻击到底在消耗服务器的什么资源?
- CPU:非对称加密运算,单次握手往往比一次普通请求贵几十倍。
- 连接与内存:半截连接长期挂起会占用连接槽与缓冲区。
- 证书链与状态校验:未开启 OCSP Stapling 时,服务器可能要去外部查询证书状态,额外增加延迟与失败点。
- 这就是为什么「加了带宽、换了更大的服务器还是被打挂」——瓶颈从来不是带宽,而是每秒能完成多少次握手。
- 需要与另一类攻击区分:连接耗尽型攻击打的是连接表(TCP 半连接与 conntrack),连接数会异常膨胀;握手攻击耗的是 CPU,连接数可能并不高。
Q:怎么判断网站卡在握手层,而不是后端?
# 1) 单次握手耗时(多次取样看波动)
for i in 1 2 3 4 5; do
curl -o /dev/null -s -w "handshake: %{time_appconnect}s total: %{time_total}s\n" https://www.example.com/
done
# 2) Nginx 访问日志里的 SSL 类错误码,495/496/400 大量出现是典型信号
grep -E ' (400|495|496|497) ' /var/log/nginx/access.log | wc -l
tail -f /var/log/nginx/error.log | grep -i ssl
# 3) 连接状态与 443 并发总览
ss -s
ss -ant state established '( sport = :443 )' | wc -l
- 判据:握手耗时明显拉长、SSL 错误码激增、nginx worker CPU 高但后端应用进程 CPU 不高,基本可以确认压力卡在 TLS 层。
- 反过来,如果握手正常而后端 CPU 高,那是应用层 CC,不属于握手攻击。
Q:Nginx 有哪些配置能降低握手成本、限制握手滥用?
# 开启会话复用,让老客户端不必每次都做完整握手
ssl_session_cache shared:SSL:50m; # 1MB 约存 4000 个会话
ssl_session_timeout 1d;
ssl_session_tickets on;
# 握手超时收敛:不让半截连接长期挂着
ssl_handshake_timeout 10s;
# 优先启用 TLS 1.3(1-RTT、不支持重协商、默认前向保密)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
- 同时在 443 的 server 块里对并发与频率做限制,把单个来源卡住:
limit_conn_zone $binary_remote_addr zone=tlsconn:10m;
limit_req_zone $binary_remote_addr zone=tlsreq:10m rate=20r/s;
server {
listen 443 ssl;
limit_conn tlsconn 30; # 单 IP 最多 30 个并发连接
limit_req zone=tlsreq burst=40 nodelay;
limit_conn_status 429;
}
- 关键参数是 ssl_session_cache 与 ssl_handshake_timeout:前者把重复握手变成复用,后者把半截连接尽早清掉,两者配合能明显压低 CPU 峰值。
- 系统层可适当调大握手队列(
net.core.somaxconn、net.ipv4.tcp_max_syn_backlog),但要清楚内核参数只能缓解排队,解决不了 CPU 被加密运算吃满的问题。
Q:证书类型也会影响握手开销吗?
- 会,而且差异不小。RSA 私钥运算比 ECDSA 重得多,同等硬件下 ECDSA 证书能承载的握手次数通常高出一个数量级。
- 证书链越长,客户端需要校验的中间证书越多,握手数据量与耗时都会增加,尽量把链裁到最短。
- 开启 OCSP Stapling,把证书状态响应缓存到服务器本地,省掉外部查询:
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
resolver 223.5.5.5 8.8.8.8 valid=300s;
resolver_timeout 5s;
# 检查证书类型与链长度
openssl s_client -connect www.example.com:443 -servername www.example.com /dev/null \
| openssl x509 -noout -subject -issuer
Q:重协商(Renegotiation)是什么,怎么关掉?
- 重协商原本用于在已有连接里更换加密参数,但攻击者可以在单条连接里反复发起重协商,用极小的流量换来巨量 CPU 消耗——这就是经典的 SSL 重协商拒绝服务攻击。
- 处理方式很直接:停用不安全重协商,并优先使用 TLS 1.3(该版本本身不支持重协商)。
ssl_protocols TLSv1.2 TLSv1.3;
# OpenSSL 1.1.1+ 已默认禁用不安全重协商,无需额外开关
# 老发行版请确认安全重协商已生效:
openssl s_client -connect www.example.com:443 -servername www.example.com -reconnect /dev/null \
| grep -i reneg
- 在 Nginx 中不要把
ssl_protocols留着 TLSv1.0/TLSv1.1:它们不仅安全性差,握手效率也更低。
Q:TLS 1.3 和会话复用能解决多少问题?
- 能解决相当一部分,但不能全指望它。真实情况是:新客户端(无会话票据)每次都要做完整握手,攻击者正是靠不断伪装成「新客户端」来规避会话复用。
- TLS 1.3 把完整握手从 2-RTT 降到 1-RTT,且默认使用 ECDHE,CPU 成本低于老版本;但它不会让握手变成零成本。
- 关于 0-RTT(
ssl_early_data):默认关闭更稳妥,它存在重放风险,下单、支付、提交这类非幂等请求绝对不要开。 - 结论:会话复用与 TLS 1.3 属于「降本」,不是「免疫」;真正扛住大流量握手攻击,最终还是要靠 CDN 分流。
Q:用 CDN 或高防终结 TLS 是不是最省事的做法?
- 是。让 CDN / 高防节点负责与客户端完成握手,把加密流量解密并清洗后再回源,源站只处理干净的请求,握手压力基本转移出去。
- 配合三件事效果最好:源站只放行 CDN 回源网段、回源启用长连接与会话复用、源站同样开启 TLS 1.3。
# 源站只允许 CDN 回源 IP 访问(网段为示例,请替换为实际回源段)
location / {
allow 203.0.113.0/24;
allow 198.51.100.0/24;
deny all;
proxy_pass http://backend;
}
# 回源启用 keepalive,减少重复握手
upstream backend {
server 127.0.0.1:8080;
keepalive 64;
}
proxy_http_version 1.1;
proxy_set_header Connection "";
- 常见坑:只在前端开了 CDN,源站域名仍可被直接解析访问,攻击者绕过 CDN 直打源站,前面所有防护等于白做。源站要么只放行回源段,要么干脆只监听内网地址。
Q:还有什么补充建议?
- 别把加密当成防护:TLS 保护的是传输内容,不区分流量善恶,握手本身就可以被当成攻击面。
- 监控盯四个量:握手耗时、SSL 错误码数量、443 并发连接数、nginx worker CPU 占比。
- 优先做低成本改造:会话复用、TLS 1.3、ECDSA 证书、OCSP Stapling,四项都属于改配置就能生效的降本手段。
- 大流量攻击交给 CDN:单机再优化也扛不住分布式握手洪水,分层卸载才是正解。
- 一句话总结:握手攻击打的是 CPU 而不是带宽——会话复用把重复握手省掉、TLS 1.3 与 ECDSA 把单次成本压低、CDN 终结 TLS 把压力转移出去,三件事做完,源站才有资格谈「扛住」。