Подтвердить что ты не робот

Создание нового сеанса ASP.NET в текущем HTTPContext

В результате теста на проникновение в некоторые из наших продуктов в трубопроводе, то, что казалось в то время "легкой" проблемой для исправления, оказалось сложным.

Не то, чтобы это было, конечно, я имею в виду, почему бы просто создать совершенно новый сеанс для текущего HTTPContext настолько сложно? Bizarre! Во всяком случае, я написал наглый небольшой класс полезности, чтобы "просто сделать это":

(извинения за форматирование/выделение кода /Visual Basic я должен делать что-то не так)


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

Он отлично работает для остальной части запроса и правильно идентифицирует себя как новый сеанс (например, HTTPContext.Current.Session.SessionID возвращает вновь сгенерированный идентификатор сеанса).

Сюрприз удивляет то, что когда следующий запрос попадает на сервер, объект HTTPContext.Session (an HTTPSessionState) идентифицирует себя с правильным SessionID, но имеет IsNewSession установлен на True и пуст, потеряв все значения сеанса, установленные в предыдущем запросе.

Итак, должно быть что-то особенное относительно предыдущего объекта HTTPSessionState, удаляемого из первоначального запроса, обработчика события здесь, обратного вызова там, что-то, что обрабатывает сохранение данных сеанса через запросы или просто что-то, что мне не хватает?

У кого-нибудь есть какая-нибудь магия?

4b9b3361

Ответ 1

Я хотел бы поделиться своей магией. На самом деле, нет, это еще не волшебство. Нам следовало бы еще раз проверить и развить код. Я тестировал только этот код в режиме cookie InProc. Поместите этот метод на свою страницу и вызовите его там, где вам нужен идентификатор, который нужно восстановить (пожалуйста, установите для своего веб-приложения полное доверие):

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);
}

Я искал исходный код .NET(который был доступен в http://referencesource.microsoft.com/netframework.aspx) и обнаружил, что я не могу регенерировать SessionID без взлома внутренних механизмов управления сеансом. Поэтому я делаю именно это - взломать внутренние поля SessionStateModule, чтобы сохранить текущий сеанс в новом ID. Возможно, текущий объект HttpSessionState все еще имеет предыдущий идентификатор, но AFAIK SessionStateModule игнорирует его. Он просто использует внутреннее поле _rqId, когда он должен где-то сохранять состояние. Я пробовал другие средства, такие как копирование SessionStateModule в новый класс с функциональностью регенерированного идентификатора (я планировал заменить SessionStateModule этим классом), но не смог, потому что в настоящее время он имеет ссылки на другие внутренние классы (например, InProcSessionStateStore). Недостатком взлома с использованием рефлексии является то, что мы должны установить наше приложение в "Full Trust".

О, и если вам действительно нужна версия VB, попробуйте следующее:

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

Ответ 2

Как упоминал Gencer - ReleaseItemExclusive не удаляет старый сеанс из хранилища, а это приводит к тому, что этот сеанс заканчивается и вызывает Session_End в Global.asax. Это вызвало у нас огромную проблему в производстве, потому что мы очищаем идентификатор Thread в Session_End, и из-за этого пользователи спонтанно теряют аутентификацию в потоке.

Итак, приведенный исправленный код работает.

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

Ответ 3

Если вы уверены в безопасности и хотели бы, чтобы версия этого ответа на С# удаляла старое поле, используйте следующее.

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);
}

Ответ 4

Считаете ли вы использование метода HttpSessionState.Abandon? Это должно очистить все. Затем запустите новый сеанс и заполните его всеми элементами, которые вы сохранили из вашего кода выше.

Session.Abandon(); должно быть достаточно. В противном случае вы можете попытаться пройти лишнюю милю с еще несколькими вызовами, если она все еще упряма:

Session.Contents.Abandon();
Session.Contents.RemoveAll(); 

Ответ 5

Не можете ли вы просто установить:

<sessionState regenerateExpiredSessionId="False" />

в web.config, а затем используйте решение, предложенное Ahmad?

Ответ 6

Для MVC4, пожалуйста, используйте этот код:

 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);

Ответ 7

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);
}