Digest Authentication with ASP.NET Web API (Part 3)

This is the final installment of the series of posts on digest authentication. I have covered the basics or theory in the first post and some C# code in the second post. In this, I’ll cover the security aspect, especially how we can try to break the digest authentication.

Digest authentication provides the nonce counter to counteract replay attacks. To test this, let’s replay a previous successful request through Fiddler. Fire up Fiddler and capture the traffic to your ASP.NET Web API. If you are testing ASP.NET Web API in the local host by running in the ASP.NET Development server, Fiddler will not catch the traffic. You might need to add a DNS entry to your hosts file and replace localhost in the URL with the name you have given in hosts file. Now, after managing to capture a few requests, select a successful ASP.NET Web API request with 200 OK response in the web sessions pane on the left. Right click, Replay > Reissue Unconditionally. If you do this, you will not get another 200 but a 401 along with a fresh server nonce. What is preventing the replay is the check we are doing with the nonce counter that it should always be greater than the previous one. Of course, you can copy paste the request into ‘Request Builder’, edit the nc so that the value is incremented and resubmit. Again, it will be a 401. This time, by messing with the nc, we have ensured calculated hash does not match the hash in the response field.

Fiddler Replay

Let’s shift our focus towards attacking digest authentication with brute force. In the response field of the authorization header, client sends the digest or MD5 hash. There is no way we can recover the password from the digest but we can assume the password as one letter ‘a’ and compute the MD5 hash. If the hash thus computed matches the response field, we have a winner! If not, we don’t loose heart and move on with the next alphabet ‘b’. Once we reach ‘z’, we start with ‘aa’ and then ‘ab’ and so on until the hashes match or in other words, we know the password.

Before we get on with this, I want to point you to the fact that rainbow tables are of no use with digest authentication. Digest is computed using both server nonce and client nonce. It is not possible to pre-compute the hashes in advance and create a dictionary to look up. So, let’s try to muscle our way out of digest authentication using brute CPU power. First, we need a method to generate combinations of alphabets. To keep this simple, let us generate the passwords based on lower case alphabets only – a through z.

static IEnumerable<string> GeneratePassword(
                              IEnumerable<string> input = null)
{
    // ASCII a is 97 and I need the next 26 letters for a - z
    var range = Enumerable.Range(97, 26);

    input = input ?? range.Select(n => char.ConvertFromUtf32(n));

    foreach (var password in input)
        yield return password;

    var appendedList = input
        .SelectMany(x => range.Select(n => x + char.ConvertFromUtf32(n)));

    foreach (var password in GeneratePassword(appendedList))
        yield return password;
}

Next, we need a method to calculate the hash. It is pretty much the same logic in the handler we saw in the part 2. One important thing to note is that ha2 need not be computed every time. Since it is based only on HTTP method and URI, it can be computed once and reused. So, I’m hard-coding it here for “GET” and the URI.

static bool IsMatch(Header header, string password)
{
    string ha1 = String.Format("{0}:{1}:{2}",
        header.UserName,
        header.Realm,
        password).ToMD5Hash();

    //string ha2 = String.Format("{0}:{1}",
    //    header.Method,
    //    header.Uri).ToMD5Hash();

    string ha2 = "347c9fe6471afafd1ac2c5551ada479f";

    string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
        ha1,
        header.Nonce,
        header.NounceCounter,
        header.Cnonce,
        "auth",
        ha2).ToMD5Hash();

    return (String.CompareOrdinal(header.Response, computedResponse) == 0);
}

Header class can be just a collection of properties representing the fields in the header.

class Header
{
    public string Cnonce { get; set; }
    public string Nonce { get; set; }
    public string Realm { get; set; }
    public string UserName { get; set; }
    public string Uri { get; set; }
    public string Response { get; set; }
    public string Method { get; set; }
    public string NounceCounter { get; set; }
}

With that, we are almost ready. Let’s say we have the ability to sniff a network and we caught a request with a valid authorization header (like the one below).

Authorization: Digest username=”john”, realm=”RealmOfBadri”, nonce=”932444f708e9c1e5391aad0e849ea201″, uri=”/api/values”, cnonce=”968ffba69bfc304eabaebffc10d56a0a”, nc=00000001, response=”4d0b1211f1024ec616d55ac8312f5f46″, qop=”auth”

Let’s new up an instance of header and load the values from the header into the properties. I call IsMatch() in a loop (PLINQ) and when there is a match, I come out after printing the password. I just use DateTime to time this though I could have used a StopWatch.

Header header = new Header()
{
    UserName = "john",
    Realm = "RealmOfBadri",
    Nonce = "932444f708e9c1e5391aad0e849ea201",
    Uri = "/api/values",
    Cnonce = "968ffba69bfc304eabaebffc10d56a0a",
    NounceCounter = "00000001",
    Response = "4d0b1211f1024ec616d55ac8312f5f46",
    Method = "GET"
};

DateTime start = DateTime.Now;

Parallel.ForEach<string>(GeneratePassword(),(password, loopState) =>
{
    if (IsMatch(header, password))
    {
        Console.WriteLine("Gotcha ---> " + password);
        loopState.Break();
    }
});

DateTime end = DateTime.Now;
Console.WriteLine((end - start).TotalSeconds + " seconds");

In the laptop that I use, I have two cores 2.26 GHZ and 2 GB RAM. Not a great one though but even with this, we are able to figure out the password ‘apple’ in about 46 seconds. ‘apple’ is a way too simple password but let’s try to figure out the time it takes to crack passwords ‘zzz’ and ‘zzzz’. It comes to be around 1 second and 26 seconds respectively. It makes sense. For the latter, it has to repeat the calculations 26 times – one each for each alphabet. Extending this, how much will it take to crack a password of length 8? 137 days! That is too longer a time to crack a password. Of course, if I have multiple machines at my disposal, I can divide and conquer this is in a quicker time. So, if I have 10 laptops, I can figure out the password in about 2 weeks. Well, I don’t even need laptops. If I have an Azure subscription, I can stand up as many instances as I want (20 max without calling customer service). Of course, it will cost me $2.40 per hour per compute instance for a small compute (1.6GHz CPU, 1.75GB RAM).

If the system enforces the password strength through lower and upper case, numbers and special characters, it is going to get really really difficult to use just brute force. Just as an illustration, I tweaked by password generator to include upper case letters. Mind you, not the numbers and special characters but only upper case. Instead of 26 letters, we have 52 now. Now, in the same laptop, it took 755 seconds to figure out the password of ‘aPPle’, as against the earlier 46 seconds for ‘apple’. To crack ‘zzz’ and ‘zzzz’, it took 4 and 212 seconds respectively. Extrapolating this, I’ll need about 50 years to crack a password of length 8. Yes, I typed it right, 50 years. On the other hand, we could use a dictionary of common passwords but that will not be an easy task, especially if the user creates a real strong password using characters and numbers.

So, moral of the story is, if the password is strong (longer and uses a mix of upper case, numbers and special characters) and not straight out of an English dictionary, even if a malicious user is very rich and can afford thousands of Azure compute and Amazon EC2 instances, it is going to be very tough! As they say, any security mechanism can be breached. It is only a tradeoff. It takes time and resources. Time is very important. What harm if someone gets your current online banking password in about 10 years from now? You would have changed it more than 100 times by then.

Advertisements

2 thoughts on “Digest Authentication with ASP.NET Web API (Part 3)

  1. You sure can but then you will need to compute hashes outside of Fiddler. As far as I know, there is not a built-in way of doing that in Fiddler. It will be good to let a browser do that for you once and you capture the traffic in Fiddler and try to do it similar from Fiddler yourself subsequently.

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