Open source software security

Exploiting PHP PCRE Functions

30 November -0001

Several high profile arbitrary code execution vulnerabilities in PHP web applications stem from improper handling of PCRE (Perl Compatible Regular Expression) functions. PCRE is designed to implement regular expressions for the preg_ functions in PHP (such as preg_match and preg_replace). Under most circumstances the PCRE engine is completely safe. It does, however, provide the /e modifier which allows evaluation of PHP code in the preg_replace function. This can be extremely dangerous if used carelessly.

The preg_replace function, when used with the /e modifier and supplied with a PHP function extends the functionality of the replace to allow a callback. To use an over simplified example, let\'s say we want a function that will take input and capitalize all the characters in the input string. We could use the following PHP code to accomplish this task:

<?php
$string = "this is my lower sting";
print preg_replace('/(.*)/e', 'strtoupper("\\1")', '$string');
?>

Running this code results in the following output:

THIS IS MY LOWER STING

While this code seems completely unobtrusive the '/e' modifier actually allows us to evaluate the second argument as a PHP expression. By way of comparison, without the modifier, the strtoupper() function is not evaluated:

<?php
$string = "this is my lower sting";
print preg_replace('/^(.*)/', 'strtoupper(\\1)', $string);
?>

Produces the output:

strtoupper(this is my lower sting)

As you can see nothing is actually happening here other than the interlacing of the string inside the function. Let us assume as part of our attack vector we want the phpinfo() command to fire. In order to do this we might manipulate the value of $string so that the code executed as follows:

$string = "phpinfo()";
print preg_replace('/^(.*)/', 'strtoupper(\\1)', $string);

Without the '/e' flag, however, the code simply outputs:

strtoupper(phpinfo())

If we add the flag, like so:

<?php
$string = "phpinfo()";
print preg_replace('/^(.*)/e', 'strtoupper(\\1)', $string);
?>

The function fires and calling the page will actually result in the phpinfo() command spilling results onto the screen.

Filter Evasion

The PCRE engine will do some filtering on input, however, which will prevent straightforward command injection. Assuming the attacker is attempting to execute arbitrary shell commands they might try to manipulate the code so that it looked thus:

<?php
$string = "system('ls -lah')";
print preg_replace('/^(.*)/e', 'strtoupper(\\1)', $string);
?>

Executing this script will actually cause PHP to barf out several errors and fail to fire off the requested function. Evading the PCRE input filters which are causing our input to change from:

system('ls -lah')

to:

system(\'ls -lah\')

is a little tricky, but not impossible. Scouring the web won't reveal any easy methods to do this, however. The closest mention that I can find is http://milw0rm.org/exploits/7553. Bypassing the PCRE filter isn't all that hard, however. PCRE will successfully mitigate attempts to inject single quotes or escape characters, so one must try an entirely different tact.

Using the backtick operators is the perfect solution. It turns out that PCRE doesn't actually escape these, so you can use backticks with the '/e' flag to cause PHP code to be evaluated. This is particularly dangerous as the third argument in the preg_replace() function is often user supplied data.

Altering our code above, if we execute the following PHP:

<?php
$string = "`ls -lah`";
print preg_replace('/^(.*)/e', 'strtoupper(\\1)', $string);
?>

The program will produce a capitalized directory listing. Using this technique an attacker who can control the input to the third parameter can execute arbitrary commands with the privileges of the web server.

Conclusion

Utilizing the '/e' flag on any preg_replace should instantly raise red flags. With this addition the normally innocuous search and replace function becomes a potential vulnerability. When using this flag user input needs to be carefully sanitized to prevent command injection. When auditing PHP code for vulnerabilities it is rare to look for this vulnerability but it should definitely become a regular part of the PHP security auditor\'s checklist.