实战攻防中SNI技术应用

SNI 是 TLS 协议里一个很基础的机制,但在攻防里它的意义很特殊------因为它是明文的

一、SNI 是什么

SNI(Server Name Indication,服务器名称指示) 是 TLS 握手的扩展字段。客户端在建立 HTTPS 连接时,会在 ClientHello明文 告诉服务器「我要访问哪个域名」,这样一台服务器(同一个 IP)才能返回对应的证书,实现一个 IP 托管多个 HTTPS 网站

关键点:SNI 在加密建立之前发送,是明文的(除非用 ECH)。这正是它在攻防中重要的原因。

复制代码
ClientHello
  └── SNI: www.example.com   ← 明文可见

二、攻击方视角的应用场景

场景 说明
流量识别与阻断 中间设备/防火墙读取 SNI 明文,判断目标域名并封禁。这是很多企业网关、运营商做域名过滤的常用手段
资产测绘 用 SNI 反查:给一个 IP,发不同 SNI,观察返回的证书。可以发现同一 IP 上隐藏的其他站点,用于资产梳理/攻击面发现
绕过 IP 封禁 目标封了 IP 但没做 SNI 校验时,用域名+SNI 指向被封 IP 仍可访问
域名前置(Domain Fronting) 用合法大站域名做 SNI,实际流量转发到目标,隐藏真实通信目标,常用于绕过审查/C2 隐蔽通信
CDN 回源与真实 IP 探测 结合 SNI 与证书信息,判断站点是否挂在 CDN 后面,寻找回源 IP
证书信息搜集 SNI 对应的证书里常含域名、组织名,用于情报收集

三、防守方视角的应用场景

场景 说明
流量审计/出入站控制 基于 SNI 做域名白名单/黑名单,管住内网出站访问
恶意 TLS 流量检测 检测异常 SNI(如 SNI 与证书不匹配、域名为 DGA 生成、SNI 为空或乱码)
CDN/负载均衡 依据 SNI 分流到不同后端(这是正常业务用法)
威胁情报匹配 把 SNI 与情报库比对,识别 C2、钓鱼域名

四、值得注意的技术演进

技术 作用 对攻防的影响
ESNI(已废弃) 早期尝试加密 SNI 被 ECH 取代
ECH(Encrypted Client Hello) 加密整个 ClientHello,包括 SNI 让基于 SNI 的识别/阻断失效,是防守方检测能力的新挑战
SNI 为空/伪装 客户端不发或发假 SNI 常用于绕过 SNI 过滤,但会与证书不匹配,容易被识别

一句话总结

SNI 是 TLS 握手里明文的「目标域名标签」

  • 攻击方用它做资产测绘、流量混淆、绕封禁(域名前置最典型);
  • 防守方用它做出站管控、流量审计、恶意域名识别;
  • ECH 的出现正在让「靠 SNI 看流量」这套打法逐渐失效。

检测异常 SNI 有什么特征,如何在zeek、wirshark或者SOC 态势产品上发现或者验证而已SNI流量

这是个偏防守侧的好问题。我按「异常特征 → 各平台落地方法 → 验证思路」三层来讲,尽量给可操作的规则而不是泛泛而谈。

一、异常 SNI 的特征清单

1. 协议层异常(最容易落地检测)

特征 说明
SNI 与证书 CN/SAN 不匹配 最经典的异常信号。请求 a.com 但服务器返回 b.com 的证书
SNI 缺失(空) 现代浏览器几乎必发 SNI;空 SNI 常见于恶意工具、扫描器、老旧/定制客户端
SNI 是 IP 字面量 正常客户端很少把 IP 当 SNI
SNI 与 HTTP Host 不一致 同一连接里 TLS 层和 HTTP 层目标不一致 → 疑似隧道/代理
SNI 后跟不同的 SNI(同连接) 罕见,可能异常协商

2. 内容层异常

特征 说明
域名非标准格式 纯数字、随机字符串、超长域名、多级随机子域(DGA 特征)
使用免费/可疑 TLD .tk .ml .ga .cf .xyz .top 等高频出现在恶意外联
域名刚注册 结合 whois/情报:注册时间 < 30 天
命中威胁情报 SNI 落在 C2、钓鱼、矿池等情报库中
高频轮换 同一源 IP 短时间内访问大量不同 SNI(扫描/DGA 行为)
时序/长度异常 心跳式周期连接、固定大小包(C2 beacon 特征)

3. 行为层异常

  • 单主机 SNI 数量异常多
  • 非工作时间的固定周期外联;
  • 内网主机访问与业务无关的外部域名;
  • 同一 SNI 分辨率下的证书频繁变化

