Page 1 of 1

Guarding Against Session Hijacking in ASP.NET

#1 PsychoCoder  Icon User is offline

  • Google.Sucks.Init(true);
  • member icon

Reputation: 1642
  • View blog
  • Posts: 19,853
  • Joined: 26-July 07

Posted 23 August 2008 - 07:01 AM

In this tutorial we're going to be looking at something all .Net developers need to be aware of and guard against, and that is prevent session hijacking. Session hijacking is a form of hacking attack that involves accessing a users session state. While the damage can be as small as having access to someone's shopping cart data, or as severe as hijacking a session that contains a users personal, or financial, information. This kind of attack is generally carried out in two forms:
  • ID Guessing
  • Solen ID's
Session ID guessing is harder for an ASP.NET website because ASP.NET employs a random 120-bit number, but stealing a session ID is more prevalent. There are three main ways hackers steal session ID's:The main reason stealing session ID's from an ASP.NET application takes such little skill from the hacker is because ASP.NET doesn't encode any information in the session cookie other than the ID itself. If the server receives a Request with a valid ID it accepts the Request, no questions asked. Though it is impossible to create a fool-proof defense against such attacks, the developer can take certain steps to make them harder to pull off, and that is what this tutorial looks at.

In this tutorial we will look at intercepting the session cookie (before ASP.NET sees it), taking the MAC (Message Authentication Code), and creating our own MAC, based on the session ID, the users IP address and their User Agent. Our class will also rely on a validation key that is stored in the web.config file. The key will be based on a MD5 hash of a string, and should be different for all applications this is used for. Make sure your key is long and random, shorter keys are easier to guess. We will also be creating a custom Exception that will be used in the class.

Before we start, here's a short method you can use to create the MD5 hash for your validation key. It employs the MD5CryptoServiceProvider Class in the System.Security.Cryptography Namespace:

/// <summary>
/// method to generate a MD5 hash of a string
/// </summary>
/// <param name="strToHash">string to hash</param>
/// <returns>hashed string</returns>
public string GenerateMD5(string str)
{
    MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

    byte[] byteArray = Encoding.ASCII.GetBytes(str);

    byteArray = md5.ComputeHash(byteArray);

    string hashedValue = "";

    foreach (byte b in byteArray)
    {
        hashedValue += b.ToString("x2");
    }

    return hashedValue;
}




Now that we have the creation of the key covered, lets start making our secure session class. First and foremost, as with all classes you write, you need to make sure you have the proper Namespace's for your class, in this case we need seven of them:

using System;
using System.Web;
using System.Text;
using System.Web.Security;
using System.Configuration;
using System.Security.Cryptography;
using System.Globalization;




Now we need any global variables, in this case we have a single global, the variable that will hold the value of our key


private static string secretKey = null;



This class is designed to operate completely silent, meaning it works in the background with zero interaction from the developer whatsoever. Our class inherits the IHttpModule Interface.

First thing we will do is call the Init() and Dispose() Methods of the IHttpModule. In the Init() Method we will first check the value of our global variable secretKey, if it doesn't have a value we will initialize it.

We then wire up two Event Handlers, these will handle the BeginRequest Event and the EndRequest Event of the HttpApplication Class. The Dispose() method is a blank method, but it is required when inheriting from the IHttpModule Interface.


/// <summary>
/// method to initialize our class when the page is initialized
/// </summary>
/// <param name="application"></param>
public void Init(HttpApplication application)
{
    //find out of we have a validation key, if we dont initialize it
    if (secretKey == null) secretKey = GetKey();

    //register event handlers for the BeginRequest and EndRequest events
    application.BeginRequest += new EventHandler(onbeginRequest);
    application.EndRequest += new EventHandler(onendRequest);
}

public void Dispose() 
{ 
}



Now the Event Handler, we have two to write up:
  • onbeginRequest: Handles all transactions at the start of the request cycle
  • onendRequest: Handles all transactions at the very end of the request cycle
onbeginRequest is where we do the bulk of our work. The first thing we do is grab the current Request, this allows us access to all the information we need, including the current ASP.NET_SessionID cookie. Once we have the cookie in our possession we first check it's length, if it's less than 24 long we throw an exception because that tells us the cookie doesn't have a MAC attached. If we make it past that check we then grad the session ID and the MAC value off of the cookie (using string manipulation), then compare the MAC value with our generated MAC. If they don't match we throw an exception because something's happened to the cookie. Barring any errors we quickly assign the session ID to the value of the cookie, all before ASP.NET see's it.

