登录后如何刷新 JSESSIONID cookie

Posted

技术标签:

【中文标题】登录后如何刷新 JSESSIONID cookie【英文标题】:how to refresh JSESSIONID cookie after login 【发布时间】:2011-12-31 01:11:42 【问题描述】:

我工作的一个产品得到了潜在客户的严格安全审核,他们对 Tomcat 在身份验证发生之前设置了一个 JSESSIONID cookie 感到不安。也就是说,Tomcat 在我们的无状态登录页面加载时设置此 cookie,但在登录之前。

他们提出以下任一建议:

    登录后发出新的 JSESSIONID cookie 首先防止在登录页面上设置 JSESSIONID cookie(即在进行身份验证之前)

我一直在仔细研究此站点上与 JSESSIONID 相关的所有内容,但找不到简单的答案。我只是希望有一些想法。我对每个问题的最佳解决方案是:

    登录后立即克隆会话(减去 id),方法是复制所有属性、使旧会话无效、创建新会话、复制值、将其与请求相关联,并希望它能正常工作。 在链的最末端创建一个 servlet 过滤器,在初始加载登录页面之前去除 JSESSIONID cookie。然后希望登录请求能够在没有设置 JSESSIONID 的情况下成功。

我得睡一觉,但会在早上尝试这些。从比我聪明得多的人那里获得一些反馈或更好的建议会很棒——比如你!

无论如何,我会在这里发布我的结果,因为似乎很多其他人都想做类似的事情。

【问题讨论】:

【参考方案1】:

问题是 JSESSIONID 在浏览器中可见还是完全设置在 cookie 中?我假设你的情况是后者。

1.登录后发出新的JSESSIONID cookie

这是默认的 Tomcat 行为如果您在登录时从 http 切换到 https。旧的被丢弃并生成一个新的。

如果您的登录本身是通过 http 进行的,我想这对审计员来说是另一个安全问题;)

或者你所有的页面都是通过 https 的?

【讨论】:

问题是JSESSIONID cookie是在浏览器中设置的,并且在Firefox cookie查看器中可见(例如)。正如 cherouvim 上面指出的,这是“会话固定”安全漏洞。有趣的是 http/https 开关...不过,该应用程序仅在 https 上运行。【参考方案2】:

您不会在之后刷新,而是在之前刷新。执行登录操作时首先执行:

HttpSession session = request.getSession(false);
if (session!=null && !session.isNew()) 
    session.invalidate();

然后做:

HttpSession session = request.getSession(true); // create the session
// do the login (store the user in the session, or whatever)

仅供参考,您使用此技巧解决的问题是 http://www.owasp.org/index.php/Session_Fixation

最后,您可以禁用自动会话创建,仅在您真正需要时才创建会话。如果你使用 JSP,你可以这样做:

<%@page contentType="text/html"
        pageEncoding="UTF-8"
        session="false"%>

【讨论】:

有趣的东西。我正在使用 Wicket 1.3,但我似乎找不到设置“session=false”的方法(视图端不是基于 JSP 的)。我现在要尝试 getSession(false) 的想法...谢谢! 在登录前使会话无效会导致 Wicket 混乱(只是不断重定向到登录页面)。还在纠结这个... @RichardsonHeights:看看issues.apache.org/jira/browse/WICKET-1767。它似乎已记录并已解决。 感谢您的帮助。我能够将 Wicket 修复移植到我们正在运行的旧版本的 Wicket,它基本上解决了这个问题。它在我们的传统自定义身份验证方案中引发了一些其他问题,但这应该适用于一般使用 Wicket 的任何人。 @cherouvim 我非常感谢你,我花了几个小时调试.. 我有另一个 owasp 链接,这让我很困惑,因为它说“登录后应该重新生成会话 ID”:@987654323 @.. 再次感谢!【参考方案3】:

我发现有两件事可能对其他人有所帮助。

    如果您使用的是 Apache Wicket,则在 1.4 版之后有一个解决方案。我的应用程序仍在 1.3 上,所以我没有意识到,但我能够在我自己的 WebSession 类中非常轻松地向后移植它。 Wicket 1.4 为 WebSession 添加了一个 replaceSession() 方法,效果很好。您可以在身份验证后立即调用它,您将获得一个新的 JSESSIONID。它基本上为我解决了这个问题。更多信息在这里:https://issues.apache.org/jira/browse/WICKET-1767。

    版本 5.5.29 之后有一个可用的 Apache Tomcat 阀门,您可以将其添加到 context.xml。它将在身份验证后处理发布一个新的 JSESSIONID。更多信息可在此处获得:https://issues.apache.org/bugzilla/show_bug.cgi?id=45255。阀门的条目如下所示:&lt;Valve className="org.apache.catalina.authenticator.FormAuthenticator" changeSessionIdOnAuthentication="true"/&gt;

【讨论】:

【参考方案4】:

我无法对@cherouvim 的上述回答发表评论,因为我没有足够的分数。新的会话 ID 应在用户成功登录后设置,以避免会话固定。我会尝试解释我的推理。

会话固定实际上意味着攻击者以某种方式欺骗用户使用攻击者已知的值。为简单起见,我们假设攻击者走到用户的办公桌前,使用 Firebug 并编辑了用户的 cookie。现在,当用户登录时,他/她将使用攻击者控制的 cookie 登录。由于攻击者也知道这个值,他/她将刷新他们的浏览器,并将映射到该会话 ID 的资源(受害者的资源)提供给他们。那是会话固定。对吗?

现在假设我们在受害者用户登录之前运行了 session.invalidate。假设 cookie 最初的值为 abc。在运行 session.invalidate 时,值 abc 会从服务器的会话中清除。

