|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[2016-01-15 07:00 UTC] stas@php.net
-Assigned To:
+Assigned To: stas
[2016-01-15 07:00 UTC] stas@php.net
[2016-02-02 03:57 UTC] stas@php.net
-Status: Assigned
+Status: Closed
[2016-02-02 03:57 UTC] stas@php.net
[2016-02-02 04:46 UTC] stas@php.net
[2016-02-05 04:36 UTC] manhluat at vnsecurity dot net
[2016-02-05 05:00 UTC] stas@php.net
[2016-02-07 05:47 UTC] manhluat at vnsecurity dot net
[2016-03-07 13:54 UTC] manhluat at vnsecurity dot net
[2016-04-28 16:59 UTC] remi@php.net
-CVE-ID:
+CVE-ID: 2016-4343
[2016-05-02 14:51 UTC] jpauli@php.net
[2016-05-25 00:21 UTC] stas@php.net
[2016-05-25 03:51 UTC] stas@php.net
[2016-05-25 03:52 UTC] stas@php.net
[2016-05-25 03:53 UTC] stas@php.net
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 09:00:01 2025 UTC |
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 ()