One Security Fix Introduces Another
Today, Stefan Esser (@i0n1c) reported a critical remotely exploitable vulnerability in PHP 5.3.9 (update assigned CVE-2012-0830). The funny thing is that this vulnerability was introduced in the fix for the hash collision DOS (CVE-2011-4885) reported in December.
The Vulnerable Fix
The fix to prevent hash collisions introduces a new configuration property in php.ini called
max_input_vars
This configuration element limits the number of variables that can be used in a request (e.g. http://request.com/foo.php?a=1&b=2&c=3). The default is set to 1000.
The changes were made to php_variables.c in the function php_register_variable_ex.
PHP starts off by “registering” all variables in a request through this function
if (sapi_module.input_filter(PARSE_POST, var, &val, val_len, &new_val_len TSRMLS_CC)) {
php_register_variable_safe(var, val, new_val_len, array_ptr TSRMLS_CC);
}
Which in turn calls the vulnerable function
PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array TSRMLS_DC)
{
...tons of code removed...
if (is_array) {
... code removed ...
if (zend_hash_num_elements(symtable1) <= PG(max_input_vars)) {
if (zend_hash_num_elements(symtable1) == PG(max_input_vars)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variables exceeded %ld. To increase the limit change max_input_vars in php.ini.", PG(max_input_vars));
}
MAKE_STD_ZVAL(gpc_element);
array_init(gpc_element);
zend_symtable_update(symtable1, escaped_index, index_len + 1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p);
}
... some code removed...
symtable1 = Z_ARRVAL_PP(gpc_element_p);
... tons more code removed ...
}
The vulnerability happens when the number of variables exceeds max_input_vars and the variable is an array variable (if (*p == ‘[')). Instead of an else case which would stop and return, the code is allowed to continue executing. The code continues to execute up to line 207, the second highlighted line. At line 207, it is calling a macro Z_ARRVAL_PP to get a reference to the updated hashtable. This is where the code execution can occur.
If we look a little further down, gpc_element is eventually assigned the value of the variable.
plain_var: MAKE_STD_ZVAL(gpc_element); gpc_element->value = val->value;
When the code reaches the point where the number of variables is greater than max input, gpc_element_p will point to the previous variable value. Z_ARRVAL_PP will expect the value to be a ZVAL struct with a hashtable (usually initialized by array_init(...)) but that memory hasn't been initialized in the case were the previous value was a non-array (plain) variable.
The Ironic Part
The most ironic thing about all of this is that because this fix was for a security vulnerability, it was backported and applied more likely than a regular bug fix or update.
Update 1: PoC
A very nice PoC by Paul Westin
Update 2: Fixed Released
PHP 5.3.10 released - UPDATE NOW!

Hey, wanna hear a joke?
PHP.
Pingback: Новости компьютерного мира - Критическая уязвимость в PHP 5.3.9, позволяющая выполнить код на сервере
Even more ironic: the proposed fix isn’t actually very good. Input variables are not the only place where PHP uses hash tables, and hash tables are exploitable in general, not just in this one case. See http://www.youtube.com/watch?v=0JgeiZ5FAJw for a good presentation on the issue.
I vaguely remember some of Esser’s exploits on this topic….
So how does this happen exactly?
You should force some memory to be free() d then allocated again and at some point should be a pointer to the destructor of that zval which holds the array?
Or I’m way off this……
Thanks
You’re way off. You control the flow of the code such that when the number of input variables exceeds max_input_vars and the last variable is an array, Z_ARRVAL_PP will access data supplied by the request from the previous variable’s value.
So basically this is about treating a string variable as an array right?
Because if it finds a variable with the same name which is not an array as in:
if (zend_symtable_find(symtable1, escaped_index, index_len + 1, (void **) &gpc_element_p) == FAILURE
193 || Z_TYPE_PP(gpc_element_p) != IS_ARRAY) {
then it will go to:
symtable1 = Z_ARRVAL_PP(gpc_element_p);
which can access a string as an array, right?
Yep and since you control the value of that string variable, you can construct a fake array (a HashTable) value (e.g. a=\x00\x00\x00\x00…a[]=0). There is a really nice PoC for exploiting it here.
very nice poc, thanks for the link!
I made a number of updates to the post – it should be more clear now.
so Z_ARRVAL_PP will be applied on the previous value which is a string?…hmmm…i’ll go through the code again, i’m not really familiar with this part of php core……
Any hint/documentation which would help me with this part of php core is appreaciated!
Thanks
Even more ironic, the hacker’s ethos of giving the makers of the exploited software a chance to repair it is thrown away in favor of ego and arrogance. You’re soooo smart, and that’s what is important. Here’s the thing guys: you report the hack, you wait for fix, if the fix doesn’t happen you release the hack and make fun of the makers (be it PHP, Microsoft, Linux or whatever you hate or love). Do it like this, and the one not looking pretty is you. Really.
Actually the bug was reported 3 weeks ago. Stefan Esser reported the vulnerability and the fix was committed before this was published.
code is different with this patch:
https://github.com/laruence/laruence.github.com/blob/master/php-5.2-max-input-vars/php-5.2.10-max-input-vars.patch
Pingback: Новости компьютерного мира - Критическая уязвимость в PHP, позволяющая выполнить код на сервере. Вышел релиз PHP 5.3.10
I’m seriously disappointed by PHP …
First the stuff with the version 5.3.7 and now that with 5.3.9 not pointing at the security vulnerability fixed in 5.3.10 but more that they’re not searching for a good solution.
But as mentioned in the presentation linked above .. maybe if someone provides a working fix, that really fixes the issue, it will be applied to the repository.
I saw that PHP is available at github and has 47 forges. Maybe someone will introduce a good bugfix here.
Sweet. This was already reported to the php team 3 weeks ago. See http://bugs.php.net/60708
Pingback: PoC-exploit för exekvering av godtycklig kod i PHP 5.3.9 | SAFESIDE-bloggen
Pingback: PHP任意代码执行漏洞(CVE-2012-0830)
Pingback: PHP-5.3.9远程执行任意代码漏洞(CVE-2012-0830) | 风雪之隅
Pingback: Ubuntu y las actualizaciones de seguridad…
Pingback: PHP “php_register_variable_ex()”函数任意代码执行漏洞(CVE-2012-0830)
Pingback: Krytyczna dziura w PHP 5.3.x. | HackingNews.pl - Najnowsze wiadomości ze świata hackingu
Pingback: php 任意代碼執行漏洞 | Nroe 的 PHPer 博客
Pingback: RSA Conference Europe Wrap-Up Day #1 | /dev/random
Pingback: Grave vulnerabilidad en PHP introducida con un parche anterior. | Descargas Antivirus Gratis