WEB安全实战(七)会话标识未更新




上一篇文章中,我们讨论了关于“浏览器记住用户名和密码的问题,至于这篇文章嘛,我想谈谈关于“会话标识”的漏洞。而且,这篇文章也是“Web安全实战”系列的最后一篇。为什么要拿到最后来说呢,其实,是之前一直困扰我的问题,这个问题曾一顿让我抓狂,n(n=3~5)多天一直没有解决,当时也是搜遍了各大网站,各大论坛,均未找到合适的解决方案。其中的过程就不再废话了,转到正题。


问题


先介绍一下问题是如何发现的,当然,这个不是我发现的,是我们测试部的童鞋发现的漏洞,然后她转交给我,让我去解决这个问题。刚开始也是一点思路都没有,然后就是一顿狂搜,后来慢慢发现,其实这个问题产生的根源,就是应用服务器(如 Tomcat)的 JSessionId 没有更新。只要使得旧的 Session 过期,重新生成新的 Session 即可。当然,这是理论上的解决方案,很想当然的。


曲折过程


有了思路之后,再想解决问题就容易了很多。于是,我就把范围固定在了如何让 Session 过期,当然,这样的解决方案网上有一箩筐,这里就不多说了,我拿到项目中试了试,结果你肯定能想到 —— 抛异常,异常原因是:...Session already invalidated 。这说明在程序中的某处,需要用到 Session 中的数据,但是此时 Session 已经过期了,无法取到数据。

于是,我开始找是哪里用到了 Session ,遗憾的是,我没能找到。心想,找不到 Session 我就换个思路吧,那就让 Session 过期之前,重新生成一个新的 Session ,把旧的 Session中的数据拷贝到新产生的 Session 中。这样就可以避免 Session 过期的问题了(理论上是这样)。

到这里,如果是一般的情况,就可以解决了,不过嘛,如果你的项目中还加入了安全框架 Shiro ,那么在 Session 的处理上,会有一些麻烦,因为 Shiro 也会有自己的 Session ,而且在认证的时候,Shiro 会有一些处理 Session 的操作,这就是导致 ...Session already invalidated 的原因所在。后来试了几种把旧 Session 数据转新 Session 数据的方案也不好使。


最终方案


一个偶然的机会,在网上找到了一篇《会话标识未更新》的文章,这篇文章就是介绍的在使用 Shiro 的情况下,如何解决这个漏洞,不过,情况不同的是,他使用的是应用服务器是 jBoss,而我们用的则是 Tomcat,本着试一试的态度,按照他给的思路,把自己的代码做了一下整理,把对 Session 操作的处理类封装成了一个 Filter,通过这个 Filter 对 Session 进行处理,下面请看相关代码。

代码如下

首先,增加一个新类,NewSessionFilter。

<span style="font-family:Comic Sans MS;">package com.test.web.common;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NewSessionFilter implements Filter {

	private String url;
	private static final Logger logger = LoggerFactory.getLogger(NewSessionFilter.class);
	public static final String NEW_SESSION_INDICATOR = "com.cacss.sc.web.common.NewSessionFilter";

	public static void newSession(){
		HttpSession session = (HttpSession) SecurityUtils.getSubject().getSession(true);
		session.setAttribute(NEW_SESSION_INDICATOR, true);
	}

	@Override
	public void destroy() {
		System.out.println("NewSessionFilter destory");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		System.out.println("NewSessionFilter doFilter");
		
		if (request instanceof HttpServletRequest) {
			HttpServletRequest httpRequest = (HttpServletRequest) request;
			
			//取的url相对地址
	        String url = httpRequest.getRequestURI();  
	        System.out.println(url);  
	        if (httpRequest.getSession() != null) {
	        	System.out.println("NewSessionFilter doFilter httpRequest.getSession().getId()"+ httpRequest.getSession().getId());
				//--------复制 session到临时变量
				HttpSession session = httpRequest.getSession();
				HashMap old = new HashMap();
				Enumeration keys = (Enumeration) session.getAttributeNames();
				
				while (keys.hasMoreElements()){
					String key = (String) keys.nextElement();
					if (!NEW_SESSION_INDICATOR.equals(key)){
						old.put(key, session.getAttribute(key));
						session.removeAttribute(key);
					}
				}
				
				if (httpRequest.getMethod().equals("POST") && httpRequest.getSession() != null 
						&& !httpRequest.getSession().isNew() && httpRequest.getRequestURI().endsWith(url)){
					session.invalidate();
					session=httpRequest.getSession(true);
					logger.debug("new Session:" + session.getId());
				}
				
				//-----------------复制session
				for (Iterator it = old.entrySet().iterator(); it.hasNext();) {
					Map.Entry entry = (Entry) it.next();
					session.setAttribute((String) entry.getKey(), entry.getValue());
				}
			}
		}
		
		chain.doFilter(request, response);
		System.out.println("NewSessionFilter doFilter end");
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("NewSessionFilter init");
		System.out.println("NewSessionFilter init end");
	}

}</span>

然后,在 web.xml 中配置 Filter。

<span style="font-family:Comic Sans MS;"><filter>
	<filter-name>NewSessionFilter</filter-name>
	<filter-class>com.cacss.sc.web.common.NewSessionFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>NewSessionFilter</filter-name>
	<url-pattern>/login</url-pattern>
</filter-mapping>

</span>

这样处理完之后,再启动应用服务器,登录前后的 JSessionId 就已经不一样了,也就是说,会话标识未更新的问题也就解决了。


结束语


这个问题困扰了我 n 多天,直到看到这个解决思路之后,通过跟测试部的童鞋协商,测试通过之后,才算是真正的解决了。这也算是这个系列的最后一篇了,写到现在已经把大部分的 Web 安全方面的漏洞都提到过了,而且也都给出了一些解决方案。

通过这近一个多月的漏洞修复,我在 Web 安全方面真的是恶补了一番,也接触了很多安全方面的技术,这对于我以后的开发、设计都是很有好处的,在开发、设计的时候,就会考虑到会不会产生安全漏洞,怎样做会避免这样的问题。这样,就不会在测试的时候出现很多不必要的漏洞了。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。