Yesterday I've discovered an "interesting" implication of using user input inside var_export() and print_r() functions. To those who have never used the two, a brief overview of their functionality. The var_export() function takes a variable and represents the data found within as a valid PHP string. By default this string is dumped to screen, but if you want you can have it be returned as a string, by passing a 2nd optional parameter as boolean TRUE. For example if you wanted to put an array creation code into a file, you'd do something like this:
The print_r() function is similar in function, except the returned data is intended for debugging and not storage. As with var_export() by passing a 2nd optional parameter you can force the data to be returned as a string rather then dumped to screen.
Herein lies the problem, when it comes to storing the data, this is done by enabling output buffering of the content without a set buffer size limit. Consequently, by forcing the function to generate a massive string it is possible to launch a denial of service attack aimed at exhausting both processor and memory.
Some of you are probably thinking this is pretty hard to accomplish via user input, requiring hundreds of megabytes of data to be delivered, but this couldn't be further from the truth. To successfully complete such as an attack all you need is to submit a post request which takes just 1 line of code to generate:
PHP:
<?php
$post_data = "foo".str_repeat("[]", 20000)."=bar";
?>
This will force PHP to create a 20,000 dimensional array, causing the generated output for var_export($foo,1); or print_r($foo,1); to take hundreds of megabytes, eventually leading to PHP crash. The actual size of the request is a mere 40 kilobytes.
So, how do you protect your code against exploitation through this?
The first and easiest way to avoid using var_export() and print_r() with the 2nd parameter, especially in situations where the data in question contains user supplies input.
If you absolutely must use the functionality then consider enabling memory_limit, which sets restriction on just how much memory a script my try to use. This however is not the gentlest of fixes, since out of memory situation will result in the termination of a PHP instance, forcing the web server to re-create the process; this itself in sufficient quantity can constitute a DOS.
Another take on the problem is to use the count() function with the 2nd parameter set to 1 that makes the function recursive, giving you a total count of ALL the elements found within. Since each dimension counts as an element it’ll be able to spot overly nested arrays. If the returned count exceeds the value of a static limited, let's say 250 for examples sake, then var_export or print_r is not called.
PHP:
<?php
if (count($array, 1) < 250) {
$data = var_export($array,1);
}
?>
The final solution involves patching your PHP with a security patch from
http://www.hardened-php.net/.
One the features of this patch is the ability to restrict the size of a nested array supplied via user input, by default set to 100. Thus preventing the user from creative a complex array necessary to exploit this problem.
Digital Sandwich on : var_export/print_r