现在是我不同意的部分。您的建议是在用户实际登录之前生成一个新会话(输入用户名和密码并单击提交)。这无疑会导致生成一个新的 cookie,但它会在用户登录之前出现在用户的浏览器上。因此,如果攻击者可以再次编辑“预登录”cookie,攻击仍然存在,因为即使在用户登录后也会使用相同的 cookie。

我认为这是正确的流程。

用户执行 GET /login.html 使用浏览器中当前存在的任何 cookie 返回登录页面 用户输入凭据并点击提交 应用程序验证凭据 发现凭据正确。 session.invalidate() 正在运行..破坏旧的 cookie。 现在使用 request.getSession(true) 生成新的 cookie

这意味着,即使攻击者设法在登录之前诱骗您使用受控值,您仍然受到保护..因为应用程序会在您登录后强行更改值。

这是一个关于这个问题的好博客 - https://blog.whitehatsec.com/tag/session-fixation/

【讨论】:

【参考方案5】:

我按照以下方式从旧会话重新生成新会话。希望您能从中受益。

private void regenerateSession(HttpServletRequest request) 

    HttpSession oldSession = request.getSession();

    Enumeration attrNames = oldSession.getAttributeNames();
    Properties props = new Properties();

    if (attrNames != null) 
        while (attrNames.hasMoreElements()) 
            String key = (String) attrNames.nextElement();
            props.put(key, oldSession.getAttribute(key));
        

        //Invalidating previous session
        oldSession.invalidate();
        //Generate new session
        HttpSession newSession = request.getSession(true);
        attrNames = props.keys();

        while (attrNames.hasMoreElements()) 
            String key = (String) attrNames.nextElement();
            newSession.setAttribute(key, props.get(key));
        
    

【讨论】:

【参考方案6】:
session=request.getSession(true);
Enumeration keys = session.getAttributeNames();     
HashMap<String,Object> hm=new HashMap<String,Object>();  
while (keys.hasMoreElements())

  String key = (String)keys.nextElement();
  hm.put(key,session.getValue(key));
  session.removeAttribute(key);      

session.invalidate();
session=request.getSession(true);
for(Map.Entry m:hm.entrySet())

  session.setAttribute((String)m.getKey(),m.getValue());  
  hm.remove(m);
  

【讨论】:

【参考方案7】:

使用spring时,应该使用SessionFixationProtectionStrategy

<property name="sessionAuthenticationStrategy" ref="sas"/>
...
<bean id="sas" class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"/>

当检查source code时,你会发现这和harsha89的做法类似:会

    创建一个新会话 传输旧会话的属性。

【讨论】:

【参考方案8】:

如果您正在使用 Tomcat 并希望将此全局应用于所有使用 Tomcat 身份验证机制的 servlet,您可以编写一个 Valve 来强制执行此行为,如 sample code. 所示

【讨论】:

【参考方案9】:

如果您使用的是 jboss 4 等旧版本的 jboss,那么在 session.invalidate() 调用之后简单地调用 request.getSession(true) 不会更改会话 ID。

如果您不想使用 Valve 并且想要更改操作类中的会话 ID,则可以使用反射存档,因为 CatalinaRequest 将无法直接在您的操作类中使用。

示例代码

private HttpSession changeSessionId( HttpServletRequest request )

    HttpSession oldSession = request.getSession( false );
    HttpSession newSession = null;

    try
    
        //get all cookies from request
        Cookie[] cookies = request.getCookies();

        //Get all attribute from old session
        Enumeration< Object > attrNames = oldSession.getAttributeNames();

        Properties attributFromOldSession = new Properties();

        while ( attrNames.hasMoreElements() )
        
            String key = (String)attrNames.nextElement();
            attributFromOldSession.put( key, oldSession.getAttribute( key ) );
        

        //Actual logic to change session id

        Field catalinaRequestField;

        //Getting actual catalina request using reflection
        catalinaRequestField = request.getClass().getDeclaredField( "request" );
        catalinaRequestField.setAccessible( true ); // grant access to (protected) field
        Request realRequest = (Request)catalinaRequestField.get( request );

        //Invalidating actual request
        realRequest.getSession( true ).invalidate();
        realRequest.setRequestedSessionId( null );
        realRequest.clearCookies();

        //setting new session Id
        realRequest.setRequestedSessionId( realRequest.getSessionInternal( true ).getId() );

        //Put back the cookies
        for ( Cookie cookie : cookies )
        

            if ( !"JSESSIONID".equals( cookie.getName() ) )
            
                realRequest.addCookie( cookie );
            
        

        // put attribute from old session
        attrNames = attributFromOldSession.keys();

        while ( attrNames.hasMoreElements() )
        
            String key = (String)attrNames.nextElement();
            newSession.setAttribute( key, attributFromOldSession.get( key ) );
        
    
    catch ( Exception e )
    
        e.printStackTrace();
    
    return newSession;


【讨论】:

【参考方案10】:

HttpServletRequest.changeSessionId() 可用于随时更改会话 ID。

【讨论】:

这是 Tomcat 新版本的正确答案

以上是关于登录后如何刷新 JSESSIONID cookie的主要内容,如果未能解决你的问题,请参考以下文章

JSESSIONID 是如何传递的?作为标头参数还是作为 cookie 参数?

jmeter动态获取jsessionid

Soapui接口测试之cookie设置

xss?=客户端拒绝服务

Chrome 关闭后 Jsessionid cookie 不会过期

如果登录尝试中我的应用程序的浏览器中没有以前的 JSESSIONID cookie,则 springSecurityService.reauthenticate 不起作用