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

Posted

技术标签:

【中文标题】在当前 HTTPContext 中生成新的 ASP.NET 会话【英文标题】:Generating a new ASP.NET session in the current HTTPContext 【发布时间】:2010-11-25 00:01:03 【问题描述】:

由于对我们正在开发中的一些产品进行了渗透测试,当时看起来“容易”解决的问题现在变成了棘手的问题。

当然不应该,我的意思是为什么为当前的HTTPContext 生成一个全新的会话会这么困难?奇怪!无论如何-我已经写了一个厚脸皮的小实用程序类来“做它”:

(为代码格式化/突出显示/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(一个HTTPSessionState 对象)将自己标识为正确的SessionID,但将IsNewSession 设置为True,并且是空的,丢失之前请求中设置的所有会话值。

因此,从初始请求中删除的先前 HTTPSessionState 对象一定有什么特别之处,这里有一个事件处理程序,那里有一个回调,处理跨请求持久化会话数据的东西,或者只是我缺少的东西?

有人有什么魔法可以分享吗?

【问题讨论】:

我通过给SwitchSession 类赋予一些状态(replacement 会话)并为活动的 ASP.NET 应用程序实例连接SessionStateModule 事件来改进我的SwitchSession 类。当Start 事件触发时,它会检查ASP.NET 生成的会话是否具有相同的SessionID,并将前一个请求中的所有会话状态值复制到其中。显然,只有当所有请求都来自处理前一个请求的HTTPApplication 实例时才有效。我正在使用反射器对SessionStateModule 进行更深入的挖掘,但它并不漂亮。请为这个问题投票! 我到了和你差不多的地方(通过搜索 RemoveHttpSessionStateFromContext 到达你的页面)。不幸的是,你也遇到了同样的问题 - 似乎无法生成新的会话。关键当然是 SessionStateModule.CompleteAcquiredState(),这很难实现 - Yudhi 的反射方法将是实现它的一种方法,但我不确定它是否值得麻烦。我必须说,尽管我非常喜欢 C#,但 .NET 的 API 却是一个巨大的失望——他们怎么能不公开这个! 仅供参考:CompleteAcquiredState() 调用 SessionStateUtility.AddDelayedHttpSessionStateToContext(),它为新会话发挥了所有作用。 【参考方案1】:

如果您有安全意识并希望此答案的 C# 版本删除旧字段,请使用以下内容。

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

【讨论】:

与accepted answer 相比,它捕获了_rqItem 对象,因此它可以调用store.RemoveItem 而不是store.ReleaseItemExclusive,这似乎是使旧会话失效的重要一步。【参考方案2】:

正如 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

【讨论】:

Max,你能把它翻译成C#吗?【参考方案3】:

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

【讨论】:

同意,这几乎与接受的答案完全相同。【参考方案4】:

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

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

【讨论】:

感谢您的贡献,我的结论是相似的,会话状态模块的本机行为需要进行操作。提升到完全信任权限是一种耻辱,尤其是在解决安全问题时(iirc 的问题是会话固定)。干得好:) 效果很好。 Session.SessionID 为请求的其余部分保留旧值。有更新吗? +1 非常感谢这段代码!我在从当前上下文创建的 SimpleWorkerRequest 上使用 HttpRuntime.ProcessRequest() 生成子请求时遇到问题 - 如果先前的请求已写入 Session,则请求开始出现异常。在子请求执行代码期间使用您的代码切换 ID 解决了该问题。 我知道这个线程有点老了,但我一直在使用这个函数 (C#),它工作正常,除了 Session_End 事件处理程序仍然在其中一个会话上被调用。这会导致我们的应用程序出现问题,因为我们在 Session_End 中有一些特殊的登录名并且它被过早地调用。 我看到您调用的是 store.ReleaseItemExclusive 而不是 store.RemoveItem。这样做的理由是什么? ReleaseItemExclusive 只是删除锁,但将旧会话保留在会话存储中。 RemoveItem 完全删除旧会话。【参考方案5】:

你可以不只是设置:

<sessionState regenerateExpiredSessionId="False" />

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

【讨论】:

不幸的是,它仍然保留活动的SessionID,当下一个请求命中时会使用它。但是,在这种情况下,“过期”是什么意思?我没有使会话过期-除了使客户端 cookie 过期之外,我看不到使会话“过期”的方法。不过,这对我的活跃HTTPContext 没有帮助。但即便如此,使用这种方法——在我放弃会话并删除所有内容之后,我验证我的用户并在会话中放置一些东西,因为会话已被“放弃”,在下一个请求时它是空的。 :( :( :( 【参考方案6】:

您是否考虑过使用HttpSessionState.Abandon method?那应该清除一切。然后启动一个新会话并使用您从上面的代码中存储的所有项目填充它。

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

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

【讨论】:

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

以上是关于在当前 HTTPContext 中生成新的 ASP.NET 会话的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Binance 中生成新的存款 Tether 地址

在经纬度中添加 X 米并在 PHP 中生成新的经纬度

React 和 Socket.io | useState 在输入中生成新的套接字连接 onChange

C#和sqlserver中生成新的32位GUID

从已有container中生成新的image&打标签——Creating a Docker Image from an Existing Container

.Net Core在类库中使用当前HttpContext