二、Zeek 上怎么发现

Zeek 自带 ssl.log,里面有 server_name(SNI)、subjectissuervalidation_status 等字段,非常适合做检测。

关键字段

  • server_name --- SNI
  • subject / issuer --- 证书主体/签发者
  • validation_status --- 含 certificate-unknown-authorityself signed certificate

实用检测思路

zeek 复制代码
# 1. 无 SNI 的 TLS 连接(可疑客户端)
event ssl_client_hello(c: connection, version: count, record_version: count,
                        possible_ts: time, client_random: string,
                        session_id: string, ciphers: index_vec,
                        comp_methods: index_vec) {
    if ( ! c$ssl?$server_name || c$ssl$server_name == "" )
        print fmt("[No-SNI] %s -> %s", c$id$orig_h, c$id$resp_h);
}

更实用的做法是用 Zeek 输出日志 + 集中分析

  • ssl.log 喂给 SIEM,做字段关联与情报匹配;
  • conn.log 关联 ssl.log,看「同 SNI 的证书是否稳定」。

验证方式zeek -r capture.pcap 重放流量,直接看 ssl.log 里的 server_name / validation_status


三、Wireshark 上怎么发现

显示过滤器(快速定位)

复制代码
# 只看含 SNI 的 ClientHello
tls.handshake.extensions_server_name

# 按具体 SNI 过滤
tls.handshake.extensions_server_name == "example.com"

# 无 SNI 的 ClientHello
tls.handshake.type == 1 && !tls.handshake.extensions_server_name

# 找证书里的 CN(配合 SNI 对比)
tls.handshake.type == 11

排查手法

  1. tls.handshake.type == 1 抓 ClientHello → 展开 Extension: server_name 看明文 SNI;
  2. 再找同连接的 Certificate 消息 → 看 CN/SAN;
  3. 两者不一致,即为强异常信号。

小技巧:Statistics → Conversations 看 TLS 会话,再逐条跟到 SNI。


四、SOC / 态势感知怎么落地

能力 实现方式
日志接入 采集 Zeek ssl.log、防火墙/代理日志、DNS 日志
关联分析 SNI ↔ DNS 查询 ↔ 证书 ↔ 源主机 多源关联(能大幅降误报)
情报匹配 SNI 与威胁情报(C2/钓鱼/矿池域名)实时比对
基线建模 建立「主机---SNI」正常基线,检测从未出现过的新 SNI异常时段外联
规则告警 无 SNI、SNI/证书不匹配、DGA 域名、可疑 TLD、高频轮换
可视化 SNI 频次 TOP、新出现域名趋势、主机外联拓扑

推荐的降误报组合(单看 SNI 误报很高,要叠加):

复制代码
异常SNI  AND  (新域名 OR 命中情报 OR 无SNI OR 证书不匹配)  AND  内网主机发起

五、验证用的实验方法(授权前提下)

要验证检测规则是否有效,可以在自己环境里做:

  1. 构造异常流量
    • curl --resolve example.com:443:1.2.3.4 https://example.com/ 验证「SNI 与证书不匹配」;
    • openssl s_client -servername <SNI> -connect <ip>:443 指定任意 SNI,观察返回证书;
    • curl --no-alpn 或自定义客户端构造空 SNI
  2. 抓包验证:Wireshark 确认 SNI 字段值,标记 pcap 作为测试样本。
  3. 回放检测zeek -r test.pcap,确认 ssl.log 抓出预期字段;再喂 SIEM 看规则是否触发。
  4. 误报评估:用正常业务流量做基线,测量误报率后再调阈值。

⚠️ 边界提醒:以上验证方法只应在你自有或明确授权的环境中进行。「指定任意 SNI 探测某 IP 返回证书」这类操作若指向未授权目标,属于越界探测。


六、必须知道的现实局限

ECH(Encrypted Client Hello)正在让 SNI 检测逐渐失效------启用 ECH 后 SNI 被加密,网关只能看到外层 SNI(通常是 CDN 域名)。

应对方向:

  • SNI 检测 转向证书/指纹/行为检测(JA3/JA4 指纹、连接时序、流量大小分布);
  • 结合 DNS 层(DoH/DoT 会让 DNS 也难见,需终端侧遥测);
  • 加强**端点侧(EDR)**的可见性,弥补网络层盲区。

一句话总结

