Use-after-free in php_curl related to CURLOPT_FILE/_INFILE/_WRITEHEADER

Disclosed: 2015-04-14 00:00:00 By mongo To ibb
Unknown
Vulnerability Details
https://bugs.php.net/bug.php?id=69316 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); ?> ```
Actions
View on HackerOne
Report Stats
  • Report ID: 73246
  • State: Closed
  • Substate: resolved
  • Upvotes: 3
Share this report