PHP dangerous function preg_replace() leads to remote code execution with improper implementation
Preg_replace
preg_replace — Perform a regular expression search and replace
Description
preg_replace ( mixed
$pattern
, mixed$replacement
, mixed$subject
[, int$limit
= -1 [, int&$count
]] ) : mixed
Environment
All the testing will be test under docker environment with php version 5.3
docker run --name app -d -p 8080:80 -v $(pwd):/var/www/app romeoz/docker-apache-php:5.3 |
put all your php files under the same directory with the docker file.
Access the page in localhost port 8080
Start
preg_replace(patterns, replacements, input, limit, count) |
Searches subject
for matches to pattern
and replaces them with replacement
.
The normal use of Preg_replace()
is safe enough for replacing pattern using regex
Let see a example:
When we want to filter unwanted words from user input and replace it with proper words
|
The /i
modifier will match both upper and lower case letters.
we expect the output to be eat my shit please
without any parameter
But what if we want to change the shit
to poo
instead ?
And we filter off bad words. Everything seems fine with this function
The danger comes in when the modifier set to /e
instead of /i
, it will cause PHP to execute the replacement value as code.
the preg_replace()
has come preg_replace('/shit/e','system('whoami'),"eat my shit please")
The string shit
trigger the replace function to execute a system('whoami')
Null byte bypass
Let’s look at the another example if we are not able to control the delimiter
|
pattern
parameter no longer require the /
and delimiter
This code seems safe, attacker can no longer end the regular expression with their own modifier.
Do take note PHP
take some of the syntax from C
. In C, it handles strings as a character array, it needs a way to define the last character of the string. This is done using a null byte. A null byte is denoted by \0
in C. preg_replace
function handle an input string as they handled by C.
Therefore, we can input a \0
which is %00
in URL to control the last character of the string.
CTF challenge
CTF challenge from PHP SECURITY CALENDAR
header("Content-Type: text/plain"); |
We can use PHP’s curly syntax to inject the function call
http://localhost:8080/challenges.php/?\S*={${system(id)}}
The reason why we are using curly syntax is because after the function complexStrtolower
we are storing our result into "<result>"
in double quotes
In PHP, the variable in double quotes are allow to parse as variable.
In curly syntax, single curly braces is for parsing variable.
// Works, outputs: This is fantastic |
Note:
Functions, method calls, static class variables, and class constants inside
{$}
work since PHP 5. However, the value accessed will be interpreted as the name of a variable in the scope in which the string is defined. Using single curly braces ({}
) will not work for accessing the return values of functions or methods or the values of class constants or static class variables.
For functions we have to use double curly braces. E.g. {${phpinfo()}}
Another question
why we are able to execute system(id)
without quote the 'id'
if we add id
in single quotes, it will auto add a slash to escape the single quotes (Which I have no idea ??? Comment if you know the reason)
however, in PHP. Constants without quote will assume as string beacuse of the PHP ‘loosely typed’ characterstic (Will be discover more on later post PHP type juggling
)
|