异常 SNI 的核心信号 是「无 SNI、SNI 与证书不匹配、域名字符串可疑、行为高频轮换」;Zeek 看 ssl.log、Wireshark 用 tls.handshake.extensions_server_name 过滤器 即可快速发现,SOC 上要叠加情报与基线才能把误报压下来 ;而 ECH 正让这条检测路径失效,需要转向指纹与端点侧遥测。

异常 SNI 检测:Zeek 脚本 + Wireshark 过滤器清单

用途:在自有或已授权 环境中,检测 TLS 握手阶段的异常 SNI 行为

适用:Zeek 5.x / 6.x;Wireshark 3.6+

⚠️ 仅限自有系统或明确授权的安全测试场景使用


一、Zeek 脚本

1.1 部署方式

bash 复制代码
# 放到 Zeek 本地站点目录
sudo cp snicheck.zeek /usr/local/zeek/share/zeek/site/

local.zeek 中加载:

zeek 复制代码
# /usr/local/zeek/share/zeek/site/local.zeek
@load ./snicheck

# 务必定义本地网段,否则脚本不检测内网流量
redef Site::local_nets += { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 };

或命令行直接跑:

bash 复制代码
zeek -r capture.pcap site/snicheck.zeek Site::local_nets=10.0.0.0/8

1.2 主脚本 snicheck.zeek

zeek 复制代码
##! 异常 SNI 检测脚本
##! 检测:无 SNI、可疑 TLD、DGA 特征、SNI/证书不匹配、高频轮换
##! 依赖:本机所抓 TLS 流量 + Site::local_nets 定义

module SNICheck;

export {
    redef enum Notice::Type += {
        No_SNI,
        SNI_Cert_Mismatch,
        Suspicious_TLD,
        DGA_Like_SNI,
        SNI_High_Frequency,
    };

    ## ---- 可调参数 ----
    # 可疑/高频滥用 TLD
    const suspicious_tlds: set[string] = {
        "tk", "ml", "ga", "cf", "gq", "xyz", "top", "buzz",
        "click", "work", "loan", "review", "country", "stream"
    } &redef;

    # 同一源主机在窗口内访问的不同 SNI 数阈值(扫描/轮换告警)
    const sni_freq_threshold: count = 50 &redef;
    const sni_freq_window: interval = 5min &redef;

    # 无 SNI 白名单(内部服务、已知合法客户端)
    const no_sni_whitelist: set[subnet] = {} &redef;
}

# ---------- 辅助:DGA 特征判断 ----------
function dga_like(name: string): bool
    {
    local parts = split_string(name, /\./);
    if ( |parts| < 2 )
        return F;

    local label = parts[|parts| - 2];   # 二级域名标签

    # 条件1:标签过长
    if ( |label| >= 15 )
        return T;

    # 条件2:数字占比 >= 40%
    local digits = 0;
    local letters = 0;
    local i = 0;
    local ch = "";
    for ( i in label )
        {
        ch = label[i];
        if ( ch in "0123456789" )
            digits += 1;
        else if ( ch in "abcdefghijklmnopqrstuvwxyz" )
            letters += 1;
        }

    if ( digits + letters == 0 )
        return F;
    if ( digits * 100 / (digits + letters) >= 40 )
        return T;

    # 条件3:连续 5 个及以上辅音(随机串特征)
    local cons = 0;
    local vowels = "aeiou";
    for ( i in label )
        {
        ch = label[i];
        if ( ch !in vowels && ch !in "0123456789" )
            {
            cons += 1;
            if ( cons >= 5 )
                return T;
            }
        else
            cons = 0;
        }

    return F;
    }

# ---------- 状态表:统计每源主机的 SNI 频次 ----------
global sni_tracker: table[addr] of set[string] &create_expire=sni_freq_window;

# ---------- 检测 1:无 SNI ----------
# 说明:ssl_client_hello 的签名在 Zeek 各版本间略有差异,
#      这里使用最通用的 (c, version, record_version) 前三个参数写法。
event ssl_client_hello(c: connection, version: count, record_version: count)
    {
    local orig = c$id$orig_h;
    local resp = c$id$resp_h;

    # 仅关注内网发起的连接
    if ( orig !in Site::local_nets )
        return;

    if ( orig in no_sni_whitelist )
        return;

    if ( ! c$ssl?$server_name || c$ssl$server_name == "" )
        {
        NOTICE([$note=No_SNI,
                $conn=c,
                $msg=fmt("TLS connection without SNI: %s -> %s:%s", orig, resp, c$id$resp_p),
                $identifier=cat(orig, resp)]);
        }
    }

