题 在当前HTTPContext中生成新的ASP.NET会话


由于对我们管道中的一些产品进行了渗透测试,当时看起来很容易解决的“容易”问题变得非常棘手。

当然不应该这样,我的意思是为什么会这样 为当前产生一个全新的会话 HTTPContext 这么难吗?离奇!无论如何 - 我写了一个厚脸皮的小实用工具类来“做它”:

(代码格式化/突出显示/ Visual Basic I道歉 必须 做错了什么)


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 (一个 HTTPSessionState object)用正确的方式标识自己 SessionID,但有 IsNewSession 设置 True,并为空,丢失上一个请求中设置的所有会话值。

所以前面必须有一些特别之处 HTTPSessionState 从初始请求中删除对象,这里是一个事件处理程序,那里是一个回调函数,它处理跨请求持久化会话数据的东西,或者只是我缺少的东西?

有人有分享的魔力吗?


25
2017-09-02 15:29


起源


我进化了我的 SwitchSession 给它一些状态( replacement 会话)和接线 SessionStateModule 活动ASP.NET应用程序实例的事件。当。。。的时候 Start 事件触发,它检查ASP.NET生成的会话是否具有相同的内容 SessionID 并将前一个请求中的所有会话状态值复制到其中。显然只有在所有请求都通过的情况下才有效 HTTPApplication 处理上一个请求的实例。我正在用反射器深入挖掘 SessionStateModule,但它不漂亮。请投票这个问题! - Rabid
我到达的地方几乎和你一样(通过搜索RemoveHttpSessionStateFromContext到达你的页面)。不幸的是,也和你一样撞墙 - 似乎无法获得新的会话。关键当然是SessionStateModule.CompleteAcquiredState(),这是非常难以达到的目标--Yudhi的反思方法是达到它的一种方式,但我不确定这是值得的麻烦。我必须说,就像我喜欢C#一样,.NET的API令人非常失望 - 他们怎么能不公开这个! - marq
FYI:CompleteAcquiredState()调用SessionStateUtility.AddDelayedHttpSessionStateToContext(),它为新会话完成所有魔术。 - marq


答案:


我想分享我的魔力。实际上,不,它还不是神奇的......我们应该更多地测试和改进代码。 我只在with-cookie,InProc会话模式下测试了这些代码。将这些方法放在您的页面中,并在需要重新生成ID的位置调用它(请将您的Web应用程序设置为完全信任):

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内部字段,因此它将当前Session保存为新ID。也许当前的HttpSessionState对象仍然具有先前的Id,但AFAIK的SessionStateModule忽略了它。当它必须在某处保存状态时,它只使用内部_rqId字段。我已经尝试过其他方法,比如将SessionStateModule复制到具有重新生成ID功能的新类中(我计划用此类替换SessionStateModule),但是因为它当前具有对其他内部类(如InProcSessionStateStore)的引用而失败。使用反射进行黑客攻击的缺点是我们需要将应用程序设置为“完全信任”。

哦,如果你真的需要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

32
2017-12-12 02:24



感谢您的贡献,我的结论是类似的,会话状态模块的本机行为需要操作。提升到完全信任权限是一种耻辱,特别是因为它是修复安全问题(这里的问题是iirc是会话固定)。干得好:) - Rabid
效果很好。 Session.SessionID保留请求剩余部分的旧值。有更新吗? - Vaibhav Garg
+1并感谢一堆这段代码!我在从当前上下文创建的SimpleWorkerRequest上使用HttpRuntime.ProcessRequest()生成子请求时出现问题 - 如果先前的请求已写入Session,则请求开始出错。在子请求执行代码期间使用您的代码切换ID解决了该问题。 - mawtex
我知道这个线程有点旧,但我一直在使用这个函数(C#),并且它工作正常除了Session_End事件处理程序仍然在其中一个会话上被调用。这导致我们的应用程序出现问题,因为我们在Session_End中有一些特殊的登录,并且它会过早地被调用。 - Matthew
伙计,你是英雄 - pinkdawn


正如Can Gencer所提到的 - ReleaseItemExclusive不会从商店中删除旧会话,这会导致该会话最终到期并在Global.asax中调用Session_End。这导致我们在生产中出现了一个巨大的问题,因为我们正在清除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

1
2018-03-17 11:46



Max,你能把它翻译成C#吗? - vkelman


你有没有考虑过使用 HttpSessionState.Abandon方法?这应该清除一切。然后开始一个新会话,并使用上面代码中存储的所有项目填充它。

Session.Abandon(); 应该足够了。否则,如果它仍然顽固,你可以尝试多打几个电话:

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

0
2017-09-02 15:46



不幸的是,这种方法似乎重用相同的会话标识符。我需要生成一个新的会话标识符:( - Rabid


你能不能只设置:

<sessionState regenerateExpiredSessionId="False" />

在web.config中,然后使用Ahmad建议的解决方案?


0
2017-09-10 13:34



不幸的是,它仍然保持活跃 SessionID,在下一个请求命中时使用。在这种情况下,'expired'的含义是什么?我还没有使会话过期 - 除了使客户端cookie过期之外,我看不到让会话“过期”的方法。这对我的活跃无益 HTTPContext 虽然。但即使如此,使用此方法 - 在我放弃会话并删除所有内容后,我会对我的用户进行身份验证并在会话中放置一些内容,因为会话已被“放弃”,在下一个请求时它是空的。 :( :( :( - Rabid


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

0
2018-06-19 09:49



-1:这有什么不同 stackoverflow.com/a/4420114/76337? - John Saunders