Encrypted Cookies using ASP.NET

Security Center Introduction

In order to store use specific information during a ASP.Net session you have to option to place state data in a browser cookie. These cookie are send in plain text and using various tool it is possible to read the content of these cookies.

Although reading might not always be a problem, the ability to change the content of a cookie is a big thread. Tampering with the cookie is actually very easy, and i will demonstrate this using a Firefox extension called TamperData.

Tampering a Cookie

Setup

I started by creating a small ASP.NET application that checks the presence of our cookie. If not sets, a cookie is create and filled in the page load. After that i print the values on the page.

  1: protected void Page_Load(object sender, EventArgs e)
  2: {
  3:     if (Request.Cookies["__IGUZA.NET"] == null)
  4:     {
  5:         HttpCookie cookie = new HttpCookie("__IGUZA.NET");
  6:         cookie["SomeKey"] = "SomeValue";
  7:         cookie["SomeSecondeKey"] = "SomeSecondValue";
  8:         Response.Cookies.Add(cookie);
  9:     }
 10: 
 11:     if (Request.Cookies["__IGUZA.NET"] != null)
 12:     {
 13:         HttpCookie cookie = Request.Cookies["__IGUZA.NET"];
 14:         Response.Write(string.Format("SomeKey : {0}<br />", cookie.Values["SomeKey"]));
 15:         Response.Write(string.Format("SomeSecondeKey : {0}<br />", cookie.Values["SomeSecondeKey"]));
 16:     }
 17: }

Tampering with the cookie

Once we have the code ready we open the page in Firefox. Start Firefox and open TamperData. Open the page and in TamperData select the request. Because the cookie is not there yet we get a Set-Cookie response header with the values.

TD_SetCookie

The cookie values are then displayed on the page by our code.

cookievalues

Now we can start tampering the cookie value. On the TamperData dialog click start tampering. Now refresh the page. TamperData will display a dialog asking if you want to tamper the data.

TamperRequest

Now click Tamper and change the value of the cookie we are sending from SomeValue to SomeValueTampered.

TD_ChangesCookieValue

When you click OK, the request is send to the server. Inspect the new request in TamperData and see that the new values are send in the cookie.

TD_TamperedCookiesSend

The new cookie values are displayed on page.

cookievaluestampered

Protecting against tampering

So what does this all mean?.

Well, as you can see, it is very easy to change the value of a cookie. Of course this is not the only way. There are multiple tools that can do this, including Fiddler.

The problem here is that the server is not setting this value for fun. It will probably be using this information for the next requests. Sensitive data could be kept in this cookie. A good example would be a security token that contains you login information. If one would be able to change this data it could be a high security risk.

Encryption is the key

Now let’s demonstrate the part where we protect against tampering. We take the same solution as before, but add some encryption logic that will encrypt the cookie data using the private key of a certificate. The code used to encrypt the cookie takes a string value and a certificate.

NOTE: Of course other key providers are possible to use.

  1: public static string PKIEncrypt(string data, X509Certificate2 certificate)
  2: {
  3:     if (certificate == null)
  4:         throw new ArgumentException("Certificate can not be null.");
  5:     
  6:     if (certificate.HasPrivateKey == false)
  7:         throw new CryptographicException("Certificate does not contain a private key.");
  8: 
  9:     RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key;
 10: 
 11:     byte[] plainbytes = Encoding.UTF8.GetBytes(data);
 12:     byte[] cipherbytes = rsa.Encrypt(plainbytes, false);
 13:     return Convert.ToBase64String(cipherbytes);
 14: }

Of course we also need to decrypt the string before we can use it in code. De decryption method takes a Base64 Encoded string and a certificate.

  1: public static string PKIDecrypt(string Base64EncryptedData, X509Certificate2 certificate)
  2: {
  3:     if (certificate == null)
  4:         throw new ArgumentException("Certificate can not be null.");
  5: 
  6:     if (certificate.HasPrivateKey == false)
  7:         throw new CryptographicException("Certificate does not contain a private key.");
  8: 
  9:     RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
 10: 
 11:     byte[] decryptedBytes = rsa.Decrypt(Convert.FromBase64String(Base64EncryptedData), false);
 12:     return Encoding.UTF8.GetString(decryptedBytes);
 13: }

The only thing we need to do next is change the code in the Page_Load.

First we get the certificate we want to use for encryption.

  1: X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
  2: store.Open(OpenFlags.ReadOnly);
  3: 
  4: X509Certificate2 Cert = store.Certificates.Find(
  5:     X509FindType.FindByThumbprint,
  6:     "08 15 b7 a7 26 d3 06 0a 4f 61 b9 eb f7 e4 0f 7a 7c 24 6f 0c",
  7:     false)[0];

Than we add the encryption logic to the part where we assign the cookie to the request.

  1: if (Request.Cookies["__IGUZA.NET"] == null)
  2: {
  3:     HttpCookie cookie = new HttpCookie("__IGUZA.NET");
  4:     cookie["SomeKey"] = "SomeValue";
  5:     cookie["SomeSecondeKey"] = "SomeSecondValue";
  6:     cookie.Value = Encryption.PKIEncrypt(cookie.Value, Cert);
  7:     Response.Cookies.Add(cookie);
  8: }

Then we need to add the decryption logic to the part where we get and read the cookie. The reason we are making a copy of the cookie is that if if we would assign the original cookie the decrypted value, unless we encrypt it again, that would be the new value of the cookie.

  1: if (Request.Cookies["__IGUZA.NET"] != null)
  2: {
  3:     HttpCookie cookie = Request.Cookies["__IGUZA.NET"];
  4:     HttpCookie decryptedCookie = new HttpCookie("TEMP");
  5:     decryptedCookie.Value = Decryption.PKIDecrypt(cookie.Value, Cert);
  6:     Response.Write(string.Format("SomeKey : {0}<br />", decryptedCookie.Values["SomeKey"]));
  7:     Response.Write(string.Format("SomeSecondeKey : {0}<br />", decryptedCookie.Values["SomeSecondeKey"]));
  8: }

Now if we do the requests again making use of TamperData we will see a encrypted cookie value and get the following result.

 TD_SetCookieEncrypted

If we now start tampering and eagerly change some part of the unreadable value, we get a server error (500) because we don’t have a nice try catch logic around my code :).

TD_EncryptedCookiesSendTampered

ServerError

The only way this would work is if the person trying to change the value has the private key of the certificate used to encrypt the data. He/She would have to copy the encrypted string, decrypt it,  change the values, encrypt the string again and assign the encrypted value to the cookie again. The fact that the hacker would have the private key is almost unthinkable.

Conclusion

Because of the fact that cookies are plain text and easy to tamper it is sometime required to protect the cookie against threads. In come cases cookies just contain unimportant data, like LastVisit, FirstName and LastName. This data could as well be send in plain text.

Although it all seams easy in code, the overhead of signing and decrypting should be held against the need to protect your state data.

Happy Coding.

Martijn.

Comments

Popular posts from this blog

Yet Another WiX Tutorial Part 3: Customizing the UI dialogs

Yet Another WiX Tutorial Part 2: Your First Installer