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 转义:

字符 替换后
& (& 符号) &amp;
“ (双引号) &quot;
‘ (单引号) &#039;或者&apos;
< (小于) &lt;
> (大于) &gt;

在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
/**
* 实现htmlSpecialChars函数把一些预定义的字符转换为HTML实体编码
*
* @param content 输入的字符串内容
* @return HTML实体化转义后的字符串
*/
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("&amp;");
break;
case '"':
sb.append("&quot;");
break;
case '\'':
sb.append("&#039;");
break;
case '<':
sb.append("&lt;");
break;
case '>':
sb.append("&gt;");
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,包装原HttpServletRequest对象,示例程序只重写了getParameter方法,
// 应当考虑如何过滤:getParameter、getParameterValues、getParameterMap、getInputStream、getReader
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
public String getParameter(String name) {
// 获取参数值
String value = super.getParameter(name);

// 简单转义参数值中的特殊字符
return value.replace("&", "&amp;").replace("<", "&lt;").replace("'", "&#039;");
}
};

chain.doFilter(requestWrapper, resp);
}

@Override
public void destroy() {

}

}

web.xml添加XSSFilter过滤器:

1
2
3
4
5
6
7
8
9
10
<!-- XSS过滤器 -->
<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片段,然后分析其中的标签和属性是否合法即可。