onbeginRequest:
/// <summary>
/// method for handling the HttpApplication.BeginRequest event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void onbeginRequest(Object sender, EventArgs e)
{
    //get the current Request
    HttpRequest currentRequest = ((HttpApplication)sender).Request;

    //get the ASP.NET_SessionId cookie from the Request object
    HttpCookie requestCookie = RetrieveRequestCookie(currentRequest, "ASP.NET_SessionId");

    //check to see if the cookie exists (if == null)
    if (requestCookie != null)
    {
        //if the length is less than 24 we dont have a MAC so we need to throw an exception (our custom exception)
        if (requestCookie.Value.Length <= 24) throw new SessionerrorException("Invalid Session");

        //get the session id
        string sessionID = requestCookie.Value.Substring(0, 24);

        //get the MAC
        string mac = requestCookie.Value.Substring(24);

        //create a new MAC based on the session id and some of the users info (user agent, etc)
        string macCompare = CreateMAC(sessionID, currentRequest.UserHostAddress, currentRequest.UserAgent, secretKey);

        //check to see if the MAC's match, if not we have a problem
        if (String.CompareOrdinal(mac, macCompare) != 0) throw new SessionerrorException("Invalid Session");

        //set the cookies value to the session id
        requestCookie.Value = sessionID;
    }
}



In the onendRequest we grab the response cookie and make sure it isn't null (that would mean someone has hijacked the session), if all is OK we append our newly created MAC value to the end of the cookie, and this can be compared during the next BeginRequest Event, which will be the next page load for the application.

onendRequest:
/// <summary>
/// method for handling the HttpApplication.EndRequest event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void onendRequest(Object sender, EventArgs e)
{
    //capture the current request
    HttpRequest currentRequest = ((HttpApplication)sender).Request;

    //get the session cookie
    HttpCookie sessionCookie = RetrieveResponseCookie(((HttpApplication)sender).Response, "ASP.NET_SessionId");

    //make sure the cookie isnt null
    if (sessionCookie != null)
    {
        //add our newly generated MAC to the cookie at the end of the request
        sessionCookie.Value += CreateMAC(sessionCookie.Value, currentRequest.UserHostAddress, currentRequest.UserAgent, secretKey);
    }
}



In our Init() we called a method GetKey, which we use to initialize our secretKey variable. This method simply checks the web.config file for the SessionKey section and returns the value. An exception will be thrown if this value doesn't exist in the web.config:

/// <summary>
/// method for retrieving the validation key from the web.config
/// </summary>
/// <returns></returns>
private string GetKey()
{
    //get the key
    string validationKey = ConfigurationManager.AppSettings["SessionKey"];

    //check for a null or empty key. If so throw our exception
    if (validationKey == null || validationKey == String.Empty) throw new SessionerrorException("SessionKey not found. Application 

ending");

    //return the key
    return validationKey;
}




We have four more methods to look at in this class (before we get to our custom Exception class). They are
  • RetrieveRequestCookie: Used to retrieve the current Request cookie.
  • RetrieveResponseCookie: Used to retrieve the current Response cookie.
  • FindTheCookie: Used to find a cookie by it's name.
  • CreateMAC: Used to generate our custom MAC value for the session cookie.
/// <summary>
/// method for retrieving the Request cookies
/// </summary>
/// <param name="currentRequest"></param>
/// <param name="cookieName"></param>
/// <returns></returns>
private HttpCookie RetrieveRequestCookie(HttpRequest currentRequest, string cookieName)
{
    HttpCookieCollection cookieCollection = currentRequest.Cookies;
    return FindTheCookie(cookieCollection, cookieName);
}

/// <summary>
/// method for retrieving the Response cookies
/// </summary>
/// <param name="currentResponse"></param>
/// <param name="cookieName"></param>
/// <returns></returns>
private HttpCookie RetrieveResponseCookie(HttpResponse currentResponse, string cookieName)
{
    HttpCookieCollection cookies = currentResponse.Cookies;
    return FindTheCookie(cookies, cookieName);
}



FindTheCookie takes an HttpCookieCollection and a name as a parameter. From there it loops the length of the HttpCookieCollection passed to it comparing each cookie name with the name provided. If it finds a match it returns that HttpCookie, otherwise it returns null

/// <summary>
/// method for retrieving a cookie by it's name
/// </summary>
/// <param name="cookieCollection">the cookie collection to search</param>
/// <param name="cookieName">the cookie we're looking for</param>
/// <returns></returns>
private HttpCookie FindTheCookie(HttpCookieCollection cookieCollection, string cookieName)
{
    for (int i = 0; i < cookieCollection.Count; i++)
    {
        if (string.Compare(cookieCollection[i].Name, cookieName, true, CultureInfo.InvariantCulture) == 0)
            return cookieCollection[i];
    }

    return null;
}



Now we just need to generate a MAC for our session cookie. This is done by appending the current session id with the first segment of the users IP address and his User Agent. We then use the HMACSHA1 Class to generate a new MAC for the cookie:


/// <summary>
/// method for generating a new MAC for our session cookie
/// </summary>
/// <param name="id">current session id</param>
/// <param name="ipAddress">ip address of the current Request</param>
/// <param name="userAgent">current user's User Agent</param>
/// <param name="validationKey">validation key from the web.config</param>
/// <returns></returns>
private string CreateMAC(string id, string ipAddress, string userAgent, string validationKey)
{
    //create an instance of the StringBuilder with a max length of 512
    StringBuilder sb = new StringBuilder(id, 512);

    //append the first segment of the user's ip address to the string
    sb.Append(ipAddress.Substring(0, ipAddress.IndexOf('.', ipAddress.IndexOf('.') + 1)));

    //append the users User Agent to the string
    sb.Append(userAgent);

    using (HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(validationKey)))
    {
        return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
    }
}