# ---------- 检测 2/3/4:可疑 TLD、DGA、SNI/证书不匹配 ----------
event ssl_established(c: connection)
    {
    if ( ! c$ssl?$server_name || c$ssl$server_name == "" )
        return;

    local sni = c$ssl$server_name;
    local sni_lower = to_lower(sni);

    # 2a. 可疑 TLD
    local s_parts = split_string(sni_lower, /\./);
    local tld = "";
    if ( |s_parts| >= 2 )
        {
        tld = s_parts[|s_parts| - 1];
        if ( tld in suspicious_tlds )
            NOTICE([$note=Suspicious_TLD,
                    $conn=c,
                    $msg=fmt("Suspicious TLD: SNI=%s", sni),
                    $identifier=cat(c$id$orig_h, sni)]);
        }

    # 2b. DGA 特征
    if ( dga_like(sni_lower) )
        NOTICE([$note=DGA_Like_SNI,
                $conn=c,
                $msg=fmt("DGA-like SNI: %s", sni),
                $identifier=cat(c$id$orig_h, sni)]);

    # 2c. SNI 与证书主体不匹配
    if ( c$ssl?$certificate && c$ssl$certificate$subject != "" &&
         sni_lower !in to_lower(c$ssl$certificate$subject) )
        {
        NOTICE([$note=SNI_Cert_Mismatch,
                $conn=c,
                $msg=fmt("SNI/cert mismatch: SNI=%s cert=%s",
                         sni, c$ssl$certificate$subject),
                $identifier=cat(c$id$orig_h, sni)]);
        }
    }

# ---------- 检测 5:高频 SNI(扫描 / 轮换) ----------
event ssl_established(c: connection)
    {
    if ( ! c$ssl?$server_name || c$ssl$server_name == "" )
        return;

    local orig = c$id$orig_h;
    if ( orig !in Site::local_nets )
        return;

    if ( orig !in sni_tracker )
        sni_tracker[orig] = set();

    add sni_tracker[orig][c$ssl$server_name];

    if ( |sni_tracker[orig]| == sni_freq_threshold )
        {
        NOTICE([$note=SNI_High_Frequency,
                $conn=c,
                $msg=fmt("Source %s hit %d distinct SNIs within %s (possible scan/rotation)",
                         orig, sni_freq_threshold, sni_freq_window),
                $identifier=cat(orig)]);
        }
    }

1.3 部署后验证

bash 复制代码
# 1) 语法检查(-a 表示仅解析不执行)
zeek -a site/snicheck.zeek

# 2) 回放 pcap(记得指定本地网段)
zeek -r test.pcap site/snicheck.zeek Site::local_nets=10.0.0.0/8

# 3) 查看告警
cat notice.log | zeek-cut note msg id.orig_h id.resp_h

# 4) 查看 SNI 明细(ssl.log 自带)
cat ssl.log | zeek-cut id.orig_h id.resp_h server_name validation_status

1.4 脚本要点说明(部署前必读)

要点 说明
必须定义 Site::local_nets 否则 orig !in Site::local_nets 恒真,脚本不会报任何告警
ssl_client_hello 签名 各 Zeek 版本参数不同,脚本只取前三个参数(Zeek 支持按前缀匹配 event 签名)。若你的版本编译报参数错误,改为 event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) 补齐
c$ssl?$certificate 证书字段并非所有连接都有,必须先判断存在性再访问,脚本已处理
&create_expire 5 分钟窗口自动清理,避免状态表无限增长
两个 ssl_established Zeek 允许多个同名 event handler,会依次执行;如需合并可自行整理为一个

二、Wireshark 过滤器清单

2.1 基础定位(含 SNI 的握手)

复制代码
# 只显示含 SNI 的 ClientHello
tls.handshake.type == 1 && tls.handshake.extensions_server_name

# 所有 ClientHello(含无 SNI 的)
tls.handshake.type == 1

# 按具体 SNI 过滤
tls.handshake.extensions_server_name == "example.com"
tls.handshake.extensions_server_name contains "example"

# 无 SNI 的 ClientHello(可疑客户端/扫描器)
tls.handshake.type == 1 && !tls.handshake.extensions_server_name

2.2 证书与 SNI 对比排查

复制代码
# 显示证书消息(看 CN/SAN)
tls.handshake.type == 11

# 证书里包含某个域名(与 SNI 对比)
tls.handshake.certificate contains "example.com"

# 只看 ServerHello + Certificate(快速看服务端返回)
tls.handshake.type == 2 || tls.handshake.type == 11

2.3 按主机 / IP 过滤

复制代码
# 某 IP 的所有 TLS
ip.addr == 10.0.0.5 && tls

# 某内网主机发起的 ClientHello
ip.src == 10.0.0.5 && tls.handshake.type == 1

# 排除某个已知合法的 SNI
tls.handshake.type == 1 && tls.handshake.extensions_server_name != "internal.corp.com"

2.4 结合时间 / 流

复制代码
# 指定时间段的 ClientHello
frame.time >= "2026-09-10 09:00:00" && frame.time <= "2026-09-10 18:00:00" && tls.handshake.type == 1

# 按 TCP 流排查
tcp.stream == 12 && tls

2.5 快捷排查动作

目的 操作
看 SNI 明文 抓 ClientHello → 展开 Extension: server_name
看证书 CN/SAN 同连接 Certificate 消息 → 展开 signedCertificate
SNI 与证书比对 两条消息对照,域名不一致即异常
找无 SNI 连接 用 2.1 最后一条过滤器
批量看会话 Statistics → Conversations → TCP

2.6 导出可疑样本供 Zeek 回放

复制代码
# 只保留无 SNI 的流,导出为 no_sni.pcap
tls.handshake.type == 1 && !tls.handshake.extensions_server_name
# File → Export Specified Packets

三、验证流程(仅限授权环境)

复制代码
① 构造测试流量
   - 无 SNI:      用可关闭 SNI 的客户端/自写 TLS 客户端发起连接
   - SNI 不匹配:  curl --resolve example.com:443:<目标IP> https://example.com/
   - 自定义 SNI:  openssl s_client -servername <任意SNI> -connect <ip>:443

② 抓包确认
   - Wireshark 用 2.1 / 2.2 过滤器确认字段值

③ 检测回放
   - zeek -r test.pcap site/snicheck.zeek Site::local_nets=10.0.0.0/8
   - 检查 notice.log 是否产生对应告警

④ 误报评估
   - 用正常业务 pcap 回放,统计误报
   - 调整 sni_freq_threshold、suspicious_tlds、no_sni_whitelist 后复测

四、参数调优建议

参数 默认 调整建议
sni_freq_threshold 50 / 5min 办公网调高至 100+;服务器区可调低
suspicious_tlds 常见滥用 TLD 按贵司业务裁剪,避免误伤
no_sni_whitelist 加入内网服务网段、已知无 SNI 的合法客户端
SNI/证书不匹配 全量告警 CDN 场景误报高,建议加白名单域

五、局限与演进(务必知悉)

  1. ECH(Encrypted Client Hello) 生效后 SNI 被加密,本套检测基本失效------需转向 JA3/JA4 指纹、连接时序、流量分布、端点遥测(EDR)
  2. DoH/DoT 会让 DNS 侧关联变难,需终端侧数据补充。
  3. CDN 场景下 SNI 与证书不匹配属正常(证书由 CDN 签发),需白名单降误报。
  4. 单看 SNI 误报较高,务必叠加情报、基线、行为多维关联再告警。
  5. 本脚本未做 pcap 实测验证 (编写环境无 Zeek 运行时),首次部署请在测试环境用 zeek -a 校验语法并回放样本确认。

相关推荐
行者-全栈开发4 个月前
【前端安全】CVE-2026-44578:Next.js SSRF 漏洞深度解析与修复实战指南
websocket·云原生·next.js·安全防护·vercel·cve-2026-44578·中间件绕过
一直会游泳的小猫4 个月前
gstack-guide
开源·安全防护·ai辅助开发·技能工具集·sprint流程
锐速网络4 个月前
锐速安全:跨境业务加速与安全防护一体化解决方案
waf·网络加速·安全防护·ddos防护·高防cdn·跨境业务·出海游戏
白山云北诗6 个月前
互联网常见网络攻击如何防护
网络·网络安全·ddos·waf·cc·安全防护
梵得儿SHI7 个月前
(第九篇)Spring AI 核心技术攻坚:安全防护 企业级 AI 应用的风控体系之全链路防护(API 安全到内容合规)
java·人工智能·安全·spring·安全防护·springai·企业级ai
微爱帮监所写信寄信8 个月前
微爱帮监狱写信寄信小程序针对互联网黑灰产攻击防护体系
小程序·负载均衡·信息与通信·安全架构·安全防护·监狱寄信
TracyCoder1239 个月前
DDoS/CC攻击防护常见措施
ddos·安全防护·cc攻击
xian_wwq9 个月前
【学习笔记】网络安全死于平庸
笔记·学习·安全防护
网络安全-海哥1 年前
Web安全深度实战:从漏洞原理到防护方案
sql·web安全·网络安全·xss·csrf·漏洞挖掘·安全防护