Python 中 str.lower() 竟成安全漏洞
When str.lower() is a security vulnerability in Python – Seth Larson

在 Python 中,看似无害的 str.lower() 函数竟可能引发安全漏洞。问题源于 IDNA 2003 标准对 Unicode 3.2.0 的严格依赖,而 str.lower() 使用的是当前 Python 解释器内置的最新 Unicode 数据。这导致在不同版本下,字符大小写转换结果不一致,从而破坏了 RFC 3454 的规范一致性。例如,字符 'Ꭰ' 在不同 Unicode 版本下编码结果不同,可能引发域名解析错误或安全绕过。修复方案是通过构建例外表,强制 str.lower() 在特定场景下模拟 Unicode 3.2.0 的行为。这一漏洞由 Bitshift 发现,并由多位开发者协作修复,最终被记录为 CVE-2026-17084。
str.lower() 调用之所以成为漏洞,是因为它使用了 Python 解释器自带的 Unicode 数据,而非 RFC 3454 所要求的固定版本 Unicode 3.2.0。
HN 评论区
71- echoangle
> 这就是为什么调用 str.lower() 代表了实现与规范之间的差异,因此构成了一个漏洞:
我希望能有人解释一下,这为什么是一个漏洞,而不仅仅是一个生成错误数据的 bug。
对我来说,“漏洞”听起来意味着存在一种合理的方式可以利用这个 bug 进行攻击,但作为一个对此话题不太熟悉的人,我看不出这里存在这样的可能性。
- tialaramex
这种荒谬的做法正是为什么让从事 TLS 实现的 Python 开发者理解 SAN(主题备用名称)的既定机制如此重要的原因之一(注意,SAN 中的“备用”并非指“多个”的意思,X.509 最初是为 X.500 系统设计的,互联网后来借用了 X.509,所以这些是互联网视角下的备用名称),该机制明确指出这些是 DNS 名称,它们绝不应被理解为某种人类可读的文本,因此将它们“解码”为 Unicode 绝对是胡扯,尽管 Python 确实很想这么做,而且过去可能确实这么做过,或者至少曾提议过。
关于 SAN 中的 DnsNames 如何与 DNS 中的类似名称进行匹配,规则非常简单,简单到让你无法搞砸。你只需要处理单个通配符(ASCII 代码 42 的 * 匹配任意单个 DNS 标签),除此之外,就是字面上的字节比较。你不需要关心这些字节代表什么含义,要么所有字节完全一致,要么就不匹配,到此为止。
- ummonk
> 修复方案是创建新的异常,使得 str.lower() 在特定函数中表现得如同使用了 Unicode 3.2.0 一样。因此,我们需要遍历每个 Unicode 码点,并记录当比较 Python 随附的 Unicode 版本与 Unicode 3.2.0 时,str.lower() 的行为何时出现差异。
与实现一个独立的、冻结的 Unicode 3.2.0 小写转换函数相比,这听起来像是一个非常 hacky(取巧/临时)的解决方案。
- jooon
这让我想起 Spotify 发生过的一个旧的安全事件 https://engineering.atspotify.com/2013/06/creative-usernames
- ajd555
所以攻击面是域名中的位翻转吗?或者更具体地说,是 Unicode 转换的翻转,攻击者借此将流量重定向到恶意 IP?
能发现这样的漏洞真是令人印象深刻!