序
上一篇文章中,我們討論了關(guān)于“瀏覽器記住用戶(hù)名和密碼”的問(wèn)題,至于這篇文章嘛,我想談?wù)勱P(guān)于“會(huì)話(huà)標(biāo)識(shí)”的漏洞。而且,這篇文章也是“Web安全實(shí)戰(zhàn)”系列的最后一篇。為什么要拿到最后來(lái)說(shuō)呢,其實(shí),是之前一直困擾我的問(wèn)題,這個(gè)問(wèn)題曾一頓讓我抓狂,n(n=3~5)多天一直沒(méi)有解決,當(dāng)時(shí)也是搜遍了各大網(wǎng)站,各大論壇,均未找到合適的解決方案。其中的過(guò)程就不再?gòu)U話(huà)了,轉(zhuǎn)到正題。
問(wèn)題
先介紹一下問(wèn)題是如何發(fā)現(xiàn)的,當(dāng)然,這個(gè)不是我發(fā)現(xiàn)的,是我們測(cè)試部的童鞋發(fā)現(xiàn)的漏洞,然后她轉(zhuǎn)交給我,讓我去解決這個(gè)問(wèn)題。剛開(kāi)始也是一點(diǎn)思路都沒(méi)有,然后就是一頓狂搜,后來(lái)慢慢發(fā)現(xiàn),其實(shí)這個(gè)問(wèn)題產(chǎn)生的根源,就是應(yīng)用服務(wù)器(如 Tomcat)的 JSessionId 沒(méi)有更新。只要使得舊的 Session 過(guò)期,重新生成新的 Session
即可。當(dāng)然,這是理論上的解決方案,很想當(dāng)然的。
曲折過(guò)程
有了思路之后,再想解決問(wèn)題就容易了很多。于是,我就把范圍固定在了如何讓 Session 過(guò)期,當(dāng)然,這樣的解決方案網(wǎng)上有一籮筐,這里就不多說(shuō)了,我拿到項(xiàng)目中試了試,結(jié)果你肯定能想到 —— 拋異常,異常原因是:...Session already invalidated 。這說(shuō)明在程序中的某處,需要用到 Session 中的數(shù)據(jù),但是此時(shí)
Session 已經(jīng)過(guò)期了,無(wú)法取到數(shù)據(jù)。
于是,我開(kāi)始找是哪里用到了 Session ,遺憾的是,我沒(méi)能找到。心想,找不到 Session 我就換個(gè)思路吧,那就讓 Session 過(guò)期之前,重新生成一個(gè)新的 Session ,把舊的 Session中的數(shù)據(jù)拷貝到新產(chǎn)生的 Session 中。這樣就可以避免 Session 過(guò)期的問(wèn)題了(理論上是這樣)。
到這里,如果是一般的情況,就可以解決了,不過(guò)嘛,如果你的項(xiàng)目中還加入了安全框架 Shiro ,那么在 Session 的處理上,會(huì)有一些麻煩,因?yàn)?Shiro 也會(huì)有自己的 Session ,而且在認(rèn)證的時(shí)候,Shiro 會(huì)有一些處理 Session 的操作,這就是導(dǎo)致 ...Session already invalidated 的原因所在。后來(lái)試了幾種把舊
Session 數(shù)據(jù)轉(zhuǎn)新 Session 數(shù)據(jù)的方案也不好使。
最終方案
一個(gè)偶然的機(jī)會(huì),在網(wǎng)上找到了一篇《會(huì)話(huà)標(biāo)識(shí)未更新》的文章,這篇文章就是介紹的在使用 Shiro 的情況下,如何解決這個(gè)漏洞,不過(guò),情況不同的是,他使用的是應(yīng)用服務(wù)器是 jBoss,而我們用的則是 Tomcat,本著試一試的態(tài)度,按照他給的思路,把自己的代碼做了一下整理,把對(duì)
Session 操作的處理類(lèi)封裝成了一個(gè) Filter,通過(guò)這個(gè) Filter 對(duì) Session 進(jìn)行處理,下面請(qǐng)看相關(guān)代碼。
代碼如下
首先,增加一個(gè)新類(lèi),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.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.LoggerFactory; public class NewSessionFilter implements Filter { 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); System.out.println("NewSessionFilter destory"); 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; String url = httpRequest.getRequestURI(); if (httpRequest.getSession() != null) { System.out.println("NewSessionFilter doFilter httpRequest.getSession().getId()"+ httpRequest.getSession().getId()); //--------復(fù)制 session到臨時(shí)變量 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=httpRequest.getSession(true); logger.debug("new Session:" + session.getId()); //-----------------復(fù)制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"); public void init(FilterConfig filterConfig) throws ServletException { System.out.println("NewSessionFilter init"); System.out.println("NewSessionFilter init end");
然后,在 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-name>NewSessionFilter</filter-name> <url-pattern>/login</url-pattern>
這樣處理完之后,再啟動(dòng)應(yīng)用服務(wù)器,登錄前后的 JSessionId 就已經(jīng)不一樣了,也就是說(shuō),會(huì)話(huà)標(biāo)識(shí)未更新的問(wèn)題也就解決了。
結(jié)束語(yǔ)
這個(gè)問(wèn)題困擾了我 n 多天,直到看到這個(gè)解決思路之后,通過(guò)跟測(cè)試部的童鞋協(xié)商,測(cè)試通過(guò)之后,才算是真正的解決了。這也算是這個(gè)系列的最后一篇了,寫(xiě)到現(xiàn)在已經(jīng)把大部分的 Web 安全方面的漏洞都提到過(guò)了,而且也都給出了一些解決方案。
通過(guò)這近一個(gè)多月的漏洞修復(fù),我在 Web 安全方面真的是惡補(bǔ)了一番,也接觸了很多安全方面的技術(shù),這對(duì)于我以后的開(kāi)發(fā)、設(shè)計(jì)都是很有好處的,在開(kāi)發(fā)、設(shè)計(jì)的時(shí)候,就會(huì)考慮到會(huì)不會(huì)產(chǎn)生安全漏洞,怎樣做會(huì)避免這樣的問(wèn)題。這樣,就不會(huì)在測(cè)試的時(shí)候出現(xiàn)很多不必要的漏洞了。
|