Last updated at Sat, 19 Aug 2017 03:44:06 GMT

Everyone knows about SQL injections. They are classic, first widely publicized by Rain Forest Puppy, and still widely prevalent today (hint: don't interpolate query string params with SQL).

But who cares? SQL injections are so ten years ago. I want to talk about a vulnerability I hadn't run into before that I recently had a lot of fun exploiting. It was a NoSQL injection.

The PHP application was using MongoDB, and MongoDB has a great feature that allows you to filter the documents using Javascript queries. You pass a Javascript function in a string to the $where variable when calling find(). This function should return a boolean value, determining whether the given document should be returned in the dataset or not.

A natural progression to this is to make these dynamic, based on user input. Great, this gives me a possible opportunity to inject arbitrary Javascript (into a fairly limited sandbox) and dictate what the result set contains. It isn't super useful though at face value. Maybe you could extract data by defining new boolean clauses that would evaluate to true when 'this.username' == 'admin'.

Given the un-named application is un-named, I wanted to write a module that the framework devs could easily test, so I decided to write a small PHP script that was vulnerable to a few different vectors of NoSQL injection in a similar way to this application. Doing some research into this kind of vulnerability, I found a very useful post that included a PDF of a talk given at BlackHat a few years ago. It is the basis for the techniques used in the module.

Basically, in versions of MongoDB prior to 2.4, there was a 'db' variable available in the context of the javascript that ran in the $where variable. This 'db' variable lets you do a lot of really cool things, I chose to write a module that easily shows the exploitability of the vulnerability. It will perform boolean injections to extract the collections available in the database. I also knew that Javascript could allow for injections in a few places, so I took this into account, requiring slight syntax tweaks (much like SQL injections). The vulnerable script is available here.

Let's see some code. In this example PHP script, the programmer is going to look up people based on their age:

<?php
$m = new MongoClient("mongodb://127.0.0.1:27017");
$m->selectDB('foo');
$collection = $m->selectCollection('test', 'phpmanual');
 
if ($_GET["age"] != "") {
     $js = 'function(){if(this.name == "Joe"||this.age=='.$_GET["age"].')return true;}';
     $cursor = $collection->find(array('$where' => $js));
     foreach($cursor as $doc) {
          var_dump($doc);
     }
}
?>

If you look closely, the programmer is just dropping the GET parameter into the Javascript. What if, instead of putting "8", we put "8||true". Magically, every single document in the collection has been dumped. I love magic. But let's extrapolate on this.

Remember that 'db' variable we have? It has some pretty good methods available, such as getCollectionNames(). This returns an array of strings. An introduction to boolean attacks is in order before going much further.

If you aren't 100% sure what a boolean attack is, hopefully this will clarify it. A boolean attack allows an attacker to gain information from a system by asking a series of true or false questions. Many times, you use "metadata" such as what the response was when you ask a 'false' question and the response of a 'true' question to glean information. You don't care what the data is in these responses, you just care that they are predictably different. Unless you have great wordlists, this is generally done a byte at a time. A time-based SQL injection is actually another example of a boolean attack, except the "metadata" used is temporal to determine whether a query was true or false.

In the above code example, in order to exploit it efficiently, I need information about what exactly 'getCollectionNames()' is returning, such as how many strings it is returning. I can find this out easily using a boolean attack. Let's say I pass in "8||db.getCollectionNames().length == 1", but I get my 'false' response back. This means that whatever 'db.getCollectionNames().length is returning does not equal one. But when I pass "8||db.getCollectionNames().length == 2", I get my 'true' response, every document in the collection. This tells me that there are two collection names being returned by the method.

Now I can take the attack further. I pass in "8||db.getCollectionNames()[0][0] == 'a'" and I get a 'false' response back. I am asking the server if the zeroth character of the zeroth string is 'a'.  That sucks, and this is really tedious. I know enough now though to automate actually getting the collection names. By incrementing the name and character indexes and asking boolean questions (is the char 'a'? is it 'b'? etc...), I can easily exfiltrate the collection names available to demonstrate and fully exercise the vulnerability.

msf auxiliary(nosql_injection_collection_enum) > run
[*] Testing "'||this||'
[*] Testing "';return+true;var+foo='
[*] Testing '"||this||"
[*] Testing '";return+true;var+foo="
[*] Testing ||this
[*] Looks like ||this works
[*] 2 collections are available
[*] Length of collection 0's name is 9
[*] Collections 0's name is phpmanual
[*] Length of collection 1's name is 14
[*] Collections 1's name is systemindexes
[*] Auxiliary module execution completed
msf auxiliary(nosql_injection_collection_enum) >

As previously stated, this will only work on injections in versions of MongoDB prior to 2.4. This module was tested against 2.2.7. Version 2.4 removed the 'db' variable completely from the javascript context. My module was submitted as Pull Request 3430, was landed to the main development branch of Metasploit last night, and will be available as part of the next Metasploit Update. If you don't yet have Metasploit, might I suggest taking a few moments to download it? It's free, after all.