跨站脚本攻击防护过滤器开发指南
一、跨站脚本攻击(XSS)概述
跨站脚本攻击(Cross Site Scripting,简称XSS)是一种常见的网络安全威胁。攻击者利用Web应用程序对用户输入数据缺乏适当过滤或转义的漏洞,将恶意脚本代码注入到网页中。当其他用户访问这些被污染的页面时,浏览器会执行这些嵌入的恶意代码,可能导致信息泄露、会话劫持或其他安全风险。
例如,攻击者可能在搜索框或评论区域输入可执行的JavaScript代码,若后端未进行充分的过滤和验证,这些代码就会被存储并在其他用户浏览时执行。
二、过滤器技术基础
在Java Web开发中,过滤器(Filter)是一种用于处理请求和响应的组件。它实现了javax.servlet.Filter接口,允许开发者在请求到达目标资源之前或响应返回客户端之后对数据进行预处理或后处理。过滤器常用于安全控制、字符编码转换、日志记录等场景,特别适用于XSS防护这类需要全局处理的需求。
三、过滤器与拦截器的技术差异
- 过滤器(Filter):
- 基于Servlet容器实现,不依赖特定框架
- 采用函数回调机制工作
- 可对所有请求进行拦截,范围更广
- 在请求处理生命周期中更早介入
- 拦截器(Interceptor):
- 依赖于特定Web框架(如SpringMVC)
- 基于Java反射机制实现
- 属于面向切面编程(AOP)的具体应用
- 通常在业务逻辑层执行拦截,粒度更细
四、XSS过滤器实现原理
XSS过滤器的核心原理是通过创建HttpServletRequest的包装类,重写其中的参数获取方法(getParameter、getParameterValues、getHeader等),在这些方法中对获取的字符串进行特殊字符过滤和转义处理,从而防止恶意脚本代码的执行。
五、代码实现
5.1、Web配置文件
<!-- XSS安全过滤器配置 -->
<filter>
<filter-name>SecurityXssFilter</filter-name>
<filter-class>com.security.web.filter.XssSecurityFilter</filter-class>
<init-param>
<param-name>excludedUrls</param-name>
<param-value>/api/safe,/static/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SecurityXssFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
5.2、XSS安全过滤器
package com.security.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* XSS安全过滤器 - 用于拦截并处理潜在的跨站脚本攻击
*/
public class XssSecurityFilter implements Filter {
private FilterConfig filterConfig;
private String excludedUrls;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.excludedUrls = filterConfig.getInitParameter("excludedUrls");
}
@Override
public void destroy() {
this.filterConfig = null;
this.excludedUrls = null;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 检查是否在排除列表中
if (isExcludedUrl(httpRequest.getRequestURI())) {
chain.doFilter(request, response);
return;
}
// 使用包装类处理请求参数
chain.doFilter(new SecureRequestWrapper(httpRequest), response);
}
private boolean isExcludedUrl(String requestURI) {
if (excludedUrls == null || excludedUrls.isEmpty()) {
return false;
}
String[] urls = excludedUrls.split(",");
for (String url : urls) {
if (requestURI.contains(url.trim())) {
return true;
}
}
return false;
}
}
5.3、安全请求包装类
package com.security.web.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.HashMap;
import java.util.Map;
/**
HttpServletRequest的安全包装类,用于过滤XSS攻击字符
*/
public class SecureRequestWrapper extends HttpServletRequestWrapper {
// 存储过滤后的参数
private final Map<String, String[]> sanitizedParameters = new HashMap<>();
public SecureRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String name) {
// 先从缓存中获取
if (sanitizedParameters.containsKey(name)) {
return sanitizedParameters.get(name);
}
String[] originalValues = super.getParameterValues(name);
if (originalValues == null) {
return null;
}
// 处理每个参数值
String[] sanitizedValues = new String[originalValues.length];
for (int i = 0; i < originalValues.length; i++) {
sanitizedValues[i] = sanitizeInput(originalValues[i]);
}
// 缓存结果
sanitizedParameters.put(name, sanitizedValues);
return sanitizedValues;
}
@Override
public String getParameter(String name) {
// 先从缓存中获取
if (sanitizedParameters.containsKey(name)) {
return sanitizedParameters.get(name)[0];
}
String originalValue = super.getParameter(name);
if (originalValue == null) {
return null;
}
// 处理并缓存结果
String sanitizedValue = sanitizeInput(originalValue);
sanitizedParameters.put(name, new String[]{sanitizedValue});
return sanitizedValue;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> originalMap = super.getParameterMap();
Map<String, String[]> newMap = new HashMap<>();
for (Map.Entry<String, String[]> entry : originalMap.entrySet()) {
String[] sanitizedValues = new String[entry.getValue().length];
for (int i = 0; i < entry.getValue().length; i++) {
sanitizedValues[i] = sanitizeInput(entry.getValue()[i]);
}
newMap.put(entry.getKey(), sanitizedValues);
}
return newMap;
}
@Override
public String getHeader(String name) {
String originalValue = super.getHeader(name);
return originalValue != null ? sanitizeInput(originalValue) : null;
}
/**
* 输入净化方法,移除或转义潜在的XSS攻击字符
* @param input 原始输入字符串
* @return 净化后的字符串
*/
private String sanitizeInput(String input) {
if (input == null) {
return null;
}
// 转义HTML特殊字符
input = input.replaceAll("<", "<").replaceAll(">", ">");
input = input.replaceAll("\\(", "(").replaceAll("\\)", ")");
input = input.replaceAll("'", "'");
// 移除潜在的JavaScript代码
input = input.replaceAll("eval\\((.*?)\\)", "");
input = input.replaceAll("[\"'][\\s]*javascript:(.*?)[\"']", "\"\"");
input = input.replaceAll("script", "");
// 移除其他危险的HTML/JavaScript事件处理器
input = input.replaceAll("onload", "onload_");
input = input.replaceAll("onerror", "onerror_");
input = input.replaceAll("onclick", "onclick_");
return input;
}
}