【技术分享】如何利用Yahoo!邮件中的隐私图像泄露漏洞拿下1万4千美元奖金

http://p2.qhimg.com/t01eac30475d3158852.jpg

翻译:興趣使然的小胃

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


一、前言

滴血攻击现在是个非常热门的话题,这类漏洞中最引人注目的就是心脏滴血(Heartbleed)漏洞以及云滴血(Cloudbleed)漏洞。在这两个漏洞中,服务端代码中存在的越界读取漏洞都会导致服务器的私密内存数据被返回给客户端。这些漏洞会泄露服务端进程内存空间中的敏感信息,比如密钥、令牌、cookies等。此外,最近微软图像库的客户端实现中还存在滴血漏洞,该漏洞会通过Internet Explorer泄露敏感信息。滴血攻击之所以引人关注,原因在于它们不受大多数沙盒的影响,并且利用方式也相对容易。

本文介绍的是一个Yahoobleed #1(YB1)漏洞,我们可以利用这个漏洞,从Yahoo!服务器中窃取其他用户的Yahoo!邮件中的隐私图像附件。

YB1利用的是我之前在ImageMagick图像处理软件中发现的一个0-day漏洞。现在这个漏洞已经是一个1-day漏洞了,因为我已经将其上报给ImageMagick,同时提交了一行修复代码来解决这个问题。目前这个漏洞的编号为CESA-2017-0002。

之前滴血类漏洞通常都是越界读取漏洞,但本文分析的这个漏洞是由于未初始化内存的使用所引起的。在这个案例中,程序以某个未初始化的图像解码缓冲区为基础,来渲染某张图片并返回给用户,这将导致服务端内存泄露。这类漏洞与越界读取漏洞相比更为隐蔽,因为服务器永远不会发生崩溃。然而,所泄露的隐私数据会受限于已被释放的堆块中的数据。


二、Yahoo!的回复

Yahoo!对这个漏洞的回复是我见过的最为完美的回复,具体如下:

1、Yahoo!有个漏洞奖金项目,用来鼓励并回馈漏洞安全研究,同时也与黑客保持积极的联系等等。

2、一旦收到漏洞报告时,他们自己会有一个90天的回复期限,这是非常进步的一点,另一个反例就是微软,微软偶尔会将合理的披露期限变成毫无意义的缠斗过程。

3、该漏洞的确在90天内就被修补了。

4、沟通过程非常顺畅,即使我发送了太多的ping请求他们也不厌其烦。

5、漏洞的修复非常彻底:他们直接弃用了ImageMagick。

6、他们给我发放了1万4千美金的奖励(我结合另外一个漏洞获得了这些奖金),平均每个字节价值778美金,非常棒。

7、我将所有奖金捐献给了慈善机构。确认这一信息后,Yahoo!接受了相关建议,将奖金提高到了28,000美金。

8、事了拂衣去,深藏功与名。


三、漏洞示例

在漏洞示例中,我们所使用的攻击方法就是将某个18字节的漏洞利用文件(或者变量)作为Yahoo!邮件附件发送给我自己,然后在收到的邮件中点击这张图像,运行图片预览面板。最终浏览器收到的图像对应的是一段未初始化、或者之前已经被释放的内存数据。

请注意:对于下文所使用的三张图片,我通过各种变换抹去了图片中存在的信息熵,且原始图片已经被销毁。

在图片#1中,你可以看到被黑圈围绕的字母A。想象一下,当你正在调试一个自认为非常无趣的漏洞时,在返回的图像中突然出现这类信息会有什么感觉?如果假设内存中原始的图像不包含重复信息,那么在复原的图像中重复出现这类信息有多种可能的原因。

最为明显的一个原因就是,内存中的图像大小可能与我们未初始化的画布(canvas)尺寸不匹配,对这个例子来说,我们使用的画布尺寸为1024×1024。根据内存中图像的不同存储方式,尺寸不匹配将导致图像重复以及(或者)偏移,这两种现象都可以在这个示例中找到。

此外,内存中图像的表示方式可能与颜色空间(colorspace)、颜色空间通道顺序或者Alpha通道状态(为“是”或者“否”状态)不一致。这种情况下,缩略图解码及重新编码管道将在执行过程中在内存中留下各种不同的衍生品。

请不要怀疑我们能否正确恢复原始的图像,但这并非是我们的目标。

http://p4.qhimg.com/t0133059a14d39d7757.png

       在图片#2中,你仍然有可能找出图片中残存的人脸痕迹,也许是额头、鼻子、前额甚至颚骨?有可能什么都找不到,因为我已经去掉了图片中的信息熵。但当你从这张图片中发现一张随机的脸时,你就会感觉到问题的严重性。当我遇到这种情况时,我删除了导致未初始化内存漏洞的所有文件,并报告了该漏洞。

http://p2.qhimg.com/t018faa34f4685fd2d4.png

       在图片3#中,图像中的颜色得以保留,因为这张图片中有某些垂直的彩色线条,这些颜色包括品红色、青色以及黄色。究竟内存中的哪个结构会导致这种现象发生呢?我并不了解。最开始我认为这是某些CMYK颜色空间的外在表现,但随后我否定了这种想法。JPEG文件通常会在YCbCr颜色空间中进行编码,因此这种现象可能与某个编码或解码的JPEG图片有关。

