XSS漏洞
攻击者利用XSS(Cross-site scripting)漏洞攻击可以在用户的浏览器中执行JS恶意脚本,XSS攻击可以实现用户会话劫持、钓鱼攻击、恶意重定向、点击劫持、挂马、XSS蠕虫等,XSS攻击类型分为:反射型、存储型、DOM型。
1. 反射型XSS攻击
示例 - 存在反射型XSS的xss.jsp代码:
1
| <%=request.getParameter("input")%>
|
攻击者通过传入恶意的input参数值可以在用户浏览器中注入一段JavaScript脚本。
示例 - 注入XSS代码:
1
| <script>alert('xss');</script>
|
2. 存储型XSS攻击
示例 - 存在存储型XSS的guestbook.jsp代码:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.text.SimpleDateFormat" %> <%@ page import="java.util.*" %> <% String username = request.getParameter("username"); String content = request.getParameter("content");
String guestBookKey = "GUEST_BOOK"; List<Map<String, String>> comments = new ArrayList<Map<String, String>>();
if (content != null) { Object obj = application.getAttribute(guestBookKey);
if (obj != null) { comments = (List<Map<String, String>>) obj; }
Map<String, String> comment = new HashMap<String, String>(); String ip = request.getHeader("x-real-ip");
if (ip == null) { ip = request.getRemoteAddr(); }
comment.put("username", username); comment.put("content", content); comment.put("ip", ip); comment.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
comments.add(comment);
application.setAttribute(guestBookKey, comments); } %> <html> <head> <title>留言板</title> </head> <style> * { margin: 0; padding: 0; } </style> <body> <div style="border: 1px solid #C6C6C6;"> <div style="text-align: center;"> <h2>在线留言板</h2> </div> <div> <dl> <% Object obj = application.getAttribute(guestBookKey);
if (obj instanceof List) { comments = (List<Map<String, String>>) obj;
for (Map<String, String> comment : comments) { %> <dd> <div style="min-height: 50px; margin: 20px; border-bottom: 1px solid #9F9F9F;"> <p><B><%=comment.get("username")%> </B>[<%=comment.get("ip")%>] 于 <%=comment.get("date")%> 发表回复:</p> <p style="margin: 15px 0 5px 0; font-size: 12px;"> <pre><%=comment.get("content")%></pre> </p> </div> </dd> <% } } %> </dl> </div> <div style="background-color: #fff; border: 1px solid #C6C6C6;"> <form action="#" method="POST" style="margin: 20px;"> 昵称: <input type="text" name="username" style="width:250px; height: 28px;"/><br/><br/> <textarea name="content" style="overflow: auto;width: 100%; height: 250px;"></textarea> <input type="submit" value="提交留言" style="margin-top: 20px; width: 80px; height: 30px;"/> </form> </div> </div> </body> </html>
|


3. DOM XSS
示例 - dom.jsp代码:
1 2 3 4 5 6 7
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> Date: <span style="color: red;"></span> <input type="hidden" value="<%=request.getParameter("date")%>" /> <script> var date = document.getElementsByTagName("input")[0].value; document.getElementsByTagName("span")[0].innerHTML = date; </script>
|
XSS攻击测试:
http://localhost:8000/modules/servlet/dom.jsp?date=%3Cimg%20src=1%20onerror=alert(/xss/)%20/%3E%20/%3E
4. XSS防御
XSS最为常见的处理方式是转义特殊字符,后端程序在接受任何用户输入的参数时都应当优先考虑是否会存在XSS攻击。
4.1 htmlspecialchars
在PHP中通常会使用htmlspecialchars函数会将一些可能有攻击威胁的字符串转义为html实体编码,这样可以有效的避免XSS攻击。
示例 - htmlspecialchars 转义:
字符 |
替换后 |
& (& 符号) |
& |
“ (双引号) |
" |
‘ (单引号) |
'或者' |
< (小于) |
< |
> (大于) |
> |
在Java中虽然没有内置如此简单方便的函数,但是我们可以通过字符串替换的方式实现类似htmlspecialchars函数的功能。
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
|
public static String htmlSpecialChars(String content) { if (content == null) { return null; }
char[] charArray = content.toCharArray(); StringBuilder sb = new StringBuilder();
for (char c : charArray) { switch (c) { case '&': sb.append("&"); break; case '"': sb.append("""); break; case '\'': sb.append("'"); break; case '<': sb.append("<"); break; case '>': sb.append(">"); break; default: sb.append(c); break; } }
return sb.toString(); }
|
4.2 全局的XSSFilter
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
| package com.anbai.sec.vuls.filter;
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException;
public class XSSFilter implements Filter {
@Override public void init(FilterConfig filterConfig) {
}
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req;
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) { public String getParameter(String name) { String value = super.getParameter(name);
return value.replace("&", "&").replace("<", "<").replace("'", "'"); } };
chain.doFilter(requestWrapper, resp); }
@Override public void destroy() {
}
}
|
web.xml添加XSSFilter过滤器:
1 2 3 4 5 6 7 8 9 10
| <filter> <filter-name>XSSFilter</filter-name> <filter-class>com.anbai.sec.vuls.filter.XSSFilter</filter-class> </filter>
<filter-mapping> <filter-name>XSSFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
经过全局过滤器转义后的参数就不会再带有XSS攻击能力了。

4.3 RASP XSS攻击防御
RASP可以实现类似于全局XSSFilter的请求参数过滤功能,比较稳定的一种方式是Hook到javax.servlet.ServletRequest接口的实现类的getParameter/getParameterValues/getParameterMap等核心方法,在该方法return之后插入RASP的检测代码。这种实现方案虽然麻烦,但是可以避免触发Http请求参数解析问题(Web应用无法获取getInputStream和乱码等问题)。

反射型的XSS防御相对来说比较简单,直接禁止GET参数中出现<>标签,只要出现就理解拦截,如:
1
| http://localhost:8000/modules/servlet/xss.jsp?input=<script>alert('xss');</script>
|
过滤或拦截掉<>后input参数就不再具有攻击性了。
但是POST请求的XSS参数就没有那么容易过滤了,为了兼顾业务,不能简单的使用htmlSpecialChars的方式直接转义特殊字符,因为很多时候应用程序是必须支持HTML标签的(如:<img>、<h1>等)。RASP在防御XSS攻击的时候应当尽可能的保证用户的正常业务不受影响,否则可能导致用户无法业务流程阻塞或崩溃。
为了支持一些常用的HTML标签和HTML标签属性,RASP可以通过词法解析的方式,将传入的字符串参数值解析成HTML片段,然后分析其中的标签和属性是否合法即可。
