PHP

Some tricks specific to the PHP web programming language

WordPress

Type Juggling

When code uses == or != instead of === or !== the user may use certain strings to do weird stuff with PHP converting strings to integers

# true in PHP 4.3.0+
'0e0' == '0e1'
'0e0' == '0E1'
'10e2' == ' 01e3'
'10e2' == '01e3'
'10e2' == '1e3'
'010e2' == '1e3'
'010e2' == '01e3'
'10' == '010'
'10.0' == '10'
'10' == '00000000010'
'12345678' == '00000000012345678'
'0010e2' == '1e3'
'123000' == '123e3'
'123000e2' == '123e5'

# true in 5.2.1+
# false in PHP 4.3.0 - 5.2.0
'608E-4234' == '272E-3063'

# true in PHP 4.3.0 - 5.6.x
# false in 7.0.0+
'0e0' == '0x0'
'0xABC' == '0xabc'
'0xABCdef' == '0xabcDEF'
'000000e1' == '0x000000'
'0xABFe1' == '0xABFE1'
'0xe' == '0Xe'
'0xABCDEF' == '11259375'
'0xABCDEF123' == '46118400291'
'0x1234AB' == '1193131'
'0x1234Ab' == '1193131'

# true in PHP 4.3.0 - 4.3.9, 5.2.1 - 5.6.x
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0, 7.0.0+
'0xABCdef' == ' 0xabcDEF'
'1e1' == '0xa'
'0xe' == ' 0Xe'
'0x123' == ' 0x123'

# true in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0
# false in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2, 5.2.1 - 5.6.26, 7.0.0+
'0e0' == '0x0a'

# true in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.6.26, 7.0.0+
'0xe' == ' 0Xe.'

Magic Hashes

Collection of weird hashes that can be used for PHP Type Juggling

Comparison rules

In PHP (< 8.0) the following table of rules applies when loosely comparing variables:

A table showing common loose comparisons with interesting values

For a complete and detailed guide on every possible comparison between types, see the PHP docs.

Local File Inclusion

When some code uses the include, include_once, require or require_once keyword to include a file from user input (eg. $_GET['page']) you can include any file on the system using Directory Traversal.

The functions run PHP code in the files that are included. If you can upload any file, put PHP code in there, and when you include it, it will be executed.

If the response is PHP code, it will be executed and not shown to you, which could be a problem. If you want to read source-code of the .php files, you can use the following PHP filter to convert the file to base64 before interpreting it:

You can read PHP files like this even if .php is appended to your input in the code. Because the last part of this PHP filter is .php you can just remove it and let the code add it back

RCE using PHP Filters

The main goal for getting RCE from LFI is to get some arbitrary content returned by the URL, which is then included and read as PHP code. If you control the start of the URL in some include function, you can use PHP Wrappers to get content from other places than straight from a file. The data:// wrapper for example can return arbitrary content, for example, using the base64 encoding:

However, in more recent versions of PHP, the allow_url_include= option which enables some of these wrappers is disabled by default. However, there is a really powerful technique that I came across recently found by loknop which combines lots of PHP filters to turn any file into arbitrary PHP code. For this, you only need to have control of the start to allow PHP wrappers, and then have a valid file anywhere to transform into PHP code. But you'll have a valid file anyway from the default functionality of the site, so this is pretty much a guarantee.

Read the writeup linked above to understand how they found it, but here's the basic idea:

  • convert.iconv.UTF8.CSISO2022KR will always prepend \x1b$)C to the string

  • convert.base64-decode is extremely tolerant, it will basically just ignore any characters that aren't valid base64.

Combining these and a lot of convert.iconv to convert between encodings, we can get any arbitrary base64 string that we can decode and include. Here's the exploit script used to automatically do this for a PHP shell, and then execute commands using it:

For arbitrary contents instead of just the <?=`$_GET[0]`;;?> needed here, check out the list of all base64 characters that Carlos Polop made. Synacktiv later also made a tool that automates it:

Tool to quickly generate PHP filter chains with arbitrary content
Payload: <?=`$_GET[0]`?>

RCE using pearcmd.php

Recently a new technique was developed for cases where you don't control the start of the include path. In such cases, you cannot use wrappers, but directory traversal using ../ is still possible. This opens up the possibility of using other existing PHP files on the system to execute arbitrary code, which the following writeup found a technique for:

Using pearcmd.php to get RCE from local file inclusion through directory traversal

This technique is especially useful if .php is appended to your input like many ?page= parameters. Because the /usr/local/lib/php/pearcmd.php file fits this requirement it is very usable. To interact with this script we use the query string which is passed as command-line arguments. The config-create subcommand allows us to write a file anywhere with some content we control, perfect for writing a webshell!