http://p6.qhimg.com/t01aa602e66d0f4214b.png

四、漏洞分析

这个漏洞隐藏于RLE(Utah Raster Toolkit Run Length Encoded)图片格式中,我之前在某篇分析box.com内存损坏漏洞的文章中也提到过这个漏洞。我们可以在coders/rle.c源码中,找到与新的ImageMagick 0-day漏洞有关的代码片段:

    pixel_info_length=image->columns*image->rows*
      MagickMax(number_planes_filled,4);
    pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
    if ((flags & 0x01) && !(flags & 0x02))
      {
[...]
        /*
          Set background color.
        */
        p=pixels;
        for (i=0; i < (ssize_t) number_pixels; i++)
        {
[...]
            {
              for (j=0; j < (ssize_t) (number_planes-1); j++)
                *p++=background_color[j];
              *p++=0;  /* initialize matte channel */
            }
        }
      }
    /*
      Read runlength-encoded image.
    */
[...]
    do
    {
      switch (opcode & 0x3f)
[...]
      opcode=ReadBlobByte(image);
    } while (((opcode & 0x3f) != EOFOp) && (opcode != EOF));

这是一个相当狡猾的漏洞,因为这段代码比较抽象,同时造成漏洞的原因并不是某行代码存在漏洞,而是因为代码片段中没有包含某行必要的代码。代码的逻辑处理流程大致如下:

1、为解码图像分配大小合适的画布。需要注意的是,调用GetVirtualMemoryBlob函数并不能保证我们获得一段被零填充的内存,你可能认为这个函数内部使用的是mmap()函数,然而它内部使用的却是malloc()。

2、根据图像头部中的某些标志,程序会决定是否将画布初始化为背景颜色。

3、在一个循环中迭代RLE协议命令,这些命令可能很长,也可能为空。

4、在这个代码片段之后,解码用的画布将会被转移到ImageMagick管道中,以便进行后续处理。

正如你现在看到的那样,攻击者可以构造一个包含特定头部标志的RLE图像,使程序不对画布进行初始化处理,紧随其后的是一个包含空白RLE协议命令的列表。这将导致程序在解码图像后,使用某个未经初始化处理的画布。我们可以构造一个RLE文件完成攻击过程,这个文件只有18字节大小!

http://p0.qhimg.com/t013d22a4cbc0b33b06.png

18字节具体解释如下:

52 CC:       头部
00 00 00 00: 左上顶点位于0x0.
00 04 00 04: 图像尺寸为1024 x 1024
02:          0x02标志(不需要背景色)
01:          1个平面(即灰度图像)
08:          每个样本8比特
00 00 00:    没有颜色映射, 颜色表长度为0,使用填充
07:          图像结尾(在预循环中消耗了一个协议命令)
07:          图像结尾(解码循环真正结束)

有几个字节对漏洞利用实验来说比较重要:平面(plane)的数量(该数量可以决定图像是灰度还是彩色图像)以及图像尺寸,图像尺寸决定了经过malloc()分配的未初始化内存块的大小。


五、漏洞利用

漏洞利用是最为有趣的部分。以下是影响漏洞利用的一些关键因素,这些因素包含通用因素以及Yahoo!专有的因素:

1、解码器锁定。Yahoo!似乎没有实现任何形式的白名单机制,以确保只有正确的ImageMagick解码器才能正常工作。对现代的Web而言,RLE并不是一个合适的解码器。任何正在使用ImageMagic的相关人士都应该锁定解码器列表,只保留真正需要的那些解码器。

2、沙盒。正如前文提到的,沙盒对这类漏洞利用不会造成太多影响。尽管区别不大,我猜测存在问题的服务器最有可能使用的是Perl和PerMagick,同时也在处理各种各样的网络流量,这意味着如果不经过大量的重构处理,服务器无法应用一个非常严格的沙盒策略。

3、进程隔离。这个问题至关重要。在ImageMagick的大多数使用场景中,滴血漏洞通常是无害的,因为一般情况下,攻击者只能通过这类漏洞,启动ImageMagic的convert程序,从而发起缩略图请求,每次发起一次缩略图请求都需要重新启动一次convert程序。这意味着被漏洞利用的进程内存空间中只可能包含攻击者自身的图像数据,使得漏洞利用十分鸡肋(但对Box以及DropBox而言情况有所不同,可以参考我之前的那篇文章)。

用来处理缩略图的Yahoo!邮件进程按照某种不常规的方式来处理缩略图请求,这个进程的生存周期非常长,并且会接受许多不同用户的图像处理请求。因此,这个内存数据泄露漏洞对Yahoo!而言就显得非常严重。

4、堆实现方式。程序所使用的堆实现方式是非常重要的一个因素。正如前面分析的那个RLE文件,画布所分配的空间为1024 x 1024 x 4,也就是4MB大小。这个大小对缓冲区分配而言是非常大的,比如,x86_64架构的Linux系统对默认的malloc()调用做了些调整,使用mmap()来满足大型缓冲区的分配,该函数会在分配的内存中填充0,不会导致敏感内存数据泄露。然而,我们的确在Yahoo!的大型缓冲区分配上下文中看到泄露的堆数据,因此Yahoo!肯定没有使用默认的堆实现方式。通过堆数据泄露,我们可以通过指纹信息进一步分析堆的实现方式。也许我们会发现服务器使用的是tcmalloc或者jemalloc,许多大型服务商都会使用这两种分配器。

5、缩略图尺寸。我们有必要对缩略图尺寸做个简要的说明。如果我们构造的未初始化画布在生成较小的缩略图时尺寸会变小,那么这种情况下原始泄露的内存数据就会有部分信息丢失,这样一来漏洞利用肯定会受到影响。然而,Yahoo!邮件的预览面板看上去支持非常大尺寸的图像(经测试支持2048×2048尺寸),因此我们不必担心这一点。

6、缩略图压缩。这一点比较有趣。Yahoo!邮件会以JPEG图像形式返回缩略图和预览图像。正如我们已知的,JPEG是一个有损压缩算法。如果我们只是想从Yahoo!服务器是获取图像数据,那么这不是一个大问题。然而如果我们对Yahoo!服务器内存中的原始字节感兴趣,那么有损压缩算法会导致我们丢失数据。

举个例子,我尝试过从内存转储数据中提取具体的指针值(也许我们希望能够以远程方式绕过ASLR的限制)。我使用的是灰度图像,因为对于JPEG压缩算法来说,对比亮度数据而言,它对颜色数据的压缩强度更大。但在泄露的16个字节大小的数据中,我发现了类似0x020081c5b0be476f以及0x00027c661ac2722a形式的指针值。你可能猜到这些指针与x86_64架构的Linux系统用户空间的指针(其格式形如0x00007f….)有关系,但显然这里我们丢失了很多数据。

我想还是有办法能够解决这个问题的。最有可能的一种情况是,某些缩略图端点能够返回(或者通过某些URL参数要求这些端点返回)无损的PNG图像,这种图像就不会造成泄露信息丢失。当然更有勇气的一种办法是通过JPEG压缩算法进行数学建模,一劳永逸解决这个问题。


六、结论

从广义上讲,在现在这个世界中,内存损坏漏洞越来越少,沙盒机制应用越来越广,在这种条件下,我们可以利用滴血漏洞从服务器上轻松窃取敏感信息。

从设计上讲,使用生存周期长的进程,并将其与ImageMagick关联起来不是一个好的设计思路,因为这样做可能会使漏洞的影响更加严重。在处理任何不可信的输入时,将ImageMagick的解码器限制到某个最小集合是非常必要的一件事情。

让我们再次看看GraphicsMagick与ImageMagick的对比结果。在2016年3月发布的1.3.24版本中,GraphicsMagick引入了一个名为“对coders/rle.c:633:39中越界读取的漏洞进行修复”的代码变动集,修复了这个漏洞。这部分代码也是GraphicsMagick和ImageMagick中大量漏洞的集散地。GraphicsMagick和ImageMagick之间缺乏漏洞的协同修复机制。GraphicsMagick直到2016年3月才修复了在我之前的博客中提到的那个RLE内存损坏漏洞,而ImageMagick早在2014年12月就修复了这个漏洞。

对那些集成了ImageMagick服务的产品(如Linux发行版)而言,这个漏洞现在属于1-day漏洞,因为这些产品还没有打上对应的补丁(需要注意的是,由于某些原因,Ubuntu 16,04 LTS中完全禁用了RLE解码器)。

但在结论部分,我想重点强调在这个生态系统中有关责任方的一些问题:

1、研究人员的责任:研究人员的具体职责是什么?对于某个知晓软件漏洞的研究人员来说,我认为他们有责任将具体问题立刻通知软件所有者。假设厂商在合理的时间内对漏洞做了修复,那么一切结束了吗?对下游的用户而言,很有可能问题还没有得到解决。那么我们是否有责任通知每个受到影响的云服务提供商呢?我认为没必要这样做,我们只需要向一个或两个有漏洞奖励机制同时具备积极反馈渠道的服务商提交漏洞即可,但有些更广泛的问题仍然存在。

2、上游厂商的责任:除了简单地标识某个漏洞(假设该漏洞还没有被规范地赋予编号)之外,上有厂商如何妥善响应某个安全报告,并且及时修复这个漏洞?就个人而言,我认为厂商应该具备某种形式的畅通的通知渠道(例如通过邮件列表、网站上设立公告区等),同时他们也需要承担获取CVE编号的责任。

3、消费者的责任:对诸如Yahoo!、Box、DropBox以及Ubuntu等的消费者而言,他们的责任在哪?如果上有厂商做得足够好,那么消费者所属的安全团队只需要订阅相同的权威来源,在厂商每次发布新的公告时就第一时间采取行动即可(当然这是一个理想的场景,因为Box以及Yahoo!似乎都在运行存在漏洞的ImageMagick)。


(完)