摘要
- TLS
server_name是客户端在 ClientHello 中主动提供的 DNS 名称,服务器可据此缩小证书、TLS 上下文或透传目的地的候选范围;它不是客户端凭证。 - 服务端身份校验、HTTP
Host/:authority、代理上游 SNI、ECH 内外层处理以及应用授权各有独立证据。字符串相同,不等于权威相同。
一个完全正确的选择如何造成错误授权
事故记录里的每一步都“成功”了:解析器读出 SNI;回调找到租户 A;证书指纹与租户配置一致;TLS 1.3 握手完成。真正缺失的事实只有一个——对端是谁。
SNI 的目的,是让共享一个 IP 地址的多台虚拟服务在发送证书之前知道客户端想接触哪个名称。任何能构造 ClientHello 的程序都能填入一个语法合法的名称。服务器选中与之对应的证书,只是在准备让客户端认证服务器;它没有反过来证明客户端拥有该域名,更没有证明客户端属于某个租户。
这是一种典型的权威跃迁。底层信号只负责候选选择,上层却把“被选中”改名为“已认证”,再把它交给权限系统。配置表越准确,错误反而越像真。
server_name 的能力很窄
RFC 6066 规定,客户端在 ClientHello 的 ServerNameList 中发送名称。目前定义的 host_name 是 ASCII 形式的 DNS 主机名,不允许直接填入 IPv4 或 IPv6 地址;同一名称类型也不能出现两次。
这些约束解决的是互操作问题,不是信任问题。SNI 不证明客户端查询过 DNS,不证明它控制域名,不证明它持有私钥,也不证明它获准访问同名业务。测试工具可以直接连接某个地址并发送另一个名称;扫描器可以枚举已知名称;攻击者当然也会选择最有价值的那个。
服务器认识扩展却不接受该名称时,可以发出致命 unrecognized_name。部署也可能按明确规则继续使用默认配置。因此审计不能只问“是否启用 SNI”,而要逐项验证缺失、未知、非法名称分别进入哪个分支,默认虚拟主机又获得了什么能力。
握手完整性不会产生名字所有权
在 TLS 1.3 中,ClientHello 进入握手转录,最终由 Finished 保护。正常握手完成后,中间人不能悄悄改掉 SNI 而不破坏端点看到的转录。
但这一保证只说明:服务端收到的确实是客户端在本次握手里提交的值。它不说明客户端有资格提交这个值。主张的完整性不是主张的权威。
如果部署没有客户端证书或其他客户端认证,服务端即使完成了一次完全合规的握手,也没有因此获知租户身份。服务端证书的认证方向恰恰相反——是客户端根据自己的参考身份去判断服务端。
字段命名应当保留这条界线。记录 client_requested_server_name 是准确的;写成 verified_tenant、owned_domain 或 authorized_origin,等于在日志层先伪造了授权结论。
选出证书不等于验证证书
SNI 常被用来选择证书,但选择动作发生在服务端,验证判断通常发生在客户端。两者不能合并。
RFC 9525 要求客户端独立构造可接受的参考身份,再与证书中的呈现标识匹配。证书链、有效期、撤销策略和服务名匹配都属于验证账本。服务端因为 SNI 拿出了正确候选证书,并不能证明客户端执行或通过了这些检查。
OpenSSL 的接口把区别写得很清楚:客户端用 SSL_set_tlsext_host_name() 设置 SNI 时,还应另外设置证书校验所期待的 DNS 名称。只设置前者的程序,可能帮助服务器返回“看上去正确”的证书,却从未完成严格的名称验证。
多名称证书也不会改变边界。一张证书可以合法覆盖多个域名,因此两个名称都可能通过密码学校验;这不意味着两个租户合并,更不意味着任何一个请求获得另一个业务的权限。
HTTP 名称是另一笔账
TLS 终止后,HTTP 才提交请求目标。HTTP/1.1 主要使用 Host,HTTP/2 与 HTTP/3 常用 :authority。RFC 9110 把这项信息视为关键的应用路由输入,也明确提醒它经常成为攻击面。
普通客户端往往从同一个目标 URI 生成 SNI 与 HTTP authority,所以二者经常相等。经常相等不是协议证明。它们位于不同层、在不同时间被处理,也可能因为连接复用、代理映射、测试、错误配置或攻击而不同。
应用必须为不一致情况写出政策:严格虚拟主机可以拒绝;受控共享服务可以许可一组明确组合;HTTP/2 在连接不适合该 origin 时可以返回 421。最危险的做法,是默认让第一次握手的 SNI 为连接上后续所有请求授权。
即使 HTTP authority 与 SNI 相同,它仍然不是客户端身份。正确做法是先让两种路由信息各归其位,再用客户端证书、令牌、会话或其他应用凭证确定主体。
代理两侧没有一条继承的 SNI
TLS 终止代理结束下游握手,然后作为客户端建立另一条上游 TLS 连接。两侧各有 ClientHello、SNI、证书、参考身份、转录和失败状态。
上游 SNI 可以写死,可以从上游主机名派生,也可以来自下游 HTTP authority 或受控映射。Envoy 把这些来源做成不同选项,并把按上游 SNI 验 SAN 与按请求 authority 验 SAN 区分开来。选项之所以分开,正是因为路由输入与验证输入可能不同。
透传又是另一种证据。NGINX 的 ssl_preread 能在不终止 TLS 的情况下读取 ClientHello SNI 并选择上游。此时中间设备只能证明“解析了提示并选择了路线”,不能声称握手完成,更不能声称客户端接受了最终服务端证书。
只存一项 sni=example 的观测模型会丢失连接腿、来源和验证者。审计至少需要下游连接 ID、上游连接 ID、名称来源、所选上下文、校验策略与各自结果。
ECH 让“我看见了哪个名称”也成为证据问题
Encrypted Client Hello 把敏感参数放入 ClientHelloInner。外层 ClientHelloOuter 通常携带 ECH 的公共名称,用于把流量送到面向客户端的服务以及完成重试机制。
ECH 被接受后,TLS 参数处理基于经过认证的内层 ClientHello。ECH 被拒绝时,客户端可能只为公共名称认证一条连接,以取得重试信息。RFC 9849 明确规定:公共名称连接的认证不是 origin 认证,TLS 实现不能把它作为成功连接交给应用。
因此 outer SNI 是最直观的受限信号:它能正确完成前端路由,却被规范明确禁止冒充真实 origin。若监控只写“SNI”,便会混合普通明文名称、公共外层名称与获接受的内层名称。
ECH 感知的日志要保留处理状态、观察角色和名称来源,同时限制真实内层名称的扩散。为了可观测性重新建立一份广泛可读的明文目的地档案,会直接抵消 ECH 的隐私目的。
运行证据比回调名称更重要
OpenSSL 的早期 ClientHello 回调先执行,随后是 server-name 回调,再后是 ALPN 选择;server-name 回调还能切换 SSL_CTX。GnuTLS 也提供客户端设置和服务端读取名称的接口。
API 只能证明软件具备能力。它不能证明本次连接调用了哪个分支、输入如何规范化、选择了哪个上下文、是否落入默认项,或结果是否被上层覆盖。TLS 1.3 与旧版本恢复会话的名称处理时机也可能不同。
升级 TLS 库、代理或证书映射后,应当让恶意不一致测试走过真实运行路径。记录输入类别、选择原因、配置标识与终止状态,才能把声明的规则变成可复核证据。
用拒绝测试证明边界
第一项测试不需要复杂攻击:不给任何客户端凭证,却发送最高权限租户的 SNI。TLS 可以完成,正确证书也可以出现,但应用主体必须仍是匿名,管理动作必须被拒绝。
随后分别删除 SNI、发送未知名称、触发默认虚拟主机,再制造 SNI 与 HTTP authority 的跨租户组合。使用同时覆盖两个名称的证书复测,确保“证书也有效”不会掩盖授权缺陷。
在代理场景中,让上游 SNI 与下游不同,要求两侧选择与 SAN 校验分别出现在证据中。对透传路由,禁止生成“证书已验证”或“握手已完成”的标签。最后覆盖 ECH 成功、拒绝重试和无 ECH 三条路径,证明系统知道自己处理的是内层还是外层。
配置截图不是测试结果。只有运行中的错误输入被正确拒绝,才说明权威没有越界。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
