一. XSS 介绍
网站中包含大量的动态内容以提高用户体验,比过去要复杂得多。所谓动态内容,就是根据用户环境和需要,Web 应用程序能够输出相应的内容。动态站点会受到一种名为 “跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS, 原本应当是 css,但为了和层叠样式表(Cascading Style Sheet,CSS )有所区分,故称 XSS)的威胁,而静态站点则完全不受其影响。
二. 攻击类型
- 持久型跨站:最直接的危害类型,跨站代码存储在服务器(数据库)。
- 非持久型跨站:反射型跨站脚本漏洞,最普遍的类型。用户访问服务器 - 跨站链接 - 返回跨站代码。
- DOM 跨站(DOM XSS):DOM(document object model 文档对象模型),客户端脚本处理逻辑导致的安全问题。
关于 XSS 的更多信息,具体可以参考百度百科。
三. 解决方案
1. 自定义 HttpServletRequestWrapper
/**
* XSS过滤处理类,用于HTTP请求中特殊字符的转换,解决跨站点等攻击
*
* @date 2020-03-16
* @author DaiQi
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 请求
*/
private HttpServletRequest xssRequest = null;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
xssRequest = request;
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
* @param name 名称
* @return 返回字符串
*/
public String getParameter(String name) {
String value = super.getParameter(XssUtil.xssEncode(name));
if (value != null) {
value = XssUtil.xssEncode(value);
}
return value;
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
* getHeaderNames 也可能需要覆盖
* @param name 名称
* @return 返回字符串
*/
public String getHeader(String name) {
String value = super.getHeader(XssUtil.xssEncode(name));
if (!StringUtils.isEmpty(value)) {
//方法一:自定义XSS过滤工具类
value = XssUtil.xssEncode(value);
//方法二:转义为html格式
//org.apache.commons.lang3下的StringEscapeUtils已经过时了
//可以使用org.apache.commons.text.StringEscapeUtils 进行替换
//value = org.apache.commons.lang3.StringEscapeUtils.escapeHtml4(value);
}
return value;
}
/**
* 获取最原始的request的静态方法
* @param req req`在这里插入代码片`
* @return HttpServletRequest
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).xssRequest;
}
return req;
}
}
2. 自定义过滤器
/**
* XSS过滤器
*
* @date 2020-03-16
* @author DaiQi
*/
@Order(2)
@WebFilter(filterName = "xssFilter", urlPatterns = "/*")
public class XssFilter extends OncePerRequestFilter {
/**
* 内部过滤
*
* @param request
* @param response
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request);
filterChain.doFilter(xssRequest, response);
}
}
3.XssUtil
/**
* XSS工具类
*/
public class XssUtil {
/**
* 常量16
*/
public static final int NUM_16 = 16;
/**
* 私有构造函数
*/
private XssUtil() {
}
/**
* 将容易引起xss漏洞的半角字符直接替换成全角字符
*
* @param s 原字符串
* @return 替换后的字符串
*/
public static String xssEncode(String s) {
if (s == null || "".equals(s)) {
return s;
}
StringBuilder sb = new StringBuilder(s.length() + NUM_16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '>':
sb.append('>');// 全角大于号
break;
case '<':
sb.append('<');// 全角小于号
break;
case '\'':
sb.append('‘');// 全角单引号
break;
case '\"':
sb.append('“');// 全角双引号
break;
case '&':
sb.append('&');// 全角与逻辑符
break;
case '%':
sb.append('%');// 全角百分号
break;
case '+':
sb.append("+");// 全角加号
break;
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();
}
}