Last Friday, a vulnerability was disclosed at the Ekoparty Security Conference which exploits a bug in the .NET Framework's cryptographic classes. Apparently, the bug compromises the security of the ViewState and FormsAuthentication subsystem in ASP.NET along with several other crypto-related features in the .NET Framework.
What you’re about to read is based on what I know about FormsAuthentication and what I saw in a 4 minute YouTube video that will probably get yanked any day now due to the poor choice of background music. If this video turns out to be BS, you can assume that my foot is in my mouth and move along. Otherwise, pay attention. I’ve got a pretty good solution in the works and I’ll post what I’ve got on CodePlex under the Apache v2.0 license in a day or so.
Apparently, tools have shown up in the wild that allow an attacker to break into web applications protected by FormsAuthentication, allowing the attacker to assume the identity of any known user on the system. Due to the architecture of the FormsAuthentication subsystem, the attacker does not need to know the password of the user and might not even see a login screen. Since FormsAuthentication operates at such a low level in ASP.NET, the same exploit would theoretically work for WebForms and MVC applications in ASP.NET.
Understanding FormsAuthentication and ASP.NET Authorization
When an anonymous user requests a resource on an ASP.NET application for which anonymous access is not allowed (i.e., by web.config's [location/]authorization/deny rules or MVC's [Authorize] attribute on an action), the user is redirected to a login page where they are asked to supply credentials. If the user authenticates successfully, a FormsAuthenticationTicket is created on the server containing, among other things, the username of the user. The FormsAuthenticationTicket is serialized, encrypted, encoded as a string, stuffed in a cookie (the FormsAuthenticationCookie) and stored on the user's browser. The user is then redirected back to the originating page or action.
With each subsequent request, ASP.NET detects the presence of the FormsAuthenticationCookie. It attempts to convert it back into a FormsAuthenticationTicket by decoding the string representation, decrypting it, and deserializing it. If that process is successful, it then ensures that the user did not maliciously tamper with the FormsAuthenticationCookie's path or expiration properties by comparing them to the values embedded in the FormsAuthenticationTicket during the login step. This is how FormsAuthenticationTickets are validated. If the ticket validates, the User.Identity and Thread.CurrentPrincipal are set to a GenericPrincipal containing the username from the FormsAuthenticationTicket and the request is authenticated—the system believes that you are who you claim to be. Now the various authorization mechanisms in ASP.NET (UrlAuthorization, MVC action authorization, and code access security) can determine what privileges you have.
This process is repeated for every single request: WebForm pages, MVC actions, web services, handlers, javascript files, images, stylesheets, etc. (Not 100% accurate for static content on IIS 5/6).
How the Padding Oracle Exploit works
I'm not a mathematician and I don't have a deep understanding of the inner workings of the crypto classes in .NET. In fact, only a fraction of those classes are even written in managed code. The rest of them call into unmanaged libraries to get the job done. Like most users, when I needed to do encryption or decryption in .NET, I (re-)learned the API and treated it like a black box. I knew it worked and I assumed it was secure. Apparently, so did Microsoft.
The Padding Oracle exploit involves the side-channel release of information via the browser that allows an attacker to learn the web server's decryption key over a series of carefully crafted requests. This makes it possible for the user to decrypt a FormsAuthenticationCookie and read a FormsAuthenticationTicket on the client side. This is not supposed to be possible... but it's not the end of the world. The real risk would be if a user figured out a way to modify their FormsAuthenticationTicket & FormsAuthenticationCookie or to create new ones from scratch and still get past the server’s validation.
Enter CBC-R Encryption. I don't know how it works either, but with code in the wild, it doesn’t really matter. The gist of it is that when you have a padding oracle like the one exposed by the bug, you can convert the decryption key you just discovered into an encryption key.
Padding Oracle + CBC-R defeats FormsAuthentication
Before the compromise of the cryptographic classes, it was safe to assume that a user would be unable to maliciously tamper with the FormsAuthenticationCookie's value without breaking the server's ability to decrypt the bytes. After all, changing just one byte of an encrypted message makes decryption of that message impossible. However, the exploit makes it possible to both decrypt and encrypt messages on the client side that are compatible with the web application's machine key. Now all the attacker needs is a username with decent privileges and they are in.
To recap, an attacker:
- Finds the decryption key by way of the padding oracle exploit
- Creates a matching encryption key by using CBC-R
- Creates a FormsAuthenticationTicket on their computer with any username, expiration date, cookie path and persistence setting they want.
- Serializes, encrypts, and encodes the FormsAuthenticationTicket
- Sticks the encoded string into a standard HTTP cookie
- Passes the cookie to the server they stole the keys from.
- The server decodes the string, decrypts the bytes, deserializes the FormsAuthenticationTicket, and authenticates any requests containing that cookie as the username in the ticket (assuming that user has access to the system).
- The authorization subsystem determines what they can do on the system based solely on the username.
The attacker was never prompted to enter a password and your application is compromised.
The Second Weakest Link
The crypto bug is the biggest weak point, but its exploitation is made possible by the fact that the server doesn't keep track of the FormsAuthenticationTickets it generated. The security of the entire authentication subsystem is based on the assumption that if your application encounters a FormsAuthenticationCookie that it can decrypt into a FormsAuthenticationTicket, it must have created them both and the user presenting them must be the user whose name appears in the FormsAuthenticationTicket.
I've got a simple fix for this problem in the works. As I said earlier, I'll post it to CodePlex as soon as I can. It doesn't cure the padding oracle problem, but it eliminates the ability to create or modify FormsAuthenticationTickets on the client and it paves the way for a few other cool features.