I was planning updating my previous post, further explaining the “Padding Oracle” vulnerability, but have decided to write new post instead, explaining how you can minimize the chance of hacking. I think it will be more practical.
A brief reminder: the vulnerability is based on the ability of the attacker to detect a decryption error when sending a modified request.
In this post, I’ll work on a simple but typical application. I’ve seen hundreds of applications like that. The application uses login mechanism similar to the Form Authentication, and simulates the most painful vulnerability: impersonation into administrative account.
The application description
It doesn’t matter what the application does, the important thing is that it has authentication. The application consists of a user name/password dialog, and after the login was confirmed(using database or whatever), the system generates a cookie containing user’s name and roles, encrypts it and sends to the browser. Each consequent request, the system gets the cookie, decrypts it’s content, pulls out the user name and roles, creates a security principal and assigns it to the current request. The user then shown a home page.
The attacker’s actions
A potential attacker will read the cookie and begin modifying it according to the algorithm, and sending the modified request to the server. From each server response the attacker will try to conclude whether the cyphertext was decrypted correctly or not. In order to prevent, or at least minimize hacking possibility, our mission is to confuse the attacker. But before that, let’s understand how the attacker will distinguish between valid and invalid state.
Is it right and wrong
When processing the request by our application, two states are possible:
- The cookie is successfully decrypted, the principal is set. The response will be a home page, maybe even with a logged in user name shown on it. In this case it is easy to deduce that the modified cookie is valid.
- Cookie decryption failure. When following according to the attacking algorithm, the only possible exception will be because of invalid padding. In this case, it is not important how you deal with the error: allow the YSOD to appear, redirect to a constant error page (the Microsoft’s workaround), or even show a welcome screen without the user name shown. It doesn’t matter, because the attacker only needs to know that there was some abnormal behavior, which implies invalid decryption.
Without taking a proactive action we will not be able to confuse the attacker. So let’s see what can we do.
Confusing the attacker
To confuse the attacker, we need to make him believe that the invalid request is valid or vice versa. However, I don’t see how we can perform the diversion only using the above two states. Therefore, we need to introduce a third state, which will be different, but will be seen by the attacker as one of the two.
The additional state I’m going to introduce will be “invalid user name”. To create this state, I’m going to confirm, that the user name in the cookie is a valid one. In this case, the decryption will be successful, however, we are going to send back to the browser a generic error page. It is important to show the same error page, even with a random delay, exactly according to the guidelines described in the Microsoft’s workaround. The random delay is used to confuse the attacker even more. One of the ways to detect abnormality is to compare response times. Because exception throwing takes more time than just a simple validation, an attacker can take advantage of it to distinguish between the states.
There are numerous ways to validate the user name. Here are the ones I think are the most appropriate:
- Validate against the users’ repository, like a database table or an Active Directory. The problem is that reading from repository may be is expensive performance-wise, and may hurt system’s scalability. To overcome this, you can cache user’s data in a memory and try validating against the in-memory cache.
- As an addition to the previous method, do not store human-understandable values in the cookie. For example, store user’s GUID instead of the name, and on each request pull out user’s data from the cache or a repository. This way, even if the encryption will be broken, it will be hard to guess GUIDs. Moreover, a GUID can represent a single-use token, pointing to a cache value. This is the most secure way!
- Add a checksum to the value before encrypting it. After decrypting, validate the checksum. Remember, the attacker will send random requests. His chances to guess the checksum is extremely low, so you’ll most certainly will be able to detect an attack.
Now, the attacker will not be able to distinguish between the decryption error and validation error, and won’t be able to take advantage of the vulnerability.
The “Padding Oracle” vulnerability is not the simplest to prevent, however, the prevention is possible. Microsoft’s workaround is vital to apply, however, it is not enough in many situations. Preventing the exploit requires thinking and coding
Summing up, here are my recommendations:
- Apply the Microsoft’s workaround. Even though it is not enough, it is vital.
- Do not keep sensitive values on the client. Instead, keep references to values stored on server. For example in a cache or a session variable key.
- Always validate values received from the client. In case of invalid value, do not show explicit message – redirect to a generic error page and log the actual error.
- Ensure DDOS filter activated on your firewall. The attacker will run thousands of requests to the server in a short time. Firewalls are capable of detecting such an activity and blocking the attacker’s IP address, leaving him outside.