A Tale of a Trailing Dot (2022)
标题:一个关于末尾句点(Trailing Dot)的故事 (2022)
daniel.haxx.se
一个关于末尾句点(Trailing Dot)的故事
May 12, 2022 Daniel Stenberg 10 Comments
URL 中主机名后带末尾句点,一直是一个惊喜。
让我带你了解一下,在一个互联网客户端的堆栈中,不同的地方是如何处理这个句点的。“邪恶”的末尾句点。
DNS
当一个给定的主机名需要在联网的计算机上解析为 IP 地址时,有一些专门的函数可以使用。主机名 example.com
可以解析为多个 IP 地址。
如果在主机名末尾添加一个句点,解析结果不会改变。“example.com.” 解析的地址集合与 “example.com” 相同。(但是如果在末尾放置 两个 句点,则会失败。)
之所以这样工作,是因为 DNS 的构建方式是用不同的“标签”(在文本中用句点分隔),因此末尾的句点只是一个空的最终标签,就像没有句点一样。因此,在 DNS 协议中,没有所谓的末尾句点。如果尝试在末尾添加两个句点,则会在它们之间创建一个零长度的标签,这是不允许的。
习惯于摆弄 DNS 的人通常习惯于在完全限定域名 (FQDN) 结尾加上一个末尾句点。
解析名称
除了实际的名称解析(发送到 DNS 解析器)之外,原生解析器函数通常会在解析 “hello” 和 “hello.”(带或不带末尾句点)之间赋予不同的含义和语义差异。末尾句点表示该名称应该完全按照这样使用,它是完全指定的;而没有末尾句点的名称可以尝试附加一个域名。甚至可以尝试一个域名列表,直到其中一个可以解析。这使得人们有时 想要 使用末尾句点,以避免域名测试。
HTTP 名称
想要处理给定 URL 的 HTTP 客户端需要从 URL 中提取名称部分,并使用该名称来:
- 将主机名解析为要连接的 IP 地址列表。
- 将该名称传递到
Host:
或:authority:
请求头中,以便 HTTP 服务器知道客户端正在与哪个特定服务器对话,因为它可能在同一 IP 地址上运行多个服务器。
HTTP 规范指出,Host
头部中的名称应按 URL 中的原文使用;如果 URL 中存在末尾句点,则应包含该句点。这允许服务器为 “example.com” 和 “example.com.” 托管不同的内容,即使许多服务器默认情况下将它们视为相同。有些主机只会将带句点的版本重定向到不带句点的版本。有些主机将返回错误。
对于两者,HTTP 客户端肯定连接到相同的地址集合。
对于许多 HTTP 流量,是否存在末尾句点没有区别。但是,可以使它们产生差异。而且,它们肯定会在内部产生差异……
Cookies
Cookie 使用专用的请求和响应头部在 HTTP 上来回传递。当服务器想要将 cookie 传递给客户端时,它可以指定该 cookie 对哪个特定域名有效,并且客户端仅在与其对话的域名与设置 cookie 的域名匹配时才将 cookie 发送回服务器。
Cookie 规范 RFC 6265 section 5.1.2 定义主机名的方式是忽略末尾句点。为带有句点的域名设置的 cookie 对于没有句点的相同域名有效,反之亦然。
SNI
当与 HTTPS 服务器通信时,客户端会在 TLS 握手期间传递远程服务器的名称,位于 SNI (Server Name Indication) 字段中,以便服务器知道客户端想要与哪个实例对话。你认为末尾句点会怎么样?
主机名表示为使用 ASCII 编码的字节字符串,**没有末尾句点。**
这意味着,HTTPS 服务器无法在 TLS 层区分 “example.com.” 和 “example.com” 的服务器。对于 HTTP,可以是不同的主机;对于 HTTPS,是相同的主机。
curl 的点点滴滴
在 curl 项目中,我们——和所有人一样——也一直在与末尾句点作斗争。
现状
一开始,我们大多没有注意到末尾句点的影响,并且大多数代码只是将其视为主机名的一部分,并且它会出现在主机名中的任何地方。直到有一天,有人指出 SNI 字段不认可它。我们修复了它。
移除它
在 2014 年,如果 URL 中提供了末尾句点,curl 开始总是从内部将其删除。该句点很少产生影响,它使主机名可以与 SNI 一起正常工作,并且对于 HTTPS 来说,实际上很难区分它们。
保留它
在 2022 年,有人发现了一个网站,该网站实际上需要在 Host:
头部中包含末尾句点才能正确响应,并将其报告给 curl 项目。
唉。我们撤回了八年前的决定,并决定在内部保留名称中的句点,但为了 SNI 字段的目的,将其删除。这似乎是浏览器正在做的事情。我们发布了带有此更改的 curl 7.82.0。现在可以使用 curl 检索那个需要在 Host:
头部中保留末尾句点的站点了。耶。
作为奖励,curl 现在还将 SNI 名称字段转换为小写,因为即使规范指出该字段应该不区分大小写,浏览器也是这样做的。这种习惯确保了互联网上存在一些如果 SNI 名称不是小写就无法正常工作的服务器……
打脸
我们为了 7.82.0 版本所做的“撤回”,即将句点带回主机名字段,结果是不完整的,但这一点并不完全明显或立即显现。
当我们把末尾句点带回名字段时,我们意外地破坏了几个内部名称检查。
即使如上所述,cookie 应该不关心末尾的句点,这些检查在 cookie 处理域名时也失败了。
要理解这一点,我们必须稍微回退一点,并讨论 cookie 和 cookie 域名的工作方式。
公共后缀(Public Suffixes)
Cookie 是奇怪的野兽,因为服务器可以告诉客户端 cookie 适用于哪个域名,所以客户端需要检查服务器是否尝试将 cookie 设置得太宽泛或设置给其他域名。这还不是全部,还有一个名为“公共后缀列表”(Public Suffix List,PSL)的概念,它是已知不允许设置 cookie 的域名。(此列表也用于限制浏览器中的其他内容,但这里不讨论。)其中一个广为人知的域名示例是 “co.uk”。不应允许服务器为 “co.uk” 设置 cookie,因为这样它基本上会为英国存在的每个网站发送回来。
PSL 是一个维护的列表,其中包含大量域名。为了管理这些域名并确保像 curl 这样的工具可以方便地检查它们,几年前创建了一个专用库:libpsl。自 2015 年以来,curl 一直可以选择使用它。
我说的是可选
该公共后缀列表非常庞大,这是许多用户仍然选择在构建 curl 时不包含对其支持的主要原因。这意味着 curl 需要为没有 libpsl 的构建提供备份功能。通常在许多嵌入式系统中。
如果不了解 PSL,curl 不会拒绝 “co.uk” 的 cookie,但它应该拒绝 “.uk” 或 “.com” 的 cookie,因为即使没有 PSL 知识,它仍然知道为顶级域名设置 cookie 是不允许的。
在没有 PSL 的情况下,curl 检查是如何验证给定的域名是否只是一个 TLD 的?
它会检查名称中是否存在句点——如果存在,则它不是 TLD。
CVE-2022-27779
Axel Chong 发现,对于没有 PSL 知识的 curl 构建,如果您确保以句点结束名称,则服务器可以为 TLD 设置 cookie。
使用 7.82.0 中的更改,curl 保留主机名的末尾句点,结合为带有末尾句点的 TLD 域名设置的 cookie,它们具有匹配的尾端。这意味着 curl 会将 cookie 发送到符合条件的服务器。在允许末尾句点进入之前,被破坏的 TLD 检查多年来都是良性的。这就是安全漏洞 CVE-2022-27779。
CVE-2022-30115
事情并没有到此为止。Axel 并没有到此为止。由于 curl 现在在名称中保留了末尾句点,而之前没有这样做,因此 Axel 发现并报告了第二个重要的字符串比较以意想不到的方式被破坏。由同一更改引入的第二个漏洞。
HSTS 允许 curl 存储主机名的“缓存”并将其保留,因此如果您想在这些主机名过期之前再次传输到其中一个主机名,curl 将直接转到 HTTPS,即使 URL 中使用了 HTTP。这是一种避免某些 URL 使用的明文不安全重定向步骤的方法。
对末尾句点的新处理方式基本上允许用户以两种不同的方式提供相同的主机名,但仍然解析为完全相同的地址,这暴露了 HSTS 代码没有正确处理(忽略)末尾句点。如果您让 curl 存储 没有 末尾句点的主机名的 HSTS 信息,您可以稍后通过使用 带有 末尾句点的相同主机名来绕过 HSTS。反之亦然。这就是安全漏洞 CVE-2022-30115。
alt-svc
alt-svc 的代码也需要针对句点进行调整,但幸运的是,这“只是”一个 bug,没有安全影响。
所有这三个末尾句点导致问题的独立领域已在 curl 7.83.1 中修复,并且现在所有这些领域都经过了扩展测试集的测试和验证,以确保它们保持正确处理句点。
有人称其为句点版本。
这就是句点问题的终结吗?
我不知道,但这似乎不太可能。末尾句点已经困扰我们很长时间了,所以我认为很有可能还潜伏着一些缺陷,并且有一些未来的更改正在进行中。然后,这可能会使循环再循环一两次。
我想我们会发现的。敬请关注!