LWN:Python的两个安全漏洞!
共 4507字,需浏览 10分钟
·
2021-03-23 19:04
关注了就能看到更多这么棒的文章哦~
A pair of Python vulnerabilities
By Jake Edge
February 24, 2021
DeepL assisted translation
https://lwn.net/Articles/846847/
因为两个漏洞(vulnerability)导致了 2 月 19 日快速发布了 Python 3.9.2 和 3.8.8,在其之前几天就发布了 3.7.10 和 3.6.13 的纯源代码 release。这些漏洞可能会对一些 Python 用户和使用场景造成问题,其中一个可能会导致远程代码执行(remote code execution)。另一个可以说并不完全是 Python 标准库自己的缺陷,毕竟它只是也是遵循了一个旧标准,但它确实可能会导致web cache poisoning 攻击。
Overflowing
所解决的第一个漏洞是CVE-2021-3177,这是 1 月 16日提交的bug报告中发现的一个缓冲区溢出问题。当使用为 Python 为了提供兼容 C 的数据类型的 ctypes module 来将浮点数转换为非指数形式近似值的字符串时,就可能会发生问题。使用一个非常大的数字将使 python 解释器崩溃,正如下面这个示例所示 (这是根据 bug 报告中的想法改造出的示例):
>>> from ctypes import *
>>> c_double.from_param(1e30)
<cparam 'd' (1000000000000000019884624838656.000000)>
>>> c_double.from_param(1e300)
*** buffer overflow detected ***: terminated
Aborted
正如错误信息所示,这里出现了缓冲区溢出问题,具体来说是因为 sprintf() 这个 C 函数会将格式化过的字符串 "print" 到缓冲区(buffer)里,如果缓冲区足够大,那么就没问题。sprintf() 导致缓冲区溢出的问题可以追溯到 50 多年前就有出现过了,但人们可以使用更安全的替代函数。bug 报告者 Jordy Zomer 建议使用 snprintf(),它需要一个 size 参数,不会向字符串中写入超过 size-1 数量的字符,最后还有一个 NUL 终止符。
报告里面也指出了导致出错的代码:
case 'd':
sprintf(buffer, "<cparam '%c' (%f)>",
self->tag, self->value.d);
break;
这里的 "%f" 就是指要将 float 值转成字符串,所以如果传递一个非常大的数字(比如说 1e300)进来,就会创建出一个非常长的字符串,导致这个 256 字节的、基于 stack 的缓冲区发生溢出。如果某个程序对用户提供的数据来进行这种格式转换,就可能导致执行恶意代码,因为攻击者可以对溢出写入 stack 的数据有部分控制能力。如果这个输入数据是从网络上来的,那么当然就可能产生远程代码执行攻击(remote code execution)。然而,在 python-dev 邮件列表上,Stephen J. Turnbull 对这个 bug 是否真的那么容易利用表示怀疑,他认为没有必要急于发布新版本来解决这个问题。
在这个 bug 被报告出来之后,Benjamin Peterson 很快就在多个 Python branch 上都进行了修复。不过他没有使用 snprintf() 来修复,而是使用了 PyFloat_FromDouble() 和 PyUnicode_FromFormat() 两者组合来解决这个具体问题。在 1 月 18 日的 patch 中可以看到,他还将 ctypes module 中其他对 sprintf() 的调用也改为使用 PyUnicode_FromFormat() 。PyUnicode_FromFormat()不需要一来一个 buffer(缓冲区),而是会算出要生成的字符串的大小,分配出来,然后将其返回。
正如 Python bug-tracking page 所显示的,这个问题在两天内就被修复了。大约一个月后,目前仍然得到支持的四个 Python 版本全都发布了新的 release。虽然 Python 2.7 已经没有 python 核心开发者的支持了,但一些发行版仍然会针对安全问题来提供更新。这次这个问题只出现在 Python 3.x 中,所以没有必要 backport 这个 fix。【更新:正如 Moritz Muehlenhoff 在邮件中所指出的,Python 2.7 实际上也受到了这个 bug 的影响。他指出 Debian 10("Buster") 上的 python2 也受到了影响,并且已经更新。此外,Fedora 也正在对 python2.7 包进行修复。】
Poisoning the web cache
新版本所解决的第二个漏洞是 CVE-2021-23336,这是由于对 URL query 参数的分隔符的不同解释而导致的对 Web cache 的污染攻击。旧版的 HTML4 规范允许用";" 和 "&" 来分隔查询参数,但 HTML5 只允许使用后者。标准库中的 urllib.parse 模块遵循了旧的规范,但这可能会导致 web cache 出问题。
根据 bug 跟踪页面,该问题在 2020 年 10 月就被报告给 Python 安全响应团队(PSRT,Python security response team),但在 1 月 18 日的一篇 Snyk blog文章 以及 1 月 19 日的 bug 报告导致该问题被公开,都是来源于 Adam Goldschmidt。
这篇 blog 内容很丰富,介绍了 Web 缓存污染的问题,以及某些 Python Web 框架如何成为了这类攻击的对象。基本思路是,人们出于性能考虑,经常使用 Squid 等工具对 Web query 进行缓存。缓存条目的索引 key 通常是使用 URL 中的一部分,如果一个新的 request 与这个索引 key 相匹配了,则会直接将 cache 中的版本返回回去,而不是从 host 再次请求获取。这样既可以减少响应时间,一方面因为没有到远程主机去绕一圈,从而减少了网络引入的耗时,另一方面因为没有在远程 host 上进行任何处理,所以就不存在应用层的延迟。
web 缓存污染有若干种类,但这次影响 Python 的是主要因为 cache 和 web 这两台服务器对 URL 的解释不同。基于 Python 的 web 应用如果使用 urllib.parse.parse_qsl(),则会把分号和 ampersand 符号(&)都看成是查询参数的分隔符,但 cache server 通常不会这样做。此外,有一些查询参数通常是不会用来作为 cache key 的(比如 utm_content 和其他 utm_*参数)。这使得攻击者可以进行这样的 query:
https://example.com/search/?q=common_term&utm_content=s;q=malicious_term
web cache 认为这个查询有两个参数,其中一个不会用在 key 中,但是在 web server 里运行的 Python 应用会将其看做是有三个参数,第二个 q 会将第一个参数的值给覆盖掉。这样一来 web cache 中本来指向 "common_term" 的 cache 会被引向 "malicious_term"。在 cache 内容过期(expire)之前,用户根本无法访问到他们所期待访问的东西,取到的是别人希望它看到的。这当然会事实上导致各种令人不快、令人困惑的攻击,会有潜在风险。
最明显的解决方案就是让 Web 应用停止将分号当做分隔符,但直到 2 月 14 日合入的改动(并在此后不久正式包含在 release 版本中)才真正在 urllib.parse 中改成了这样。之前的版本都无法控制了。这个 patch 将默认行为改为只允许用分号来分隔查询参数。它还为 parse_qsl() (以及相关的 parse_qs()) 添加了一个新的、可选的 separator 作为参数,允许开发者选择使用其他分隔符,比如可以是分号。但需要注意的是,无法使用超过一种分隔符,所以无法再回到现有的行为(接受两个查询分隔符)了。
显然,这个改动本身是非常简单直接的。这个 patch 中大部分内容都在修改文档和测试集(这里也有)。不过与缓冲区溢出那个问题不同的是,这个 bug 也影响到了 Python 2.7,Red Hat 的 bug tracking 页面上就指出了这一点。
可以说,这里并不完全是 Python 的错。正如 bug report中提到的,W3C 确实在 HTML4 规范中建议使用分号,不过是在附录中,而不是在此规范的主体部分。也有一些人认为改变默认值(并取消处理两种分隔符的功能)的方法不好,但 urllib 维护者 Senthil Kumaran 决定最好在 urllib 中进行修复,并按照这种避免让 Web proxy 感到混乱的方式来 fix。
这两个 bug 并没有什么特别值得注意的地方,但它们确实给大家展示了一下 Python security process(安全流程)。缓冲区溢出的问题解决得相当快,但 web cache poisoning 的问题花了相当长的时间来解决。目前还不完全清楚在 PSRT report 和公开披露这个 bug 之间的三个月里发生了什么。不过在那之后,开发人员的动作相当迅速。除此之外,了解一下正在出现的漏洞种类总是会有用的,至少,它可以帮助人们留意其他类似领域中的类似问题。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~