MOPS-2010-060: PHP Session Serializer Session Data Injection Vulnerability

May 31st, 2010

PHP’s default sesson serializer wrongly handles the PS_UNDEF_MARKER character

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.

Detailed information

The default session deserializer knows two special characters PS_DELIMITER and PS_UNDEF_MARKER. The first is used to separate stored session variables and the later marks undefined session variables. The following code is the implementation of this.

    while (p < endptr) {
        zval **tmp;
        q = p;
        while (*q != PS_DELIMITER) {
            if (++q >= endptr) goto break_outer_loop;
        }
        if (p[0] == PS_UNDEF_MARKER) {
            p++;
            has_value = 0;
        } else {
            has_value = 1;
        }

        namelen = q - p;
        name = estrndup(p, namelen);
        q++;

        if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
            if ((Z_TYPE_PP(tmp) == IS_ARRAY && Z_ARRVAL_PP(tmp) == &EG(symbol_table)) || *tmp == PS(http_session_vars)) {
                goto skip;
            }
        }

        if (has_value) {
            ALLOC_INIT_ZVAL(current);
            if (php_var_unserialize(&current, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) {
                php_set_session_var(name, namelen, current, &var_hash  TSRMLS_CC);
            }
            zval_ptr_dtor(&current);
        }
        PS_ADD_VARL(name, namelen);
skip:
        efree(name);

        p = q;
    }

The problem with this code is that the session serializer does only handle the PS_DELIMITER correctly and does not handle the PS_UNDEF_MARKER at all.

PS_ENCODE_LOOP(
        smart_str_appendl(&buf, key, key_length);
        if (memchr(key, PS_DELIMITER, key_length)) {
            PHP_VAR_SERIALIZE_DESTROY(var_hash);
            smart_str_free(&buf);
            return FAILURE;
        }
        smart_str_appendc(&buf, PS_DELIMITER);

        php_var_serialize(&buf, struc, &var_hash TSRMLS_CC);
    } else {
        smart_str_appendc(&buf, PS_UNDEF_MARKER);
        smart_str_appendl(&buf, key, key_length);
        smart_str_appendc(&buf, PS_DELIMITER);
);

The problem is that session variable names starting with PS_UNDEF_MARKER will confuse the session deserializer. This means any PHP code that allows to write arbitrary variables into the session can be used to inject arbitrary serialized values into the session. This also means that code like the following is equal to unserializing user input.

<?php
   session_start();
   $_SESSION[$_POST['prefix'] . 'bla'] = $_POST['data'];
?>

Proof of concept, exploit or instructions to reproduce

The following two code examples are both vulnerable to session injection.

<?php
   session_start();
   $_SESSION[$_POST['prefix'] . 'bla'] = $_POST['data'];
?>

or also

<?php
   session_start();
   $_SESSION = array_merge($_SESSION, $_POST);
?>

By sending a POST request with prefix=! and data=|xxx|O:10:"evilObject":0:{} it is possible to inject arbitrary serialized data into the session. Keep in mind that this also allows all the attacks usually possible when putting user input into unserialize().

Notes

This vulnerability can be fixed by correctly handling the PS_UNDEF_MARKER char.




blog comments powered by Disqus