Overview
Today, I'm reviewing the YubiKey Key Storage Module code. It is a PHP application designed to be run on a locked down server.
Operation
The basic premise of it's running is this; there are 3 participants:
- The end user
- The YubiKey Key Storage Module (YubiKey-KSM) server
- An application server
The YubiKey-KSM is assumed to be running on a 'locked-down' server, separate to the application server. An authentication looks something like this:
- The end user provides an OTP from their YubiKey to the application server
- The application server hands the OTP to the YubiKey-KSM sever
- The YubiKey-KSM server replies with an authentication success or failure message
- If the OTP validated, the application server performs any further authentication, checks the counter values of the OTP are valid, and the grants access if all of the authentication is a success.
Attack
Worst Case
The attack is quite simple; if you assume the authentication server isn't completely impenetrable (For example, using a MySQL database).
This means that an attacker can simply grab a user's OTP credentials and use them immediately, without any hindrance.
Logs
The logs contain OTPs and their associated plain-texts. Enough said. Even successful authentications are written out to the logs. These can be reused by an attacker.
Man-in-the-Middle (MITM)
A man in the middle can harvest OTPs from a user (if there is, for example a poor application with doesn't use SSL on it's sign-in page, or perhaps a phising attack), then these harvested OTPs can be reused with the YubiKey-KSM PHP implementation.
Improvements
Hashing
Protecting the user's inner name (the secret) one should be a top priority. Hashing the password with a good configuration of scrypt should take ~100ms.
Cracking that hash would be difficult. If we assume that scrypt had been used correctly, and it takes one attacker's CPU 100ms to check one guess, it will take on average 1.5x105 CPU-years to guess an internal name. Even being able to concurrently guess 106 passwords at once reduces this to 57 days. That's a lot of CPU time and a hell-of-a-lot of RAM (assuming decent scrypt settings).
This still doesn't stop the attacker gaining the AES key for all the YubiKeys and just decrypting any that they see to extract the credentials. And bear in mind that OTPs can be printed to the YubiKey-KSM logs when a failure occurs
Cracking that hash would be difficult. If we assume that scrypt had been used correctly, and it takes one attacker's CPU 100ms to check one guess, it will take on average 1.5x105 CPU-years to guess an internal name. Even being able to concurrently guess 106 passwords at once reduces this to 57 days. That's a lot of CPU time and a hell-of-a-lot of RAM (assuming decent scrypt settings).
This still doesn't stop the attacker gaining the AES key for all the YubiKeys and just decrypting any that they see to extract the credentials. And bear in mind that OTPs can be printed to the YubiKey-KSM logs when a failure occurs
Logs
Just stop writing the OTPs and their plain texts to logs, ever. Just don't do it. You wouldn't steal a car, you wouldn't log a user's password in plain-text, don't log a user's plain-text OTP credentials.
Use The Counter
Storing the last-seen counter against the user's public name is an absolute must. Don't leave any step of the validation up to the application server. If you've seen that counter value before, just stop, declare it to be an authentication failure and move on with your life! This helps to protect against a MITM attack
Attacking the Defences
A better way would be to simply retrieve the AES key (as outlined in the worst case) and attempt to sniff the incoming OTPs directly, thus breaking the secrecy of the internal name and counter. This allows an attacker to generate valid OTPs at will, even with all of the defences above implemented.
Conclusion
Using your own YubiKey-KSM is not an especially fantastic idea at the moment, with the code as it stands at this time. A few patches here and there, and it'll be much better. Some improvements might be:
- Allowing for different databases much more easily,
- Hashing the credentials,
- Asserting that the request for validation is genuine,
- Rate limiting the number of requests for OTP validations that can be made in a given second,
- Not writing sensitive information to log files,
- Checking the counter on the OTP before declaring an OTP valid).
These are quite a substantial modification to the system. However, excluding the 3rd point, none of the above actually change the interface that the YubiKey-KSM server presents to the outside world.