-
Notifications
You must be signed in to change notification settings - Fork 5
Case study of an attack on SCRAM
Suppose an attacker Mallory attempts to impersonate Alice. So upon establishing a connection with the server, Mallory sends:
clientFirst() -> <<"n,,n=Alice,r=fyko+d2lbbFgONRv9qkxdawL">>.
The server recognises Alice, and hence answers with its own nonce, and Alice's salt and iteration count (first issue, server has revealed the salt).
serverFirst() -> <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096">>.
Was Mallory to be actually a MitM, and the previous two messages be legit messages exchanged between Alice and the Server, then when Alice sends
clientFinal() -> <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=">>.
Mallory is now able to mount offline attacks, as it has now the proof and all the transient data to try password guesses.
AuthMessage := username,client-nonce,salt,ic,server-nonce
do
password' := NextPasswordGuess()
SaltedPassword' := Hi(password', salt, ic)
ClientKey' := HMAC(SaltedPassword', "Client Key"))
StoredKey' := H(ClientKey')
ClientSignature' := HMAC(StoredKey', AuthMessage)
ClientProof' := ClientKey' XOR ClientSignature'
while ClientProof != ClientProof'
This is now only made more difficult by the challenge, that might make the testing way slower, but more often than not, the passwords are human-made low-entropy passwords, so there won't be a lot of testing here.
If channel binding was used, then the best Mallory can do is not to relay the message Alice just sent with the proof back to the server. If Mallory was to do so, the server would notice that a MitM was in course and can —should!— then block authentication to this user until the account is legitimately recovered some other trusted way.
Was Mallory to then cut the connection, the server wouldn't be sure why the connection was dropped and Mallory would be left with enough data to mount an offline attack.
If Mallory was not a MitM, then she has to compose the client's final message. Of it, only the proof is unknown, so let's suppose Mallory only tries to guess:
clientFinal() -> <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=base64encodedguess">>.
Here, as Mallory is actively trying to impersonate Alice, channel binding wouldn't make any difference. So we're then left with the attempted proof. What can Mallory learn from this?
An important question to ask is whether is is sensitive to timing attacks. The answer is that not really, because the next time Mallory tries to guess a different proof, as the nonces are all different, the valid proof will be all different and hence matching on any first bytes tells Mallory nothing useful for future attempts. This is good news.
If the server was to compare instead StoredKeys from the given proof, then the server would exor the given proof with the signature the server alone knows from the secret StoredKey and the variable AuthMessage. So timing attacks on the StoredKeys comparisons wouldn't also work, as the StoredKey calculated from the given client's proof is also randomly variable.
serverFinal() -> <<"v=rmF9pqV8S7suAoZWja4dJRkFsKQ=">>.