php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #71331 Uninitialized pointer in phar_make_dirstream()
Submitted: 2016-01-10 17:51 UTC Modified: 2016-04-28 16:59 UTC
From: manhluat at vnsecurity dot net Assigned: stas (profile)
Status: Closed Package: PHAR related
PHP Version: 5.6.17 OS: Linux, Mac
Private report: No CVE-ID: 2016-4343
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: manhluat at vnsecurity dot net
New email:
PHP Version: OS:

 

 [2016-01-10 17:51 UTC] manhluat at vnsecurity dot net
Description:
------------
There is a bug in phar_make_dirstream, when it tries to parse malicious ".tar" file.

The problem starts here:

tar.c

345		if (!last_was_longlink && hdr->typeflag == 'L') {
			last_was_longlink = 1;
			/* support the ././@LongLink system for storing long filenames */
			entry.filename_len = entry.uncompressed_filesize;

You can see that if hdr->typeflag == 'L', it will parse input .tar file as longlink filename and entry.filename_len will be assigned with value of entry.uncompressed_filesize (which is retrieved from .tar file also).

Assume that I can set entry.filename_len=entry.uncompressed_filesize=0.

And a bit later, it will call phar_make_dirstream().

===========================

dirstream.c

static php_stream *phar_make_dirstream(char *dir, HashTable *manifest TSRMLS_DC) /* {{{ */
{
...
char *entry, *found, *save, *str_key;uint keylen;// Uninitialized variables
...

201	while (FAILURE != zend_hash_has_more_elements(manifest)) {
		if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key_ex(manifest, &str_key, &keylen, &unused, 0, NULL)) {
			break;
		}
..
216 if (keylen >= sizeof(".phar")-1 && !memcmp(str_key, ".phar", sizeof(".phar")-1)) {
...



=======================

zend_hash.c

1068 ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos)
{
	Bucket *p;

	p = pos ? (*pos) : ht->pInternalPointer;

	IS_CONSISTENT(ht);

	if (p) {
		if (p->nKeyLength) {
			if (duplicate) {
				*str_index = estrndup(p->arKey, p->nKeyLength - 1);
			} else {
				*str_index = (char*)p->arKey;
			}
			if (str_length) {
				*str_length = p->nKeyLength;
			}
			return HASH_KEY_IS_STRING;
		} else {
			*num_index = p->h;
			return HASH_KEY_IS_LONG;
		}
	}
	return HASH_KEY_NON_EXISTENT;
}

As you can see above, if p->nKeyLength is zero (entry.filename_len), it will try to parse current bucket in hashtable as HASH_KEY_IS_LONG. Ok get back to dirstream.c

After zend_hash_get_current_key_ex() is called, str_key/key_len will be exactly the old value if the current_hash is INT value, zend_hash_get_current_key_ex() will just assign the 3rd value as a longint value.

The problem is here...

The 201th line in dirstream.c doesn't check whether return type of zend_hash_get_current_key_ex() is STRING or INT. (as you see, it just check if it is HASH_KEY_NON_EXISTENT or not). And use it as string pointer in further (216th line).

So attacker,by somehow, he could control the stack frame at the moment before calling zend_hash_get_current_key_ex() then he could take over these variables (str_key/key_len) and it could lead to some evil scenario.

PoC works with PHP latest version (php-5.6.17) in Linux and Mac as well.

PoC can be downloaded at http://l4w.io/files/PHP-PoC/93aa2719f76a916b118fa77c62981f5f/PoC/

* How to fix:
I think we should check return type of zend_hash_get_current_key_ex() in 
phar_make_dirstream() is whether STRING or LONGINT.
And check entry.filename_len>0 when parsing longlink filename also.

Test script:
---------------
<?php
new PharData($argv[1]);
?>

Expected result:
----------------
Program received signal SIGSEGV, Segmentation fault.

Actual result:
--------------
gdb-peda$ r ./PoC/poc.php ./PoC/000.tar 
Starting program: /root/test/php-5.6.17/sapi/cli/php ./PoC/poc.php ./PoC/000.tar

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x16 
EBX: 0x8804000 --> 0x8803dd8 --> 0x1 
ECX: 0x5 
EDX: 0x86fbc93 (".phar")
ESI: 0xf7bd9254 --> 0x8 
EDI: 0xf7d5a75f (<__memcpy_ssse3_rep+47>:       add    ebx,0x37561)
EBP: 0xf7bd94d8 --> 0xf7bd002f --> 0x0 
ESP: 0xffff9c78 --> 0x8804000 --> 0x8803dd8 --> 0x1 
EIP: 0xf7d68fd0 (<__memcmp_sse4_2+64>:  mov    bl,BYTE PTR [eax])
EFLAGS: 0x10293 (CARRY parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7d68fc7 <__memcmp_sse4_2+55>:     add    ebx,DWORD PTR [ebx+ecx*4]
   0xf7d68fca <__memcmp_sse4_2+58>:     jmp    ebx
   0xf7d68fcc <__memcmp_sse4_2+60>:     lea    esi,[esi+eiz*1+0x0]
=> 0xf7d68fd0 <__memcmp_sse4_2+64>:     mov    bl,BYTE PTR [eax]
   0xf7d68fd2 <__memcmp_sse4_2+66>:     cmp    bl,BYTE PTR [edx]
   0xf7d68fd4 <__memcmp_sse4_2+68>:     jne    0xf7d6901f <__memcmp_sse4_2+143>
   0xf7d68fd6 <__memcmp_sse4_2+70>:     mov    bl,BYTE PTR [eax+0x1]
   0xf7d68fd9 <__memcmp_sse4_2+73>:     cmp    bl,BYTE PTR [edx+0x1]
[------------------------------------stack-------------------------------------]
0000| 0xffff9c78 --> 0x8804000 --> 0x8803dd8 --> 0x1 
0004| 0xffff9c7c --> 0x8169a4c (<phar_make_dirstream+988>:      mov    ecx,DWORD PTR [esp+0x30])
0008| 0xffff9c80 --> 0x16 
0012| 0xffff9c84 --> 0x86fbc93 (".phar")
0016| 0xffff9c88 --> 0x5 
0020| 0xffff9c8c --> 0xffff9cc8 --> 0x1505 
0024| 0xffff9c90 --> 0x0 
0028| 0xffff9c94 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
__memcmp_sse4_2 () at ../sysdeps/i386/i686/multiarch/memcmp-sse4.S:114
114     ../sysdeps/i386/i686/multiarch/memcmp-sse4.S: No such file or directory.
gdb-peda$ bt
#0  __memcmp_sse4_2 () at ../sysdeps/i386/i686/multiarch/memcmp-sse4.S:114
#1  0x08169a4c in phar_make_dirstream (dir=0xf7bd94d8 "/", manifest=0xf7bd9254) at /root/test/php-5.6.17/ext/phar/dirstream.c:216
#2  0x08169e14 in phar_wrapper_open_dir (wrapper=0x8805220 <php_stream_phar_wrapper>, path=0xf7bd9448 "phar:///root/test/PoC/000.tar/", mode=0x87241b8 "r", options=0x0, opened_path=0x0, context=0x0)
    at /root/test/php-5.6.17/ext/phar/dirstream.c:357
#3  0x08268dae in _php_stream_opendir (path=path@entry=0xf7bd9448 "phar:///root/test/PoC/000.tar/", options=options@entry=0x8, context=0x0) at /root/test/php-5.6.17/main/streams/streams.c:1986
#4  0x081bdf88 in spl_filesystem_dir_open (intern=intern@entry=0xf7bd9f58, path=0xf7bd9448 "phar:///root/test/PoC/000.tar/") at /root/test/php-5.6.17/ext/spl/spl_directory.c:250
#5  0x081c18af in spl_filesystem_object_construct (ht=ht@entry=0x2, return_value=return_value@entry=0xf7bd8a30, return_value_ptr=return_value_ptr@entry=0xffff9f4c, this_ptr=this_ptr@entry=0xf7bd8a68, 
    return_value_used=return_value_used@entry=0x1, ctor_flags=ctor_flags@entry=0x1) at /root/test/php-5.6.17/ext/spl/spl_directory.c:731
#6  0x081c1977 in zim_spl_RecursiveDirectoryIterator___construct (ht=0x2, return_value=0xf7bd8a30, return_value_ptr=0xffff9f4c, this_ptr=0xf7bd8a68, return_value_used=0x1) at /root/test/php-5.6.17/ext/spl/spl_directory.c:1597
#7  0x082a3fc1 in zend_call_function (fci=fci@entry=0xffff9f7c, fci_cache=fci_cache@entry=0xffff9f68) at /root/test/php-5.6.17/Zend/zend_execute_API.c:847
#8  0x082cc101 in zend_call_method (object_pp=object_pp@entry=0xffffa02c, obj_ce=<optimized out>, fn_proxy=fn_proxy@entry=0x88a3c84, function_name=function_name@entry=0x836d76a "__construct", 
    function_name_len=function_name_len@entry=0xb, retval_ptr_ptr=retval_ptr_ptr@entry=0x0, param_count=param_count@entry=0x2, arg1=arg1@entry=0xffffa030, arg2=arg2@entry=0xffffa040)
    at /root/test/php-5.6.17/Zend/zend_interfaces.c:97
#9  0x081754e1 in zim_Phar___construct (ht=0x1, return_value=0xf7bd8a4c, return_value_ptr=0xf7bbf088, this_ptr=0xf7bd8a68, return_value_used=0x0) at /root/test/php-5.6.17/ext/phar/phar_object.c:1255
#10 0x083615c0 in zend_do_fcall_common_helper_SPEC (execute_data=<optimized out>) at /root/test/php-5.6.17/Zend/zend_vm_execute.h:558
#11 0x082f1446 in execute_ex (execute_data=execute_data@entry=0xf7bbf0c4) at /root/test/php-5.6.17/Zend/zend_vm_execute.h:363
#12 0x0835f272 in zend_execute (op_array=0xf7bd8f58) at /root/test/php-5.6.17/Zend/zend_vm_execute.h:388
#13 0x082b4c1e in zend_execute_scripts (type=type@entry=0x8, retval=retval@entry=0x0, file_count=file_count@entry=0x3) at /root/test/php-5.6.17/Zend/zend.c:1341
#14 0x0824ef3e in php_execute_script (primary_file=primary_file@entry=0xffffc438) at /root/test/php-5.6.17/main/main.c:2597
#15 0x08363473 in do_cli (argc=argc@entry=0x3, argv=argv@entry=0x8820878) at /root/test/php-5.6.17/sapi/cli/php_cli.c:994
#16 0x08063f04 in main (argc=0x3, argv=0x8820878) at /root/test/php-5.6.17/sapi/cli/php_cli.c:1378
#17 0xf7c40a83 in __libc_start_main (main=0x80639f0 <main>, argc=0x3, argv=0xffffd744, init=0x836c520 <__libc_csu_init>, fini=0x836c590 <__libc_csu_fini>, rtld_fini=0xf7feb180 <_dl_fini>, stack_end=0xffffd73c) at libc-start.c:287
#18 0x08063f8a in _start ()

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-15 07:00 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-01-15 07:00 UTC] stas@php.net
Fix in https://gist.github.com/smalyshev/853621a4627e19e57f2c and security repo commit 4c2424eb24b0178456acc404dbfff528cdc44197
 [2016-02-02 03:57 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-02-02 03:57 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.


 [2016-02-02 04:46 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=4c2424eb24b0178456acc404dbfff528cdc44197
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 [2016-02-05 04:36 UTC] manhluat at vnsecurity dot net
Looks like i cant see this on the latest patches :D
 [2016-02-05 05:00 UTC] stas@php.net
Please explain - what don't you see in latest patches?
 [2016-02-07 05:47 UTC] manhluat at vnsecurity dot net
Ah, I'm sorry. I just didnt see it on http://php.net/ChangeLog-5.php
But the patches still exists in commit.
 [2016-03-07 13:54 UTC] manhluat at vnsecurity dot net
hello folks,

Can we assign a CVE for this ? :)
 [2016-04-28 16:59 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2016-4343
 [2016-05-02 14:51 UTC] jpauli@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9649ca1630433473a307d015ba1a79a4a7a779f5
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 [2016-05-25 00:21 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9649ca1630433473a307d015ba1a79a4a7a779f5
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 [2016-05-25 03:51 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9649ca1630433473a307d015ba1a79a4a7a779f5
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 [2016-05-25 03:52 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9649ca1630433473a307d015ba1a79a4a7a779f5
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 [2016-05-25 03:53 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9649ca1630433473a307d015ba1a79a4a7a779f5
Log: Fixed bug #71331 - Uninitialized pointer in phar_make_dirstream()
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Thu Jan 30 19:01:29 2025 UTC