Forcing ASP.NET to encrypt like five years ago


While preparing slides and demos for the upcoming BSides Warsaw conference, I spent some time digging through the code of the old ASP.NET Crypto stack. In case you do not remember, six years ago researchers reported multiple cryptographic design flaws in ASP.NET. One of the critical issues was that ASP.NET did not authenticate ciphertexts. Thus they were vulnerable to the padding oracle attack. Microsoft learned its lesson and rewrote the crypto stack in ASP.NET 4.5. If you want to find out more, have a look at those three excellent articles by Levi Broderick: Part 1, Part 2, Part 3. As I plan to demo the padding oracle attack during my presentation I wanted to restore the old behavior using the latest version of the ASP.NET framework. In this post, I am presenting how I achieved that. But to watch the live demo, I invite you to come to my presentation at 10:00, Saturday, October 14th :).

Finding interesting bits in the .NET Reference Code

The current recommended way to encrypt data in ASP.NET is by using Protect(byte[] userData, string[] purposes) and Unprotect(byte[] protectedData, string[] purposes) methods of the System.Web.Security.MachineKey. Both these methods accept purposes which are used to generate derived keys for different cryptographic scenarios. But the old, now obsolete, methods are still there: Encode(byte[] data, MachineKeyProtection protectionOption) and Decode(string encodedData, MachineKeyProtection protectionOption) and for my case I definitely want to use them. The MachineKeyProtection is an enum which contains three values: All, Encrypted, Validation. To simulate the old ASP.NET cryptography logic I want to encrypt my data but with no signature attached. Unfortunately (for me only of course!), calling MachineKey.Encode(mySecretBytes, MachineKeyProtection.Encrypted) produces ciphertext with appended HMAC. So I started analyzing the code of the MachineKey class on the .NET Reference Code in search for some hidden flags I could enable to remove the signature. I must say that it was an enlightening and at the same time scary experience. I found that the Encode/Decode methods end up calling the EncryptOrDecryptData method of the System.Web.Configuration.MachineKeySection class. Let’s have a look at its signature:

byte[] EncryptOrDecryptData(
    bool fEncrypt, byte[] buf, 
    byte[] modifier, int start, int length,
    bool useValidationSymAlgo, bool useLegacyMode, 
    IVType ivType, bool signData)

When I see this number of boolean flag parameters, I can smell the coming bulk of if-s in the method code. After reading the code I can only tell you that my assumptions were correct. Microsoft must have created a crazy amount of tests to verify all the scenarios this function implements. In case you would like to experiment on your own with this code, here is a snippet you may use:

var t = typeof(System.Web.Configuration.MachineKeySection);

var ivTypeType = t.Assembly.GetType("System.Web.Configuration.IVType");

var m = t.GetMethod("EncryptOrDecryptData", BindingFlags.Static | BindingFlags.NonPublic, null,
                    new Type[] { typeof(bool), typeof(byte[]), typeof(byte[]), typeof(int),
                    typeof(int), typeof(bool), typeof(bool), ivTypeType, typeof(bool) }, null);

// (bool fEncrypt, byte[] buf, byte[] modifier, int start, int length, 
//    bool useValidationSymAlgo, bool useLegacyMode, IVType ivType, bool signData)
var cipher = (byte[])m.Invoke(null, new object[] {
    true /* fEncrypt */,
    toEncrypt /* buf */,
    null /* modifier */,
    0 /* start */,
    toEncrypt.Length /* length */,
    false /* useValidationSymAlgo */,
    false /* useLegacyMode */,
    1 /* ivType:Random */,
    false /* signData */ });

Fortunately, the EncryptOrDecryptData method has an excellent comment which explains the various ways we may call it. I will cite it here:

This algorithm is used to perform encryption or decryption of a buffer, along with optional signing (for encryption) or signature verification (for decryption). Possible operation modes are:

