在网络场景中经常会遇到请求头Host与SNI(Server Name Indication),本文介绍这两者之间的区别,以及在不同的场景下当SNI与Host不一致时的解决方案。
什么是Host?
Host
是 HTTP 标头之一,格式为“Host: xxx”。HTTP 标头中有Location、User-Agent、Connection 等各种标头。Host 标头在 RFC 7230 中,用于定义 HTTP 请求的主机名。我们可以在 Web 浏览器中的开发者工具的网络选项卡中找到该标头。
在 HTTP/1.x 中使用Host
来指定请求目标的主机和端口号,您可能会疑惑当我们发起HTTP或者HTTPS请求时为什么Host中没有看到端口号呢?这是因为HTTP/HTTPS使用的是默认端口80/443,当使用自定义端口时,我们的请求中需要添加相应的端口,例如:https://my.example.com:8443/get。
在 HTTP/2 中使用:authority
用于指定请求目标的主机和端口号,类似于HTTP/1.x中的Host
头字段。HTTP/2设计去除了Host
,而使用:authority
来明确标识请求的目标。在HTTP/2中,请求的其他重要伪头字段还包括:method
(HTTP方法,例如GET、POST等)、:path
(请求资源的路径)和:scheme
(请求使用的方案,如HTTP或HTTPS)。
云原生网关的访问日志中统一使用:authority
来记录请求的主机名,如下图。
什么是SNI?
SNI 是另一个TLS 扩展,定义在RFC 6066 中,它在TLS 握手中指示来自客户端的 FQDN。TLS 握手类似于 TCP 三次握手,但 TCP 握手建立 TCP 连接,而 TLS 握手在 TCP 连接之后开始,因此 TLS 位于 OSI 模型的上层。我们需要在数据包捕获中确认 SNI,因此在浏览器中找不到它。
下面是在客户端访问微软文档时捕获的数据包。在 TCP 三次握手(蓝色)之后,客户端发送了Client Hello(红色),可以看到在Client Hello的请求包中包含 Server Name Indication,内容是“docs.microsoft.com
”(绿色)。
在 Web 服务器端,它会根据 SNI 验证服务器上证书的 FQDN,选择相应的证书,然后进行 TLS 握手。TLS 握手建立后,即可进行加密通讯。
当请求没有 SNI 时,ClientHello 数据包中也没有 server_name 扩展,如下所示:
在HTTPS请求中SNI是如何起作用的呢?
下图说明了在浏览器中打开网站的过程顺序。
在步骤5中,客户端发送服务器名称指示(SNI)。
在步骤6中,客户端发送主机标头并获取网页。在此图中,
www.contoso.com
用作 SNI 和主机标头,Web 服务使用与 FQDN 匹配的网页进行响应。
通过下图可以看到在整个HTTPS处理过程中,会先通过SNI来建立TLS安全连接,然后再发送响应的HTTP请求,HTTP请求中会包含请求的Host
标头。
在HTTPS请求中SNI与Host必须一致吗?
在HTTPS请求中,SNI(Server Name Indication)和Host头通常应该一致,但它们不一定必须相同。
SNI(Server Name Indication):这是在TLS握手过程中发送的,以便服务器知道客户端想要访问的具体主机名。SNI是在TLS层上用于指示服务器要处理哪个域名的扩展。这在共享同一个IP地址的多个虚拟主机尤其重要,因为它允许服务器在握手时选择正确的证书。
Host头:这是HTTP请求中的一个头字段,用于指定客户端希望访问的具体主机名。这个字段主要是在HTTP协议中使用的,用于指示服务器哪个虚拟主机需要处理请求。
在大多数情况下,尤其是在虚拟主机配置环境下,SNI和Host头是相同的,因为浏览器和客户端通常会自动使用相同的主机名来填充这两个字段。然而,从技术上讲,两者并没有被硬性要求必须相同。在某些特殊情况下(例如测试环境或过渡阶段),它们可能会有所不同。
如果SNI与Host不相同,可能会引发一些潜在的安全风险或问题,包括:
不正确的服务器响应:服务器可能会基于SNI选择证书和配置。如果SNI与Host不匹配,服务器可能会返回错误的证书,导致客户端收到无效的HTTPS连接,这是由于证书不匹配而引发的。
中间人攻击可能性增加:不匹配的信息可能是恶意攻击的结果。例如,中间人攻击者可能试图通过故意制造SNI与Host不匹配来诱导服务器或客户端产生错误行为。
隐私泄露:当SNI信息暴露且与期望的Host不匹配时,可能会泄露某些信息,如用户试图访问的服务类型或目的地。
服务器配置错误:不一致的SNI和Host可能导致服务器选择错误的虚拟主机,从而暴漏不必要的信息或导致应用程序错误。
为了确保连接的安全性和正确性,建议在配置和请求中确保SNI和Host头的值一致。
在云原生网关中SNI与Host不一致会出现什么问题?
在网关的访问日志中会出现404报错,您可以根据response_code:404
来检索查询,如下图所示:
通常在什么场景下会出现SNI与Host不一致的情况
HTTP/2场景下因连接复用引发的SNI与Host(:authority)不一致
对于HTTP/2来说,连接多路复用的能力是与HTTP/1 的一个核心差异。特别是对于浏览器场景,尽可能的连接复用,可以在开启 TLS 的情况下,显著优化页面加载时间(不考虑队头阻塞的情况)。在HTTP/2 的RFC 规约里对于连接复用也有以下描述:
Connections that are made to an origin server, either directly or
through a tunnel created using the CONNECT method (Section 8.3), MAY
** be reused for requests with multiple different URI authority**
** components. A connection can be reused as long as the origin server**
** is authoritative** (Section 10.1). For TCP connections without TLS,
this depends on the host having resolved to the same IP address.
所以如 Chrome 浏览器就会在达成以下条件时,对一个域名B的请求,复用和域名A建立的HTTP/2 连接:
域名 B 和域名 A 是解析到同一个IP。
跟域名 A 建立通信时获取的证书的 Common Name 是 wildcard,且可以匹配到域名 B;或者 SAN 中存在域名 B。一旦和域名 A 建立的连接上发送了域名 B 的请求,就出现了上面提到的,在网关侧的日志中看到 SNI 和 :authority 头不匹配的问题了。
解决方案
如果HTTPS证书是泛域名证书,例如 *.example.com,业务请求使用的是子域名,例如 a.example.com,b.example.com,可以通过配置调整来解决SNI与Host不一致问题,具体做法如下:
在网关的域名中创建泛域名,以 *.example.com 为例如下:
在网关中创建路由,以 a.example.com 为例如下:
在网关中为域名 b.example.com 以同样的方式创建路由即可。
云原生网关版本升级到2.0.8以上,云原生网关针对该问题做了优化可以兼容这种Host与SNI不一致的情况。网关如何兼容这种该场景具体参考Envoy HTTP2 404 如何解决。
如果业务上不是强依赖HTTP/2协议,可以在网关的参数配置中把EnableHttp2置为false关闭HTTP/2。
如果业务上使用的Client比较新可以兼容HTTP 421状态码,可以在插件市场中启用http2-misdirect插件,421状态码也是RFC推荐的解决连接复用问题的方式,具体RFC规范如下:
In some deployments, reusing a connection for multiple origins can result in requests being directed to the wrong origin server. For example, TLS termination might be performed by a middlebox that uses the TLS Server Name Indication (SNI) [TLS-EXT] extension to select an origin server. This means that it is possible for clients to send confidential information to servers that might not be the intended target for the request, even though the server is otherwise authoritative. A server that does not wish clients to reuse connections can indicate that it is not authoritative for a request by sending a 421 (Misdirected Request) status code in response to the request (see Section 9.1.2).
HTTP/1.x 场景下SNI与Host不一致
HTTP/1.X出现该问题与协议本身无关,触发原因基本有以下几种情况:
使用的HTTP Client自身存在缺陷引起,之前遇到过客户自行使用C开发的HTTP Client出现过该问题。
如果网关前面有Nginx,即请求链路是 Client —>Nginx —>云原生网关 —>Server,在Nginx转发HTTPS时需要主动设置SNI,参考配置如下(其中 server_name 即为SNI):
upstream backend { server backend1.example.com server_name backend1.example.com; server backend2.example.com server_name backend2.example.com; }
如果网关前面接入了 CDN/DCDN,即请求链路是 Client —>CDN/DCDN —>云原生网关 —>Server,回源采用HTTPS,请在DCDN的“回源配置”中设置回源SNI,回源SNI与Host应该保持一致。
如果网关前面接入了 WAF,即请求链路是 Client —>WAF —>云原生网关 —>Server,回源采用HTTPS,在WAF中使用CNAME方式接入的,需要修改对应接入域名,在其“配置转发”中选择“启用回源SNI”,回源SNI与Host应该保持一致。
HTTP/1.x 场景下因Client请求没有携带SNI引起TLS建连失败
HTTP 1.X出现该问题与协议本身无关,触发原因基本有以下几种情况:
使用的HTTP Client自身存在缺陷引起,之前遇到过客户自行使用C语言开发的HTTP Client出现过该问题。
使用的HTTP Client版本很老,是在SNI规范发布之前的,老版本在发起HTTPS请求时不会携带SNI。
网关中通过访问日志如何定位是因为Client没有携带TLS引起的失败呢?参考的异常访问日志如下:
其中 response_code_details
内容是filter_chain_not_found,authority
,requested_server_name
字段都为空。
解决方案
首先推荐用户升级 HTTP Client,在现代网络环境中,目前SNI基本成为默认配置,不管是各种现代的代理软件还是网关都要求SNI,对于不携带SNI的情况也会存在一些潜在风险。
如果HTTP Client无法升级,同时没有SNI情况的请求集中在一个精确域名或者泛域名中,可以在网关中给默认的 * 域名配置证书并添加相应的路由,如下:
如果HTTP Client无法升级,同时没有SNI情况的请求集中在多个精确域名中,可以在网关中给默认的 * 域名配置证书并添加相应的路由,注意给 * 配置的证书需要采用"多域名证书"(SAN证书,Subject Alternative Name Certificate),即一个SAN证书可以同时包含
example.com
、example.net
和sub.example.org
等多个域名。
- 本页导读 (1)
- 什么是Host?
- 什么是SNI?
- 在HTTPS请求中SNI是如何起作用的呢?
- 在HTTPS请求中SNI与Host必须一致吗?
- 在云原生网关中SNI与Host不一致会出现什么问题?
- 通常在什么场景下会出现SNI与Host不一致的情况
- HTTP/2场景下因连接复用引发的SNI与Host(:authority)不一致
- HTTP/1.x 场景下SNI与Host不一致
- HTTP/1.x 场景下因Client请求没有携带SNI引起TLS建连失败