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.
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(¤t, (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(¤t);
}
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.
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.
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.
session_start();
$_SESSION[$_POST['prefix'] . 'bla'] = $_POST['data'];
?>
or also
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.







