29

As a result of a penetration test against some of our products in the pipeline, what looked to be at the time an 'easy' problem to fix is turning out to be a toughy.

Not that it should of course, I mean why would just generating a brand new session for the current HTTPContext be so difficult? Bizarre! Anyway- I've written a cheeky little utility class to "just do it":

(apologies for code formatting/highlighting/Visual Basic I must be doing something wrong)


Imports System.Web
Imports System.Web.SessionState

Public Class SwitchSession

    Public Shared Sub SetNewSession(ByVal context As HttpContext)
        ' This value will hold the ID managers action to creating a response cookie
        Dim cookieAdded As Boolean
        ' We use the current session state as a template
        Dim state As HttpSessionState = context.Session
        ' We use the default ID manager to generate a new session id
        Dim idManager As New SessionIDManager()
        ' We also start with a new, fresh blank state item collection
        Dim items As New SessionStateItemCollection()
        ' Static objects are extracted from the current session context
        Dim staticObjects As HttpStaticObjectsCollection = _
            SessionStateUtility.GetSessionStaticObjects(context)
        ' We construct the replacement session for the current, some parameters are new, others are taken from previous session
        Dim replacement As New HttpSessionStateContainer( _
                 idManager.CreateSessionID(context), _
                 items, _
                 staticObjects, _
                 state.Timeout, _
                 True, _
                 state.CookieMode, _
                 state.Mode, _
                 state.IsReadOnly)
        ' Finally we strip the current session state from the current context
        SessionStateUtility.RemoveHttpSessionStateFromContext(context)
        ' Then we replace the assign the active session state using the replacement we just constructed
        SessionStateUtility.AddHttpSessionStateToContext(context, replacement)
        ' Make sure we clean out the responses of any other inteferring cookies
        idManager.RemoveSessionID(context)
        ' Save our new cookie session identifier to the response
        idManager.SaveSessionID(context, replacement.SessionID, False, cookieAdded)
    End Sub

End Class

It works fine for the remainder of the request, and correctly identifies itself as the new session (e.g. HTTPContext.Current.Session.SessionID returns the newly generated session identifier).

Surprise surprise then, that when the next request hits the server, the HTTPContext.Session (an HTTPSessionState object) identifies itself with the correct SessionID, but has IsNewSession set to True, and is empty, losing all the session values set in the previous request.

So there must be something special about the previous HTTPSessionState object being removed from the initial request, an event handler here, a callback there, something which handles persisting the session data across requests, or just something I'm missing?

Anybody got any magic to share?

Rabid
  • 2,984
  • 2
  • 25
  • 25
  • 1
    I evolved my `SwitchSession` class by giving it some state (the `replacement` session) and wiring up `SessionStateModule` events for the active ASP.NET application instance. When the `Start` event fires, it checks to see if the ASP.NET spawned session has the same `SessionID` and copies all of the session state values from the previous request into it. Obviously only works if all requests come through the `HTTPApplication` instance that handled the previous request. I'm using reflector to dig a little deeper into the `SessionStateModule`, but its not pretty. Please vote up this question! – Rabid Sep 10 '09 at 09:43
  • I got to pretty much the same place as you (arrived at your page by doing a search for RemoveHttpSessionStateFromContext). Unfortunately, also hit the same wall as you - can't seem to get a new session generated. The key is of course SessionStateModule.CompleteAcquiredState(), which is extremely difficult to get to - Yudhi's reflection approach would be one way of getting to it, but I'm not sure it's worth the hassle. I must say that as much as I love C#, .NET's API has been a huge disappointment - how can they not expose this! – marq Jul 20 '11 at 16:56
  • FYI: CompleteAcquiredState() calls SessionStateUtility.AddDelayedHttpSessionStateToContext(), which does all the magic for a new session. – marq Jul 20 '11 at 17:01

6 Answers6

42

I would like to share my magic. Actually, no, its not yet magical.. We ought to test and evolve the code more. I only tested these code in with-cookie, InProc session mode. Put these method inside your page, and call it where you need the ID to be regenerated (please set your web app to Full Trust):

