关注了就能看到更多这么棒的文章哦~
Fingerprinting systems with TCP source-port selection
By Jonathan Corbet
October 6, 2022
DeepL assisted translation
https://lwn.net/Articles/910435/
早在 2022 年 5 月,一组名为 insufficient TCP source port randomness 的神秘 patch 在邮件列表中出现,随后被合并到了 5.18 内核的 -rc6。当时,几乎没有任何信息介绍为什么需要在开发周期这么晚的时候对网络协议栈代码进行这样的重大修改。随着 Moshe Kol、Amit Klein 和 Yossi Gilad 的这篇论文的发表,人们终于了解到了其中的内幕。看起来内核针对那些对外网络(outgoing network)连接选择端口号的方式有可能被人利用对用户进行身份识别(fingerprint users)。
Selecting a source port
一个 TCP 连接可以通过四元组来描述,其中包括来源和目的地的 IP 地址,以及来源和目的地的端口号。在某一个具体的连接中,IP 地址和目的地的端口号都已经是确定的了,但发送方的源端口号可以是任意数字。长期以来,人们都知道有必要让这些数字不可预测,从而让网络连接更容易避免 reset attack 甚至是数据注入(data injection)等危险的影响。因此,自从 Eric Dumazet 的一个 patch(https://git.kernel.org/linus/190cc82489f4 )被合并到 5.12 版本后,Linux 内核已经按照 RFC 6056 实现了源端口随机化功能。
随机化的算法需要难以预测,而且要快;Linux 的实现已经满足了这些目标。但事实证明,对于正确地选择源端口号,还有一些其他因素要考虑。为了理解这里的道理,我们应该先快速看一下 5.18 之前的 Linux 实现机制。
简而言之,内核从四元组的三个已经确定的部分(两边的地址以及目标端口号)计算出两个哈希值,本文称之为 F 和 G。为了确保不同的系统对相同的 tuple 都产生不同的哈希值,它还混入了一个在启动时产生的 32 位随机密钥。F可以是一个任意大的数字,但 G 被限制在一组 counter 的 size 了。端口号的选择是计算出来的,计算方式大致如下:
port = (counter_table[G] + F) % port_number_range;
然后对这个 counter 进行递增操作。当然,还有一些复杂的东西需要处理,比如检查端口号是否已经在使用,这些就不细谈了。
这个算法的一个关键点就是 counter table 的 size。正如 5.17 的代码(就是在 5.18 的 fix 之前)所指出的,RFC 6056 建议分配能容纳 10 个条目的 table 就够了,但 Dumazet 决定使用 256 个条目,"从而提供更好的真正隔离和隐私"。
The attack
Kol 和他的同伴能够想出一个有趣的方法来攻击这个算法。只要某个恶意的网页(今天互联网上几乎到处都是)可以加载一个 JavaScript 片段,经过一系列尝试,就能在目标端口号和用于分配源端口号的计数器表项之间建立一个映射关系。换句话说,它是在寻找 counter table 中的 hash-table collision(哈希碰撞)。记住,这个 table 只有 256 个条目,所以哈希碰撞并不罕见,也不难。
具体来说,这个攻击会发起一系列的出站连接,都是到同一个远程地址,但每个都是到一个不同的目标端口上。然后,它查看每个连接中所分配的 source 端口号(注意,并不需要实际建立连接)。由于某个特定的 counter-table entry 在用于生成源端口号码后被递增,如果源地址和目的地址相同,在建立两个连接的尝试中碰到这个 counter-table entry 就会导致源端口号码相差 1。因此,该攻击寻找那些可得到连续的 source 端口号的连接尝试,并可以确定这些尝试中所使用的目标端口号就是 map 到同一个 counter-table entry 的。
这种攻击中,迭代的最佳出站连接数被认为是比 counter table size 少 1,也就是 255。这种算法中一次迭代最多会产生少量的碰撞,这并不能告诉攻击者什么,但可以反复运行,从而得出更多的碰撞。因此,上述过程不断重复,直到为 counter table 中的每个 entry 都能找到碰撞。在完成之后,第二步就可以使用类似的技术,但将到 loopback 地址的连接和到远端服务器的目标端口(之前在第一阶段中找到的端口好)的连接混在一起。这里目的是找到哪些目标端口在跟 loopback 地址时,会跟访问访问远程服务器的这些端口时落到相同的 table 项里。第二阶段产生了若干对目标端口号,在配合 loopback 地址使用时,可以产生 counter table 的碰撞;而这一对对的端口号都跟远程地址无关。
每一对产生碰撞的 loopback 端口号,实际上就是告诉攻击者关于内核在启动时产生的密钥的更多信息。密钥本身不会被披露,但攻击者也不需要获取这个密钥;只要有足够数量的碰撞端口号对,就足以唯一地标识该系统了。这里的关键逻辑是,这些端口号对是秘钥的函数(秘钥对每个系统都是不同的)因此可以用来创建一个唯一的设备标识符。
显然,每个 counter-table entry 都需要大约 40 次连接尝试才能产生足够的碰撞,因此大约需要 10,000 次尝试来标识一个系统。(论文中描述了如何计算出 "enough",但没有给出一个数字)。进行这种攻击所需的时间大约是 10 秒钟,所使用的资源非常少,所以很有可能不被发现。(当然,这个讨论忽略了很多重要的细节,而且几乎可以肯定某些地方都说错了;完整的内容还是要查看原论文)。
这种独特的标识符有一些有趣的特点。它完全独立于正在运行的软件,所以即使用户切换了浏览器,或者哪怕仅仅是用 curl 之类工具来获取一个网页 page,也能识别出来。无论是连接到哪个网站,它都是一样的,所以它在跟踪多个网站的用户时效果很好。在同一 host 上运行的不同容器都会有相同的标识符。即使是具有相同硬件+软件配置的系统也会产生不同的标识符。
换句话说,这种识别系统的能力看起来就像是给外界的监视资本家(surveillance capitalist)的礼物。不过,它确实还是有一些局限。它无法通过 Tor 这样的网络仍然工作,因为连接是在 Tor 网络中终止了,并在出口节点(exit node)重新启动。网络地址转换(NAT)系统会重新分配端口号,也会干扰识别工作。但正如作者所指出的,IPv6 越来越多了,可能会减少 NAT 的使用,使得 NAT 的干扰不再是一个问题。
当系统重新启动时,标识符也会改变。然而,有一类广泛存在的设备——运行安卓系统的设备——往往不会频繁重启。对安卓的威胁似乎是作者特别关注的问题。在作者的描述中,它并不是一个急迫的威胁,因为安卓设备运行的最新版本的 kernel 是 5.10 内核,而有漏洞的端口选择代码是在 5.12 中添加的。不过,作者可能忽略了这样一个事实:这个"改进过的"端口选择代码也是 5.10.119 稳定更新的一部分,而且很可能已经在一些安卓系统上运行。
The fix
由 Willy Tarreau 发布的解决该问题的 patch set 中做了一些改动。其中之一是改变了 hash 计算,混合了另一个每 10 秒变化一次的数字进来(其实就是是 jiffies/10*HZ)。这就会扰乱上述的选择使用哪个 counter 的过程,因此,就可以破坏当时可能正在进行的恶意识别的尝试。另一个改动是用一个随机数(在 0 和 7 之间)来增加选中的 counter,而不是总是加 1,这样就可以让选取的端口号变化更多。
这些改动可能足以挫败上述的攻击了,但也只是勉强。这个应对措施的核心改动其实是将 counter 的 size 增加到 65,536 个条目。这使 table 的 size 从 1KB 增加到了 256KB,但它也使得碰撞更不常见,因此更难发现,大大增加了进行成功识别所需的时间。最终得到的是一系列的防御措施,可以防止通过源端口号的选择机制来识别系统。
内核关于安全问题的策略通常是在报告出来不久之后就要求披露。在开发 fix 程序时,可以允许短时间不要发布,但也顶多就能做到这样了。但是,在这次的情况下,这些 fix 程序最初是在 4 月份发布的,其中没有对导致它们的问题进行描述。而且,在编者当时询问这个问题时,得到的答案是几个月内不会有解释。
在这种情况下,漫长的保密期似乎并不是来自于 security。fix 程序是公开的,而且很快就会被纳入所有那些正在针对 security 问题活跃维护的内核中。相反,这种 delay 完全是由发表描述该漏洞的文章的杂志的要求而导致的。该杂志要求以方便自己的出版时间表的方式进行排他授权,禁止在其他地方发布对该漏洞的解释。
因此,很少有开发者能够 review 这些 patch 是否真正 fix 了它们所针对的问题。内核社区不得不依靠对相关开发者的信任(Dumazet 参与了这个创建过程)。这并不是一个正常的流程。内核社区对寻求长期禁运的发行版提供商没有什么耐心;不清楚学术期刊是否值得更多尊重。
尽管如此,这个问题似乎已经得到了很好的解决,而且我们现在有了一个解释,即为什么需要在近六个月前首次发布这些补丁。是否有办法能永久避免个别系统被打上指纹,这还没有答案,但是至少有一个现成的机制已经被关闭了。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~