ENCRYPT + SIGN DATA (fEncrypt = true, signData = true)
Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself)
Output: E(iv + buf + modifier) + HMAC(E(iv + buf + modifier))

ONLY ENCRYPT DATA (fEncrypt = true, signData = false)
Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself)
Output: E(iv + buf + modifier)

VERIFY + DECRYPT DATA (fEncrypt = false, signData = true)
Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data)
Input (buf): E(iv + m + modifier) + HMAC(E(iv + m + modifier))
Output: m

ONLY DECRYPT DATA (fEncrypt = false, signData = false)
Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data)
Input (buf): E(iv + plaintext + modifier)
Output: m

The 'iv' in the above descriptions isn't an actual IV. Rather, if ivType = IVType.Random, we'll prepend random bytes ('iv') to the plaintext before feeding it to the crypto algorithms. Introducing randomness early in the algorithm prevents users from inspecting two ciphertexts to see if the plaintexts are related. If ivType = IVType.None, then 'iv' is simply an empty string. If ivType = IVType.Hash, we use a non-keyed hash of the plaintext.

The 'modifier' in the above descriptions is a piece of metadata that should be encrypted along with the plaintext but which isn't actually part of the plaintext itself. It can be used for storing things like the user name for whom this plaintext was generated, the page that generated the plaintext, etc. On decryption, the modifier parameter is compared against the modifier stored in the crypto stream, and it is stripped from the message before the plaintext is returned.

In all cases, if something goes wrong (e.g. invalid padding, invalid signature, invalid modifier, etc.), a generic exception is thrown.

The most surprising thing for me was how it uses the “Initialization Vector”. Usually, we add the IV (for AES it is 16 bytes long) in front of the cipher text and send both plus signature to our recipient. But Microsoft chose to encrypt the 24 bytes long IV (I’m not sure if we should still use the name Initialization Vector in this context) along with the plain text. If we set the IVType parameter to Random, the IV will play its role of producing different cipher texts for the same plain text each time we call the EncryptOrDecryptData method. But I still find this approach unusual. As the method is using AES-CBC, the “regular” IV is still required, and it is an array of zeroes.

Writing the code

I learned that the flag I need to set to false is signData. By checking the methods which call the EncryptOrDecryptData I noticed that I could modify this flag by adding the aspnet:UseLegacyMachineKeyEncryption key to the appSettings section of the web.config file:

<appSettings>
  <add key="aspnet:UseLegacyMachineKeyEncryption" value="true" />
</appSettings>

With this setting in place, I could just use the MachineKey.Encode/Decode methods. My .ashx handler (I needed to use something from those times, you know :)) for the demo looks as follows:

<%@ WebHandler Language="C#" Class="EncryptionHandler" %>

using System;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Security;
using System.Text;

public class EncryptionHandler : IHttpHandler
{
    static readonly byte[] secret = Encoding.UTF8.GetBytes("It will be something different during the demo");

    public void ProcessRequest(HttpContext context)
    {
        var viewState = context.Request.Form["VIEWSTATE"];

        if (viewState == null) {
            viewState = MachineKey.Encode(secret, MachineKeyProtection.Encryption);
            context.Response.ContentType = "text/html";
            context.Response.Write("<!doctype html><html><form action=\"/EncryptionHandler.ashx\" method=\"POST\">" +
                "<input type=\"hidden\" name=\"VIEWSTATE\" value=\"" + viewState + "\" />" +
                "<input type=\"submit\" value=\"Test\" /></form></html>");
            return;
        }

        var v = MachineKey.Decode(viewState, MachineKeyProtection.Encryption);
        context.Response.ContentType = "text/plain";
        if (v.SequenceEqual(secret)) {
            context.Response.Write("I know the secret");
        } else {
            context.Response.Write("Something is wrong with my secret.");
        }
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}

It will render a simple web form with one hidden input field and a submit button. And the value of the hidden form is something we will attack.

One thought on “Forcing ASP.NET to encrypt like five years ago

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s