核心是验证clienthello阶段是否立即拒绝非法sni:需在http块顶层配ssl_reject_handshake on;所有https server显式列合法域名、禁用通配符和default_server;用openssl模拟fake.test.com或ip直连,成功时输出unrecognized_name且无certificate段。

要排查 Nginx 在 TLS 握手阶段对 SNI 域名伪造攻击的防护是否生效,核心是验证它能否在 ClientHello 一发出就拒绝非法 SNI,而不是等 HTTP 层才响应。这不是看网页打不打得开,而是看协议层有没有真正“卡住”。
确认 ssl_reject_handshake 是否正确启用
该指令必须写在 http 块最顶层,不能放在某个 server 内、不能用 if 包裹、也不能写在 stream 上下文里:
- 检查 nginx.conf 中 http { } 区域开头是否有 ssl_reject_handshake on;
- 确保所有 listen 443 ssl 的 server 块都显式声明了合法域名,例如:server_name example.com api.example.com;
- 删掉任何带证书的 default_server,也禁用 *.example.com 这类通配符——它们会让伪造域名顺利通过握手
用 OpenSSL 模拟伪造 SNI 验证拦截效果
真实攻击者常用随机域名或 IP 直连试探,你得用同样方式测:
- 执行:openssl s_client -connect your.ip:443 -servername fake.test.com -tls1_2
- 成功防护时,输出中应出现 SSL routines::UNRECOGNIZED_NAME,且没有 Certificate: 段、不显示 Server Hello
- 再试 IP 直连:openssl s_client -connect your.ip:443 -tls1_2(不带 -servername),同样应触发 UNRECOGNIZED_NAME
- 最后用合法域名测试,确认业务不受影响:openssl s_client -connect your.ip:443 -servername example.com 应正常返回证书
检查日志是否捕获可疑 SNI 行为
即使握手被拒,Nginx 仍能记录客户端声明的 SNI 字段,这是识别扫描器的关键线索:
- 在 http 块中定义含 SNI 的日志格式:log_format sni_log '$remote_addr "$ssl_server_name" $ssl_protocol/$ssl_cipher';
- 配合 access_log 使用,并定期筛查:grep -E "(^$|^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$|\.local|\.test|\.dev)" /var/log/nginx/access.log
- 高频出现空值、IP 地址或非常规后缀,基本就是扫描器指纹
补充基础加固防绕过
单靠 ssl_reject_handshake 不够全面,还需几项轻量但关键的配套措施:
- 对 HTTP 80 端口也做兜底拦截:server { listen 80 default_server; return 444; },避免暴露服务信息
- 限制 TLS 握手频率:limit_req_zone $binary_remote_addr zone=handshake:10m rate=1r/s;,并在 server 块中应用
- 设置握手超时:ssl_handshake_timeout 3s;(需 Nginx ≥ 1.19),防 Slow-SSL 类攻击
- 禁用老旧协议和弱密钥套件,缩小协商攻击面:ssl_protocols TLSv1.2 TLSv1.3;,ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:...;