Creating a custom Exception class is fairly straight forward so I am just going to post the code with little or no explanation. For more information on creating your own exception classes, read this on Creating Custom Exception In .Net, it has some pretty good information in it.

[Serializable]
public class SessionerrorException : Exception
{
    public SessionerrorException() : base("Invalid Session") { } 

    public SessionerrorException(string message) : base(message) { }

    public SessionerrorException(string message, Exception inner) : base(message, inner) { }

    protected SessionerrorException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}




Now that we have the coding part done, that is creating the module and the custom Exception, there are some things we need to add to the web.config file in order to wrap this up. First is the validation key, which should be a long random string generated with a MD5 hash. This should be placed in the <appSettings> section of your web.config file, and should look like this


<appSettings>
	<add key="SessionKey" value="3595381625A3DCC07E84E626939254834E0FD16B"/>
</appSettings>



My particular key is a MD5 hash based on a 11 character word (that will remain a secret). The last thing we need to do is register this HttpModule in our web.config. As you can image this needs to go in the <httpModules> section of the web.config. That looks like this


<httpModules>
	<add name="SecureSession" type="RLM.Core.Components.Security.SecureSession, SecureSession"/>
</httpModules>



The syntax for registering a module is


Quote

<httpModules>
<add name="YourName" type="YourNamespace.YourClassName, YourProjectName"/>
</httpModules>


There you have it, a way to fight session hijacking in your ASP.NET applications. Remember, there is no 100% foolproof way to prevent this, this class is simply meant as one way to make it harder for hackers to hijack your users sessions, and thus giving them access to the users information. I provide no warranty, either implicit or not, with this code, and I make no guarantees of any kind. When it comes to dealing with security all you can hope to do is to make it harder for hackers to do their job.

I hope you found this tutorial informative and useful. Thank you for reading and see you soon :)

Happy Coding!

Is This A Good Question/Topic? 2
  • +

Replies To: Guarding Against Session Hijacking in ASP.NET

#2 gbertoli3  Icon User is offline

  • DIC at Heart + Code
  • member icon

Reputation: 40
  • View blog
  • Posts: 1,162
  • Joined: 23-June 08

Posted 25 August 2008 - 08:25 PM

I don't know a lot of ASP.NET, but I know enough to know that this works.
GREAT POST!

This post has been edited by gbertoli3: 25 August 2008 - 08:26 PM

Was This Post Helpful? 0
  • +
  • -

#3 jdemarco7751  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 10-October 08

Posted 19 February 2009 - 06:24 AM

Extremely well done. I'll be using this in all my work.
Was This Post Helpful? 0
  • +
  • -

#4 fete  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 10-June 09

Posted 10 June 2009 - 05:14 AM

Great post, but IP address can be changed a few times during short period of time (for example 5 min), if the user is logged in (AOL, mobile phone connection, etc.)
Was This Post Helpful? 0
  • +
  • -

#5 fracky  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 07-March 10

Posted 07 March 2010 - 02:47 PM

Hmm, did this and compiles OK.

For some reason, with my existing cookie, it returns the "Invalid Session" in this block:

'check to see if the MAC's match, if not we have a problem
If [String].CompareOrdinal(mac, macCompare) <> 0 Then
Throw New SessionerrorException("Invalid Session")
End If

Within the "onbeginRequest" Sub.

Can you show us what you're using to issue the cookie with and any code/help if any?

Cheers,

Fraser
Was This Post Helpful? 0
  • +
  • -

#6 Guest_Freda*


Reputation:

Posted 09 April 2010 - 05:41 AM

I implemented the code in my web project and for most pages the SecureSession module works fine but when I use it on a page containing a gridview with a sqldatasource the cookies no longer have MACs attached in the onbeginRequest event!
Any ideas?
Here’s hoping!
Was This Post Helpful? 0

#7 Guest_Guest*


Reputation:

Posted 17 June 2010 - 11:52 AM

View Postfracky, on 07 March 2010 - 01:47 PM, said:

For some reason, with my existing cookie, it returns the "Invalid Session" in this block:


Yup, same happens for me. And as I look at this code, it seems the author doesn't show how the cookie first gets the appended MAC. I can only assume it occurs elsewhere in his application because I noticed the same thing.
Was This Post Helpful? 0

#8 gugujiao  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 11-August 11

Posted 11 August 2011 - 10:08 PM

View PostGuest, on 17 June 2010 - 11:52 AM, said:

View Postfracky, on 07 March 2010 - 01:47 PM, said:

For some reason, with my existing cookie, it returns the "Invalid Session" in this block:


Yup, same happens for me. And as I look at this code, it seems the author doesn't show how the cookie first gets the appended MAC. I can only assume it occurs elsewhere in his application because I noticed the same thing.


so you have any idea how to solve that? i am at learning curve XD
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1