Even if the user has no write privileges to the webroot, we already have a directory traversal on the include function to be able to do this in the first place, so we can re-use it later to include the file we write executing the payload. We will write it to the /tmp folder with a simple shell that runs the ?0= parameter as a system command:

Note that the config file we will write contains this string multiple times, so the command is executed and its output is included in the response multiple times. We first use the directory traversal to include the pearcmd.php file and write the config with a PHP shell:

You should receive a verbose CONFIGURATION ... response if this was successful. Then the only thing left to do is execute our written webshell with the same vulnerability:

RCE using Session file

Another way is using PHP sessions, which store your session data in /tmp/sess_[PHPSESSID] which you can access using your own PHPSESSID= cookie on the site. Anything saved to $_SESSION[] in the code will be saved to this file. If you put PHP code into your session and include it, the PHP code will be executed.

A short writeup showing this attack in practice (fun fact, my first ever blog post!)

RCE using logs

You can include log files with your input in them, which can contain PHP code to be executed on include. The User-Agent is often saved to logs:

If you can't find the logs you might be able to find it by looking at the configuration of the server, you can include/read any file after all:

Reading Files from error-based oracle

A trick using php://filter was shown in RCE using PHP Filters to craft any arbitrary string from any other content by chaining filters. It was discovered however that this idea could be brought even further in order to leak file content when it is not reflected. Here is a vulnerable code example:

This type of code may be common in a backend process that the user doesn't directly notice. While nothing is reflected back, an attacker can still leak the content of the file by carefully crafting PHP filters that expand exponentially when a certain character is in a certain place. By creating many of these filter chains they can begin to leak all the characters of the file one by one.

For a more technical breakdown, see the following writeup:

Detailed walkthrough of the error-based filter chain oracle, including vulnerable functions and a tool

In the above post, they also include a tool for exploiting such vulnerabilities automatically by telling it your request endpoint and parameters:

Exploit the vulnerability easily by passing your request via the CLI

In a real-world scenario, this could be used to potentially leak secret keys or passwords stored in files like config.php or .env. Another thing to keep in mind is that the error-based method might not work if a server treats warnings as errors. In such cases, you can use the alternative timing attack built-in because these high-memory operations take more time to exponentially grown than others.

Tip: If your injection is more complex than a POST request with some extra parameters or headers, like a JSON format or multi-step process, you can try to change the requester.py -> req_with_response() function to include your custom flow.

Reading files using Prefix+Suffix format

The latest development in filter chain attacks for LFI is a way to add arbitrary prefixes and suffixes to a file's content, without any noise. This allows parsers expecting a specific format to validate/extract the part you want to leak without the original file having to have that format. It is best shown with an example:

Normally, the above code expects a JSON-formatted file like {"message": "Hello, world"} and reads its message attribute back to the client. While an attacker can change the $_POST['url'] value to any URL, this would fail on the json_decode() function without actually showing the content. This is where the new technique and tool come in:

Generate a filter chain for arbitrary prefixes and suffixes (blog post)

With the techniques outlined in the blog post, it can add characters to the front of the file's content, as well as to the end. The content itself will remain in the center, allowing a simple format like JSON or XML to drag it along to a response that the attacker can see. Then it becomes possible to leak big chunks of a file with a single request, instead of single bits with many requests like in the Reading Files from error-based oracle section.

This file gets big quickly as you increase the prefix/suffix length, as well as the number of bytes. GET parameters are often limited by a maximum URI length, but POST parameters often lack this maximum and thus allow for giant filter chains like the one above.

Tip: Using this same technique, you can also simply use it to generate an arbitrary string like in RCE using PHP Filters without noise like non-ASCII characters. This allows you to exploit even more formats even when they are sanity-checked or parsed!

Debugging

The most well-known debugging protocol for PHP is Xdebug. For the cleanest and most realistic experience, use a VSCode Dev Container for your workspace as explained in Debugging for Python.

You will need to add xdebug to the PHP configuration so that any server that runs PHP code on the system will use it. For any setup, paste the output of php -i into xdebug.org/wizard. Below is a common configuration that works in most cases. If pelc is installed inside the container, setting up xdebug is simple:

In more generic containers, you can build it from scratch:

Then inside VSCode, install the PHP Debug extension and in the panel click create a launch.json file followed by PHP. Save this and press the button to start the locally-listening server. You can now set any breakpoints in the .php files, and when they are executed/requested, the breakpoint will trigger.

To start the server now, run the CMD that the Dockerfile normally would. It may be inherited from the FROM image, in that case, look it up on Docker Hub. In case of Apache2, for example, the command to run will be apache2-foreground. Sending an HTTP request to the configured port should now trigger the breakpoints you set in the code.

Last updated