1

I was learning about SCRAM and liked its ability to protect against various attacks (as mentioned in this MongoDB blog post), specifically:

  1. Eavesdropping - The attacker can read all traffic exchanged between the client and server. To protect against eavesdropping, a SCRAM client never sends the password as plaintext over the network.
  2. Replay - The attacker can resend the client's valid responses to the server. Each SCRAM authentication session is made unique by random nonces, so that the protocol messages are only valid for a single session.
  3. Database Compromise - The attacker can view the contents of the server's persistent memory. A database compromise is mitigated by salting and iteratively hashing passwords before storing them.
  4. Malicious Server - The attacker can pose as a server to the client. An attacker is unable to pose as server without knowledge of the client’s SCRAM credentials.

My question is specifically concerned with the Eavesdropping and Database Compromise parts.

My Understanding

According to my understanding, a SCRAM authentication session looks like this (I'm intentionally ignoring anything GS2 related):

Client |                                                  | Server
       | >>>--------{ username, client_nonce }-------->>> |
       | <<<---{ combined_nonce, salt, iter_count }---<<< |
       | >>>-----{ combined_nonce, client_proof }----->>> |
       | <<<-----------{ server_signature }-----------<<< |
       |                                                  |

and the server has a database that contain an authentication information table that has the following columns:

  • user_id
  • username
  • salt
  • iter_count
  • stored_key
  • server_key

Threat Model

Eavesdropping

If an attacker can only read network traffic, then they have learned all information going through the network (username, combined_nonce, salt, iter_count, client_proof, server_signature), but this is not a problem since this information is not enough to impersonate a client, since the attacker cannot forge a client_proof without salted_password, which only the real client knows. That's because

  • client_key = hmac(salted_password, "Client Key")
  • stored_key = hash(client_key)
  • client_signature = hmac(stored_key, {username, combined_nonce, salt, iter_count})
  • client_proof = client_key ^ client_signature

The only secret information here is the salted_password, which only the real client knows. So eavesdropping is "safe".

Database Compromise

If an attacker can only read the database, then they have learned all information stored in the database (user_id, username, salt, iter_count, stored_key, server_key), but this is still not a problem since the data stored can only be used to verify a client and not impersonate it. That's because after the client sends the client-final-message, all the information the server needs to verify the client is either already exchanged by the client and server (username, combined_nonce, salt, iter_count, client_proof) or stored in the database (stored_key). And then does the following

  • client_signature = hmac(stored_key, {username, combined_nonce, salt, iter_count})
  • client_key = client_proof ^ client_signature
  • should_by_stored_key = hash(client_key)
  • Check the computed should_by_stored_key by the stored_key stored in the database, and if the match then the verification process is successful, otherwise it's not.

Notice how the information stored in the database is not sufficient to impersonate the client because the stored_key is the result of a one-way hashing function to the client_key, so even if an attacker gains access to the stored_key, they cannot recover the client_key. So database compromise is "safe".

Eavesdropping and Database Compromise

If an attacker can read both the network traffic and the database, then they have all data exchanged through the network (username, combined_nonce, salt, iter_count, client_proof, server_signature) and all data stored in the database (most importantly stored_key). This allows the attacker to do the following:

  • client_signature = hmac(stored_key, {username, combined_nonce, salt, iter_count})
  • client_key = client_proof ^ client_signature

And once the client_key is recovered, the attacker can use it in subsequent authentication sessions, because client_key is not secure against replay attacks because it is not protected by the randomized nonce.

For example the attack would look like this after gaining the client_key

Client |                                                  | Server
       | >>>--------{ username, client_nonce }-------->>> |
       | // The attacker sends username and random nonce  |
       |                                                  |
       | <<<---{ combined_nonce, salt, iter_count }---<<< |
       | // The server responds with combined nonce and   |
       | // hashing configuration                         |
       |                                                  |
       | // Now the attacker computes                     |
       | // client_signature = hmac(stored_key,           |
       | //   {username, combined_nonce, salt, itr_cnt})  |
       | // client_proof = client_signature ^ client_key  |
       | // and sends the client_proof to the server      |
       |                                                  |
       | >>>-----{ combined_nonce, client_proof }----->>> |
       | // The server checks and the check succeeds!?    |
       |                                                  |
       | <<<-----------{ server_signature }-----------<<< |
       |                                                  |

Question

  1. Is this a real issue? Is there anything obvious I'm missing?
  2. If so, how can it be mitigated? Is there newer standards that avoid this issue?
  3. How have nobody thought about this before? I searched all over the internet and found nothing mentioning anything close to this (except this which mentions other issues)
  4. Is there other issues like this that I'm not aware of?

1 Answer 1

0

Whether this is a real issue depends on your threat model. We do know that attackers can compromise a site and read all the traffic that passes through it, but that's of course more difficult than passively eavesdropping or just exfiltrating the database (e.g., through an SQL injection vulnerability). SCRAM makes things more difficult for an attacker than it would be otherwise, which is good.

We can help make this even more difficult by using things like TLS. If an attacker can sniff network traffic and has compromised the database, but can't get live access to the server on which the operation is running, then TLS will prevent them from eavesdropping. We will want to use TLS in most cases anyway, since it prevents the attacker from reading or tampering with the data, which we will also want to protect. (Nobody will be pleased that someone sniffing the network traffic for your database can read all of your users' email addresses and the content they've uploaded, for example.)

There are, of course, other approaches using asymmetric cryptography where even a full compromise of the server does not allow impersonation. With SSH public keys, the client and the server each pick a nonce and use that to negotiate a shared secret with Diffie-Hellman key exchange, and the client signs data from the shared secret plus additional authentication information. Since the server has only the public key, there's no way to sign future challenges. Even if the server always picked the same nonce and DH private key, as long as the client continues to act properly, the shared secret will differ with high probability and impersonation will be impossible.

A similar approach is also used with FIDO2 and WebAuthn, which use an asymmetric key pair as a second factor to authenticate users. This, too, is invulnerable to eavesdropping or compromise of the server. There are some password-based authenticated key exchanges (PAKEs) which also have a similar property. However, they suffer from the shortcoming that people tend to pick poor passwords, and thus they may be vulnerable to attack that way.

There is no 100% attack-proof scheme in the real world. Cryptography relies on the fact that secrets are, well, secret, and that only the authorized parties have those secrets, including private keys. We can minimize the risk of attack by minimizing the number of secrets and the number of parties who have secrets, such as in the SSH and WebAuthn protocols, but there will always be some required.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .