本文共 5195 字,大约阅读时间需要 17 分钟。
引入:
这篇文章我们来专门探讨如何防范来自外部的XSS攻击。
实践:
其实从 文章中可以看出,主要的攻击手段无让攻击者机器上运行一段JS,这段js中包含一段对于document.cookie的处理,如果这个能拿到正确的值,那么就可以用获取的信息发送到攻击者的某个指定机器上,从而盗用。所以从这个思路上想解决方法就很容易了,就是通过一段机制,让植入到攻击者机器上的恶意的js代码中拿document.cookie拿不到内容。
能实现这一点么,简单查阅了下文档,发现了原来有Cookie 有个HttpOnly这么个标记,如果把它设置为true的时候,那么这个cookie只能通过http协议访问,而无法通过javascript 脚本,或者applet进行访问。我们在想,既然信息的盗用都是一段js脚本去拿到document.cookie的内容,那么如果设置Cookie为HttpOnly后,是否解决问题了呢?
为此,我们做个实验,我们让Cookie用java代码生成而不是和上文连接中的用某段js生成(这样做的目的是万一HttpOnly真起作用了,那么我们设置Cookie的这段js代码就生效了)
代码很简单,我们做了一个Servlet,然后让用户访问这个Servlet的时候,它会创建一个Cookie(现在hard-coded):为了比较,我们先做一个不设置HttpOnly的例子:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | package com.charles.study; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 这个Servlet用于产生一个Cookie * @author charles.wang * */ public class CookieServlet extends HttpServlet{ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { buildResponseWithCookie(response); } /** * hard-code一个Cookie信息,并且写到客户端 * @param response * @throws IOException */ private void buildResponseWithCookie(HttpServletResponse response) throws IOException { response.setContentType( "text/html;charset=utf-8" ); PrintWriter out = response.getWriter(); Cookie cookie = new Cookie( "charles" , "1234567890" ); cookie.setMaxAge( 24 * 3600 ); cookie.setPath( "/" ); response.addCookie(cookie); out.println( "Already Written Cookie to Client" ); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } |
然后在 web.xml中定义好 servlet-mapping(略去)
然后当我们访问页面时候,打开Firebug,切换到Cookie标签:
可以看到,默认情况下,这个Cookie的HttpOnly属性是没有设置值的(下方表格第七栏)
现在,我们切换到Console标签页,然后输入document.cookie:
红色部分显示,这时候我们是拿得到cookie的值的,也就是说js完全可以获取cookie的内容。
现在我们把我们的代码变下,在创建cookie的地方,加一行 cookie.setHttpOnly(true);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * hard-code一个Cookie信息,并且写到客户端 * @param response * @throws IOException */ private void buildResponseWithCookie(HttpServletResponse response) throws IOException { response.setContentType( "text/html;charset=utf-8" ); PrintWriter out = response.getWriter(); Cookie cookie = new Cookie( "charles" , "1234567890" ); cookie.setMaxAge( 24 * 3600 ); cookie.setPath( "/" ); //以下这行专门用于解决来自外部的XSS攻击问题 cookie.setHttpOnly( true ); response.addCookie(cookie); out.println( "Already Written Cookie to Client" ); } |
其他不变,然后我们重复上面实验,我们打开这个servlet页面,打开Firebug,切换到Cookie标签:
这时候可以发现第7列HttpOnly属性被设置了。
现在,我们切换到Console标签页,然后输入document.cookie:
这次我们发现,这个Cookie不再显示“charles=1234567890"了,取而代之的是,它只显示""空,这就表明,我们的HttpOnly属性起作用了。我们的恶意js无法通过document.cookie拿到任何有价值信息。
结论的延伸:
于是,从上面2个实验对比,我们发现了HttpOnly属性是解决来自外部XSS攻击的关键属性,我们不满足上面实验,我们还可以进行扩展,因为上述实验中,我们是让response直接添加了一个cookie, 然现实中的例子多数是通过会话HttpSession,然后它的scope上添加了一些机密信息,并且维护在服务器端的。然后客户端也会有一个id去引用这个对应的session,那么这个Session是否可以使用HttpOnly属性呢?答案是肯定的。
如果你使用的是servlet 3.0+的版本,那么在对应的web.xml中,可以天然的通过下面一段代码来配置HttpOnly属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?xml version= "1.0" encoding= "UTF-8" ?> <web-app version= "3.0" xmlns= "http://java.sun.com/xml/ns/javaee" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" > <display-name>XSSDemo</display-name> <session-config> <cookie-config> <http-only> true </http-only> </cookie-config> </session-config> .. |
这里,注意我们的xsd是servlet 3.0的xsd,所以我们可以在<session-config>中使用 <cookie-config>子元素,然后用<http-only>为true来启用这个HttpOnly特征。
我们来做个实验证明,我们修改下CookieServlet,从而当我们访问这个Servlet时候他会创建一个Session:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class CookieServlet extends HttpServlet{ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session= request.getSession(); session.setAttribute( "charles_session" , "9876543210" ); response.setContentType( "text/html;charset=utf-8" ); PrintWriter out = response.getWriter(); out.println( "Session Created" ); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } |
然后我们访问页面:
显然,从第七列看出来,这个刚创建的Session Cookie ,它的HttpOnly被正确的设置了。
总结:
我们做了几个实验,大体上解决了外部XSS攻击的问题,主要是通过设置HttpOnly属性,这个属性可防止js或者applet去操作cookie,而且它不仅适用于一般的Cookie,也适用于Session Cookie. 其实它的思想很简单,既然外部XSS攻击是你把情报带出去,那么我海关就严格一条,任何纸条只要通过海关就”烧“掉(我只是打个比方) ,这样你不管如何,你都不能通过恶意代码从这个灰烬的纸条中拿到信息了。
实践上看,对于一般的Cookie,只要在它创建的时候,设置cookie.setHttpOnly(true).
对于Session Cookie,如果你的app容器支持servlet 3.0规范,那么只要配置 <cookie-config>让其启用<http-only>就可以了,如果你的app容器比较老,那么你必须通过覆写SET-COOKIE的 Http响应头来显式添加HttpOnly标志。
1 2 3 | //通过覆写SET-COOKIE的 Http响应头来显式的添加HttpOnly String sessionid = request.getSession().getId(); response.setHeader( "SET-COOKIE" , "JSESSIONID=" + sessionid + "; HttpOnly" ); |
其实,现在主流的浏览器都能很好的支持HttpOnly了,黑客们要入侵难度又加大了。