void regenerateId()
{
    System.Web.SessionState.SessionIDManager manager = new System.Web.SessionState.SessionIDManager();
    string oldId = manager.GetSessionID(Context);
    string newId = manager.CreateSessionID(Context);
    bool isAdd = false, isRedir = false;
    manager.SaveSessionID(Context, newId, out isRedir, out isAdd);
    HttpApplication ctx = (HttpApplication)HttpContext.Current.ApplicationInstance;
    HttpModuleCollection mods = ctx.Modules;
    System.Web.SessionState.SessionStateModule ssm = (SessionStateModule)mods.Get("Session");
    System.Reflection.FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    SessionStateStoreProviderBase store = null;
    System.Reflection.FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null;
    foreach (System.Reflection.FieldInfo field in fields)
    {
        if (field.Name.Equals("_store")) store = (SessionStateStoreProviderBase)field.GetValue(ssm);
        if (field.Name.Equals("_rqId")) rqIdField = field;
        if (field.Name.Equals("_rqLockId")) rqLockIdField = field;
        if (field.Name.Equals("_rqSessionStateNotFound")) rqStateNotFoundField = field;
    }
    object lockId = rqLockIdField.GetValue(ssm);
    if ((lockId != null) && (oldId !=null)) store.ReleaseItemExclusive(Context, oldId, lockId);
    rqStateNotFoundField.SetValue(ssm, true);
    rqIdField.SetValue(ssm, newId);
}

I have been digging around .NET Source code (that were available in http://referencesource.microsoft.com/netframework.aspx), and discovered that there is no way I could regenerate SessionID without hacking the internals of session management mechanism. So I do just that - hack SessionStateModule internal fields, so it will save the current Session into a new ID. Maybe the current HttpSessionState object still has the previous Id, but AFAIK the SessionStateModule ignored it. It just use the internal _rqId field when it has to save the state somewhere. I have tried other means, like copying SessionStateModule into a new class with a regenerate ID functionality, (I was planning to replace SessionStateModule with this class), but failed because it currently has references to other internal classes (like InProcSessionStateStore). The downside of hacking using reflection is we need to set our application to 'Full Trust'.

Oh, and if you really need the VB version, try these :

Sub RegenerateID()
    Dim manager
    Dim oldId As String
    Dim newId As String
    Dim isRedir As Boolean
    Dim isAdd As Boolean
    Dim ctx As HttpApplication
    Dim mods As HttpModuleCollection
    Dim ssm As System.Web.SessionState.SessionStateModule
    Dim fields() As System.Reflection.FieldInfo
    Dim rqIdField As System.Reflection.FieldInfo
    Dim rqLockIdField As System.Reflection.FieldInfo
    Dim rqStateNotFoundField As System.Reflection.FieldInfo
    Dim store As SessionStateStoreProviderBase
    Dim field As System.Reflection.FieldInfo
    Dim lockId
    manager = New System.Web.SessionState.SessionIDManager
    oldId = manager.GetSessionID(Context)
    newId = manager.CreateSessionID(Context)
    manager.SaveSessionID(Context, newId, isRedir, isAdd)
    ctx = HttpContext.Current.ApplicationInstance
    mods = ctx.Modules
    ssm = CType(mods.Get("Session"), System.Web.SessionState.SessionStateModule)
    fields = ssm.GetType.GetFields(System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
    store = Nothing : rqLockIdField = Nothing : rqIdField = Nothing : rqStateNotFoundField = Nothing
    For Each field In fields
        If (field.Name.Equals("_store")) Then store = CType(field.GetValue(ssm), SessionStateStoreProviderBase)
        If (field.Name.Equals("_rqId")) Then rqIdField = field
        If (field.Name.Equals("_rqLockId")) Then rqLockIdField = field
        If (field.Name.Equals("_rqSessionStateNotFound")) Then rqStateNotFoundField = field
    Next
    lockId = rqLockIdField.GetValue(ssm)
    If ((Not IsNothing(lockId)) And (Not IsNothing(oldId))) Then store.ReleaseItemExclusive(Context, oldId, lockId)
    rqStateNotFoundField.SetValue(ssm, True)
    rqIdField.SetValue(ssm, newId)

End Sub
YudhiWidyatama
  • 1,684
  • 16
  • 14
  • 1
    Thanks for your contribution, my conclusion was similar, that the native behaviour of the session state module would require manipulating. The elevation to full trust permissions is a shame, especially as it was to fix a security problem (the problem here iirc is session fixation). Good work though :) – Rabid Jan 03 '11 at 11:33
  • Works great. Session.SessionID retains the old value for the remainder of the request. Is there any update? – Vaibhav Garg Feb 14 '11 at 12:11
  • +1 and thanks a bunch for this code! I had an issue with spawning a child request using HttpRuntime.ProcessRequest() on a SimpleWorkerRequest created from the current context - requests began misbehaving if a previous request had written to Session. Using your code to switch ID during the child request execution code solved that issue. – mawtex Aug 12 '11 at 13:53
  • I know this thread is kind-of old, but I've been using this function (C#), and it works fine EXCEPT that Session_End event handler still gets called on one of the sessions. This causes a problem in our application as we have some special login in Session_End and it gets called prematurely. – Matthew Aug 29 '11 at 13:33
  • I see that you're calling store.ReleaseItemExclusive instead of store.RemoveItem. What is the rationale for that? ReleaseItemExclusive simply removes the lock, but keeps the old session around in the session store. RemoveItem removes the old session entirely. – Mark Shapiro Jul 19 '12 at 13:35
  • This approach shall surely lead us to the Session ID promised land :) – Rabid Jan 10 '13 at 09:30
  • This doesn't actually remove the old session and is still vulnerable to session fixation. You should be using `store.RemoveItem` instead. The `SessionStateStoreData` parameter is accessible as the `_rqItem` field. – Can Gencer Mar 21 '13 at 14:14
  • I am using this code, but any session values I set are being lost at the next page load, and there is another new session ID in an empty session. I do not understand why this is happening?!! – Nevyn Sep 26 '16 at 18:42
  • This has potentially solved a nasty pen test issue for me. Thanks! – PKD May 23 '22 at 18:26
5

If you're security concious and would like the C# version of this answer removing the old field, please use the following.

private static void RegenerateSessionId()
{

    // Initialise variables for regenerating the session id
    HttpContext Context = HttpContext.Current;
    SessionIDManager manager = new SessionIDManager();
    string oldId = manager.GetSessionID(Context);
    string newId = manager.CreateSessionID(Context);
    bool isAdd = false, isRedir = false;

    // Save a new session ID
    manager.SaveSessionID(Context, newId, out isRedir, out isAdd);

    // Get the fields using the below and create variables for storage
    HttpApplication ctx = HttpContext.Current.ApplicationInstance;
    HttpModuleCollection mods = ctx.Modules;
    SessionStateModule ssm = (SessionStateModule)mods.Get("Session");
    FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    SessionStateStoreProviderBase store = null;
    FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null;
    SessionStateStoreData rqItem = null;

    // Assign to each variable the appropriate field values
    foreach (FieldInfo field in fields)
    {
        if (field.Name.Equals("_store")) store = (SessionStateStoreProviderBase)field.GetValue(ssm);
        if (field.Name.Equals("_rqId")) rqIdField = field;
        if (field.Name.Equals("_rqLockId")) rqLockIdField = field;
        if (field.Name.Equals("_rqSessionStateNotFound")) rqStateNotFoundField = field;
        if (field.Name.Equals("_rqItem")) rqItem = (SessionStateStoreData)field.GetValue(ssm);
    }

    // Remove the previous session value
    object lockId = rqLockIdField.GetValue(ssm);
    if ((lockId != null) && (oldId != null))
        store.RemoveItem(Context, oldId, lockId, rqItem);

    rqStateNotFoundField.SetValue(ssm, true);
    rqIdField.SetValue(ssm, newId);
}
DubDub
  • 1,277
  • 1
  • 10
  • 24
  • 1
    In comparison with the [accepted answer](https://stackoverflow.com/a/4420114/295686) this captures the _rqItem object so it can call [store.RemoveItem](https://learn.microsoft.com/en-us/dotnet/api/system.web.sessionstate.sessionstatestoreproviderbase.removeitem?view=netframework-4.8) instead of [store.ReleaseItemExclusive](https://learn.microsoft.com/en-us/dotnet/api/system.web.sessionstate.sessionstatestoreproviderbase.releaseitemexclusive?view=netframework-4.8) which appears to be an important step towards invalidating the old session. – mlhDev Oct 10 '19 at 13:53
3

As Can Gencer mentioned - ReleaseItemExclusive doesn't remove old session from the store and that leads to that session eventually expiring and calling Session_End in Global.asax. This caused us a huge problem in production, because we are clearing Thread identity in Session_End, and because of this - users were spontaneously losing authentication on thread.

So below is the corrected code that works.

Dim oHTTPContext As HttpContext = HttpContext.Current

Dim oSessionIdManager As New SessionIDManager
Dim sOldId As String = oSessionIdManager.GetSessionID(oHTTPContext)
Dim sNewId As String = oSessionIdManager.CreateSessionID(oHTTPContext)

Dim bIsRedir As Boolean = False
Dim bIsAdd As Boolean = False
oSessionIdManager.SaveSessionID(oHTTPContext, sNewId, bIsRedir, bIsAdd)

Dim oAppContext As HttpApplication = HttpContext.Current.ApplicationInstance

Dim oModules As HttpModuleCollection = oAppContext.Modules

Dim oSessionStateModule As SessionStateModule = _
  DirectCast(oModules.Get("Session"), SessionStateModule)

Dim oFields() As FieldInfo = _
  oSessionStateModule.GetType.GetFields(BindingFlags.NonPublic Or _
                                        BindingFlags.Instance)

Dim oStore As SessionStateStoreProviderBase = Nothing
Dim oRqIdField As FieldInfo = Nothing
Dim oRqItem As SessionStateStoreData = Nothing
Dim oRqLockIdField As FieldInfo = Nothing
Dim oRqStateNotFoundField As FieldInfo = Nothing

For Each oField As FieldInfo In oFields
    If (oField.Name.Equals("_store")) Then
        oStore = DirectCast(oField.GetValue(oSessionStateModule), _
                            SessionStateStoreProviderBase)
    End If
    If (oField.Name.Equals("_rqId")) Then
        oRqIdField = oField
    End If
    If (oField.Name.Equals("_rqLockId")) Then
        oRqLockIdField = oField
    End If
    If (oField.Name.Equals("_rqSessionStateNotFound")) Then
        oRqStateNotFoundField = oField
    End If
    If (oField.Name.Equals("_rqItem")) Then
        oRqItem = DirectCast(oField.GetValue(oSessionStateModule), _
                             SessionStateStoreData)
    End If
Next

If oStore IsNot Nothing Then

    Dim oLockId As Object = Nothing

    If oRqLockIdField IsNot Nothing Then
        oLockId = oRqLockIdField.GetValue(oSessionStateModule)
    End If

    If (oLockId IsNot Nothing) And (Not String.IsNullOrEmpty(sOldId)) Then
        oStore.ReleaseItemExclusive(oHTTPContext, sOldId, oLockId)
        oStore.RemoveItem(oHTTPContext, sOldId, oLockId, oRqItem)
    End If

    oRqStateNotFoundField.SetValue(oSessionStateModule, True)
    oRqIdField.SetValue(oSessionStateModule, sNewId)

End If
0

Have you considered using the HttpSessionState.Abandon method? That ought to clear everything. Then start a new session and populate it with all the items you stored from your code above.

Session.Abandon(); should suffice. Otherwise you could try to go the extra mile with a few more calls if it's still being stubborn:

Session.Contents.Abandon();
Session.Contents.RemoveAll(); 
Ahmad Mageed
  • 94,561
  • 19
  • 163
  • 174
  • 3
    Unfortunately this approach appears to reuse the same session identifier. I require a new session identifier to be generated :( – Rabid Sep 03 '09 at 09:08
0

Can you not just set:

<sessionState regenerateExpiredSessionId="False" />

in web.config, and then use the solution suggested by Ahmad?

Jason
  • 3,599
  • 10
  • 37
  • 52
  • 3
    Unfortunately not, it still retains the active `SessionID`, which gets used when the next request hits. What is the meaning of 'expired' in this context though? I haven't expired the session- I can't see a way to 'expire' the session other than expiring the client cookie. That doesn't help my active `HTTPContext` though. But even still, using this method- after I've abandoned the session and removed all the contents, I then authenticate my user and place something in the session, because the session has been 'abandoned', it is empty on the next request. :( :( :( – Rabid Sep 10 '09 at 15:54
-1

For MVC4 please have this code:

 System.Web.SessionState.SessionIDManager manager = new System.Web.SessionState.SessionIDManager();
            HttpContext Context = System.Web.HttpContext.Current;
            string oldId = manager.GetSessionID(Context);
            string newId = manager.CreateSessionID(Context);
            bool isAdd = false, isRedir = false;
            manager.SaveSessionID(Context, newId, out isRedir, out isAdd);
            HttpApplication ctx = (HttpApplication)System.Web.HttpContext.Current.ApplicationInstance;
            HttpModuleCollection mods = ctx.Modules;
            System.Web.SessionState.SessionStateModule ssm = (SessionStateModule)mods.Get("Session");
            System.Reflection.FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
            SessionStateStoreProviderBase store = null;
            System.Reflection.FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null;
            foreach (System.Reflection.FieldInfo field in fields)
            {
                if (field.Name.Equals("_store")) store = (SessionStateStoreProviderBase)field.GetValue(ssm);
                if (field.Name.Equals("_rqId")) rqIdField = field;
                if (field.Name.Equals("_rqLockId")) rqLockIdField = field;
                if (field.Name.Equals("_rqSessionStateNotFound")) rqStateNotFoundField = field;
            }
            object lockId = rqLockIdField.GetValue(ssm);
            if ((lockId != null) && (oldId != null)) store.ReleaseItemExclusive(Context, oldId, lockId);
            rqStateNotFoundField.SetValue(ssm, true);
            rqIdField.SetValue(ssm, newId);