Improved password-based authentication in MongoDB 3.0: SCRAM Explained (Part 2)

< View all blog posts
MongoDB
February 06, 2015
Category: Technical

In MongoDB 3.0, SCRAM is now the default password-based authentication mechanism. In the first post in this series, we introduced the threat-model SCRAM is designed to counter, and explained the inner workings of the protocol. Now we can take a closer look at how SCRAM addresses the attacks that follow from this threat-model. For a more detailed discussion, please see the Security Considerations section of the SCRAM RFC.

Resistance to Eavesdropping

By capturing the full exchange of messages, an attacker can learn the username, salt, iteration count, nonces, ClientProof and ServerSignature. While there is no efficient way to learn the ClientKey or ServerKey from this information alone, an attacker could mount a dictionary attack by repeatedly picking a candidate password ‘Guess’ and checking the following equation:

HMAC(HMAC(KeyDerive(Guess, salt, i), "Server Key"), AuthMessage) = ClientProof

This attack can be mounted offline, and can be highly parallelized. As such SCRAM's resistance to eavesdropping is dependent on the iteration count supplied to the key derivation function (and the strength of the user's password). While the default iteration count should suffice for most deployments, users who require increased security have the ability to increase it. It is also recommended that SCRAM is used in conjunction with Transport Layer Security (TLS) to further mitigate the risk of eavesdropping.

Resistance to Replay

SCRAM resists replay by randomizing each instance of the protocol using the ClientNonce and ServerNonce. The nonces are used as input to the ClientSignature and ServerSignature (via the AuthMessage), so the signatures are only valid for a single session. The risk of nonce reuse, which would enable a replay attack, is reduced by using entropy from both the client and the server to compute the ClientSignature.

Resistance to a Malicious Server

SCRAM requires the server to prove its identity by sending a valid ServerSignature to the client as the final step of the protocol. Producing a valid ServerSignature requires knowledge of all messages sent in the SCRAM session and access to the ServerKey stored in the client’s credential. If an attacker poses a malicious server by eavesdropping on a prior session to learn the salt and the iteration count, the attacker will be unable to generate a valid ServerSignature. As a successful instance of SCRAM verifies the client and server’s identities to each other, we say that SCRAM provides mutual authentication.

Resistance to Database Compromise

In SCRAM, the server verifies the ClientProof without having the ability to generate it11 (as it doesn't store the SaltedPassword). If an attacker gains access to a server's stored credentials, it will learn the StoredKey and ServerKey. While the attacker will gain the ability compute a valid ServerSignature and thus impersonate the compromised server, the attacker still needs the ClientKey (or SaltedPassword) to generate a valid ClientProof. Thus a server compromise alone does not reveal sufficient information for the attacker to impersonate a client. However, as with the eavesdropping attack, the attacker will have gained sufficient information to mount a computationally expensive dictionary attack.

If in addition to compromising the server's credentials, the attacker eavesdrops on an instance of SCRAM, the attacker will be able to learn the ClientKey without performing a dictionary attack. Recall that the attacker will learn the ClientProof and AuthMessage, and can compute the ClientSignature using the AuthMessage and StoredKey. Then the attacker can simply compute:

ClientKey = ClientProof ⊕ ClientSignature.

Thus if an attacker can view the server's credential store and capture arbitrary network traffic, the attacker can impersonate a client. Again, we recommend using TLS in addition to SCRAM to mitigate this attack.

Using SCRAM-SHA-1

Using SCRAM as an authentication mechanism is as simple as using the legacy MONGODB-CR mechanism. As SCRAM-SHA-1 (the mechanism name for SCRAM in MongoDB) is the default authentication mechanism in MongoDB 3.0, it is not necessary to specify it when starting the server.

First we create a user on our test database.

> use testdb
> db.createUser({user: "testUser", 
…               pwd: "testPassword", 
…               roles: [ { role: "readWrite", db: "testdb" } 
… ]})

If we restart the server without the --auth flag (or authenticate as a user with readWrite on the admin database), we can see the SCRAM credential for our test user.

> use admin
> db.system.users.findOne({user: "testUser"})
{
    "_id" : "admin.testUser",
    "user" : "testUser",
    "db" : "testdb",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "+seF99VS0sZFe30VPBHA7A==",
            "storedKey" : "DYPbk/QJVowCNDPe2O2uWMmGq8U=",
            "serverKey" : "q4KAi4pVZNOLCgWcxcBr7jkM3m8="
        }
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "testDb"
        }
    ]
}

Then we can authenticate to the server using SCRAM.

> use testdb
switched to db testdb
> db.auth({user: "testUser", pwd: "testPassword"})

Upgrading to SCRAM from MONGODB-CR

Assume that we start with an instance of MongoDB 2.6.x. We start by authenticating as an administrator, then we create a user.

> db.createUser({user: "testUser", pwd: "testPassword", roles: [ { role: "readWrite", db: "myDb" } ] })

If we check the system.users collection, we can see the newly created MONGODB-CR credential.

> use admin
> db.system.users.findOne({user: "testUser"})
{
    "_id" : "myDb.testUser",
    "user" : "testUser",
    "db" : "myDb",
    "credentials" : {
        "MONGODB-CR" : "f050eba660da0d34bb60db5e13c2be32"
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "myDb"
        }
    ]
}

Now we stop the 2.6.x server and start a 3.0 server using the same data directory. We can see that the MONGODB-CR credentials are still in the system.users collection.

> db.system.users.findOne({user: "testUser"})
{
    "_id" : "myDb.testUser",
    "user" : "testUser",
    "db" : "myDb",
    "credentials" : {
        "MONGODB-CR" : "f050eba660da0d34bb60db5e13c2be32"
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "myDb"
        }
    ]
}

We can run the authSchemaUpgrade command to replace the MONGODB-CR credentials with SCRAM credentials.

> db.getSiblingDB("admin").runCommand({authSchemaUpgrade: 1 });
{ "done" : true, "ok" : 1 }

We can now see that a SCRAM credential has been created for the test user.

> use admin
> db.system.users.findOne({user: "testUser"})
{
    "_id" : "myDb.testUser",
    "user" : "testUser",
    "db" : "myDb",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "aCXRCYs9kgn5I3sliluXdQ==",
            "storedKey" : "2aDnf8OIv8dLeUDOZJwI15bRHWc=",
            "serverKey" : "9rqa5qYZzsl8SKU1CIOJgQh+j4Y="
        }
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "myDb"
        }
    ]
}

For more information about securing your MongoDB deployment, download our Security Architecture Guide. It includes details about MongoDB’s security features and provides a security configuration checklist:

Download the Security Guide

About Adam Midvidy

Adam Midvidy is an engineer on the MongoDB Platforms team with an interest in cryptography.


11 More formally, we say that SCRAM is a zero-knowledge proof of password.

<< Read Part 1

comments powered by Disqus