在本文中,我们将与读者一起深入考察解释型语言背后隐藏的攻击面。
通常情况下,在高级语言的内存管理功能的实现代码中,往往存在着相对脆弱的基于C/C++的攻击面。这种问题可能存在于语言本身的核心实现中,也可能存在于将向高级语言提供基于C/C++的库的第三方语言生态系统中。本上一篇文章中,我们为读者介绍了与此紧密相关的C格式字符串漏洞方面的知识,在本文中,我们将为读者介绍这些底层实现是如何影响解释型语言的安全性的。
Perl格式化的幽灵(CVE-2005-3962)
对于解释型语言来说,提供自己的格式设置函数的情况并不少见,特别是Perl通过其较低级别的Perl_sv_vcatpvfn函数提供格式支持。这些低级C API为高级Perl API提供了许多核心格式化支持。它的格式化支持在语法上与C语言的格式化支持有些相似,因为它也支持直接参数访问的概念,在Perl中,该参数被称为“精确格式索引”,以及格式标识符%n。
当我们考虑到存在基于Perl的远程服务应用程序明显易受格式字符串错误的影响时,了解Perl内置的格式化支持就变得非常有趣。然而,由于没有办法在Perl级别上直接利用这些错误,因此,安全研究社区并没有花太多精力来尝试利用这些错误,而是通常将它们视为“只不过是一个bug而已”。
大约在2005年,在CVE-2005-3962的作者(Jack Louis)确定这些漏洞的可利用性之后,我对Perl格式字符串漏洞进行了更深入的研究。在Webmin中测试Jack Louis发现的Perl格式字符串错误时,他在Perl解释器中遇到了一些可观察到的崩溃。
事实证明,攻击者确实可以通过Perl_sv_vcatpvfn中的格式化支持的C级实现来利用基于Perl的格式字符串漏洞。
Perl格式字符串的参数存储在参数结构指针数组(称为svargs)中,并为格式说明符(例如%1$n)提供准确的格式索引,以使用该索引从参数数组检索适当的参数结构指针。当从数组中检索关联的参数结构指针时,Perl将根据格式字符串可用的参数数量,确保所提供的索引不超过数组的上限。这里的参数计数实际上保存一个带符号的整型变量中,即svmax。也就是说,如果将格式字符串传递了1个参数,则svmax的值为1,并且检查精确格式索引值不超过1。如果攻击者提供了格式字符串,则不存在任何参数,这时svmax的值为0。
但是,精确格式索引也是带符号的32位整数,并且其值完全由攻击者提供的格式字符串控制。这意味着您可以将此参数数组索引设置为负值,这样也可以通过针对svmax的带符号上限检查。
了解这一点后,漏洞的利用就变得相当简单了。人们可以直接通过svargs数组索引指向任何指向攻击者控制的数据的指针。这种受攻击者控制的数据将被解释为参数结构,其中包含指向值字段的指针。与熟悉的%n格式说明符相结合,攻击者就能够对受控位置执行受控写入操作。使用这样的写原语,就可以覆盖任何可写进程内存的内容,而这些内容可以通过各种方式用于完整的过程控制中。
这是一个很好的例子,它为我们展示了Perl格式化实现的bug是如何转化为安全漏洞的。结合Webmin中的格式字符串漏洞,攻击者就能够对Webmin发动远程代码执行(RCE)攻击。
我们得出的结论是,即使在较高级语言级别上看起来似乎“只是一个bug”的问题,也需要对错误输入的较低级别处理进行深入的考察,因为它们很可能会转化为一个高危漏洞——即使人们曾经认为这样的问题实际上是不可利用的。
PHP解释器的无限潜力
从攻击者的角度来看,他们一直对PHP解释器的许多版本趋之若鹜。这是因为,攻击者通常可以从解释器控制角度和远程API输入角度对其发动攻击:对于前者,攻击者能够执行任意PHP代码;对于后者,攻击者可以向潜在易受攻击的PHP API提供恶意输入。
利用PHP解释器的一个更有趣的例子是反序列化攻击。因为攻击者曾经在PHP逻辑级别和核心解释器级别攻陷过PHP的反序列化API。
人们普遍认为,对不受信任的用户提供的数据进行反序列化是一个坏主意。在远程应用程序的上下文中,任意的对象反序列化可能会导致相对简单的PHP任意执行,这取决于应用程序命名空间中哪些类是可用的和允许的。这个主题超越了语言的界限,我们在几乎所有支持反序列化的语言和应用程序框架中都看到了同样的概念。
在攻击者实现任意PHP执行攻击后,他们可能会发现自己受到受限解释器配置的制约,这时他们通常会探索解除这些限制的方法。历史上流行的一种方法是滥用PHP解释器本身的bug。最近在 https://bugs.php.net/bug.php?id=76047中可以找到这类攻击的一个示例,其中可以利用debugbacktrace()函数中的释放后使用(UAF)漏洞来完全控制PHP解释器本身,并废除所有配置方面的限制。
有时,即使提供了受控的PHP反序列化原语,由于攻击者无法获悉哪些类是可用的,或因应用程序命名空间存在某些限制,而无法将其转化为任意PHP执行能力。这时,从上层下潜到较低的代码层,很可能就能找到突破口。
由于PHP的反序列化API中存在大量内存管理不善问题,因此,长久以来,它们一直都是模糊测试和解释器漏洞的热门研究目标。
通过利用反序列化API在解释器实现层面的内存管理不善问题,一个坚定的攻击者能够将一个原本不可利用的漏洞转变成一个完全可利用的漏洞。实际上,已经出现过许多通过该攻击面远程利用PHP应用程序的实际例子。
最近的一个例子出现在Ruslan Habolov撰写的一篇优秀的文章中,其中描述了他们如何利用低级PHP解释器错误和高级PHP API的交互,对一个著名的现实目标发动RCE攻击。
PHP反序列化在较高和较低级别实现的混合攻击面是解释型语言垂直攻击面的另一个很好的例子。
把C带入Python中:CVE-2014-1912漏洞
就本文而言,我们的第三个也是最后一个例子是CVE-2014-1912漏洞。这个漏洞存在于Python的socket.recvfrom_into函数中。
在Python2.5中引入的socket.recvfrom_into的预期用途是将数据接收到指定的Python字节数组中。但是,由于该函数缺乏明确的检查,所以无法确保接收数据的目标缓冲区的大小足以容纳指定数量的传入数据。
例如socket.recvfrom_into(bytearray(256),512)就会触发内存损坏问题。
后来,人们通过下面的代码对其进行了修复:
diff -r e6358103fe4f Modules/socketmodule.c --- a/Modules/socketmodule.c Wed Jan 08 20:44:37 2014 -0800 +++ b/Modules/socketmodule.c Sun Jan 12 13:21:19 2014 -0800 @@ -2877,6 +2877,14 @@ recvlen = buflen; } + /* Check if the buffer is large enough */ + if (buflen < recvlen) { + PyBuffer_Release(&pbuf); + PyErr_SetString(PyExc_ValueError, + "buffer too small for requested bytes"); + return NULL; + } + readlen = sock_recvfrom_guts(s, buf, recvlen, flags, &addr); if (readlen < 0) { PyBuffer_Release(&pbuf);
为了利用这个漏洞,应用程序必须显式使用一个大于目标字节数组长度的长度参数,向该字节数组中读入比分配给该数组的空间更多的数据。如果未提供长度参数,则该函数则默认使用目标字节数组本身的长度,因此,就不会发生内存损坏问题。
如果您使用的开发语言要求程序员自己负责内存管理的话,那么对于上述问题肯定不会陌生。您甚至可能认为,任何一个心智正常的人都不会做这样的蠢事。因为很明显,您不应该读取比目标缓冲区中可用的数据更多的数据,对吗?
这个案例的有趣之处就在这里:提供内存管理功能的编程语言的开发人员,通常会倾向信任该语言的实现。但是,当语言中存在诸如CVE-2014-1912之类的问题时,则可能会出现认知失调。
Python开发人员可能完全希望能够在Python解释器不受内存损坏的情况下使用s.recvfrom_into(bytearray(256), 512) 。实际上,如果您尝试这个打过补丁后的程序,它现在的表现就像您所期望的那样:
Traceback (most recent call last): File " ValueError: nbytes is greater than the length of the buffer >>>
所以,这个问题的重点在于,使用被认为是内存安全的语言实现的函数,竟然存在内存破坏漏洞。但对于一个C程序员来说,面对CVE-2014-1912漏洞,他们多半是这样理解的:“是的,就是这样工作的呀,难道不是吗?”
这给了我们一个教训,即使是在宣称内存安全的高级语言中,也绝对不要认为其内存管理绝对是安全的。当处理显式操作静态长度的可变缓冲区的API时,检查你的长度与你的缓冲区相匹配是永远不会有坏处的,即使在由于语言本身的原因不这样做也可能是安全的情况下也是如此。
从攻击者的角度来看,对于通常被认为是内存安全的API进行审计,常常会有意想不到的收获。
小结
作为关于隐藏的C/C++攻击面的系列文章的第一篇,我们已经探讨了几个实际的例子,为大家展示了内存安全的幻觉是如何麻痹开发人员,从而让他们放松对应用程序中接受的输入的警惕的。
本文探讨的漏洞的变体可能而且确实存在于所有解释器API中,这些API一般可以从更高级别访问,并且其核心是用内存不安全语言实现的。这些漏洞是否可被利用,通常取决于开发人员为攻击者提供了多大的回旋余地。
如果开发人员对输入类型、大小和值范围加以严格要求的话,通常能够挫败这种漏洞——例如,当接收到整数值时,将该值的范围显式地限制在应用程序上下文中有意义的范围内,而不是将其开放给变量类型本身的取值范围,这是一种防御性的编程习惯,对您将有很大的帮助。
在本系列的下一篇文章中,我们将深入研究解释型语言的现代C/C++攻击面,重点介绍流行解释型语言框架的第三方库生态系统,以及针对它们的新型攻击方法。
本文翻译自:https://securitylab.github.com/research/now-you-c-me如若转载,请注明原文地址: