Reputation: 3034
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?
Upvotes: 32
Views: 47636
Reputation: 3814
how to change session id after login in asp.net
For those that are searching now and seeing all those reflection hacks and are struggling with the session fixation issue. The only way to get to asp.net to create a new session id is to fake a SessionIDManager
that returns null for you login page. That way asp.net thinks the browser never sent a cookie.
Here are the steps:
CustomSessionIDManager
, pay attention to YourLoginController/LoginMethod
:public class CustomSessionIDManager : ISessionIDManager
{
private readonly SessionIDManager _sessionIDManager;
public CustomSessionIDManager()
{
_sessionIDManager = new SessionIDManager();
}
public string CreateSessionID(HttpContext context)
{
return _sessionIDManager.CreateSessionID(context);
}
public string GetSessionID(HttpContext context)
{
var path = context.Request.Path.ToString();
if (path.EndsWith("YourLoginController/LoginMethod"))
return null;
return _sessionIDManager.GetSessionID(context);
}
public void Initialize()
{
_sessionIDManager.Initialize();
}
public bool InitializeRequest(HttpContext context, bool suppressAutoDetectRedirect, out bool supportSessionIDReissue)
{
return _sessionIDManager.InitializeRequest(context, suppressAutoDetectRedirect, out supportSessionIDReissue);
}
public void RemoveSessionID(HttpContext context)
{
_sessionIDManager.RemoveSessionID(context);
}
public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
{
_sessionIDManager.SaveSessionID(context, id, out redirected, out cookieAdded);
}
public bool Validate(string id)
{
return _sessionIDManager.Validate(id);
}
}```
- Put it on the `web.config`:
<system.web>
<sessionState sessionIDManagerType="CustomSessionIDManager" />
</system.web>
Now it should return a new session for each login.
Upvotes: 0
Reputation: 1377
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);
}
Upvotes: 7
Reputation: 31
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
Upvotes: 3
Reputation: 17
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);
Upvotes: -1
Reputation: 1704
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
Upvotes: 44
Reputation: 3689
Can you not just set:
<sessionState regenerateExpiredSessionId="False" />
in web.config, and then use the solution suggested by Ahmad?
Upvotes: 0
Reputation: 96477
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();
Upvotes: 0