Springboot 防止XSS攻击的一部分解决方案
1. XSS与处理思路
XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。解决方案可以分为将用户输入的数据无害化处理或者将用户输入数据中有害的部分去除两种思路。
无害化可以使用类库 owasp-java-html-sanitizer 来处理
<dependency> <groupId>com.googlecode.owasp-java-html-sanitizer</groupId> <artifactId>owasp-java-html-sanitizer</artifactId> <version>20211018.2</version> </dependency>
去除有害部分可以使用 jsoup 来处理
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency>
这里由于需要使用富文本框输入文本,并在展示时保留样式,我使用的是 jsoup 来过滤用户输入时的有害部分的做法。
2. 处理方法
2.1 创建XSS过滤工具类
import org.jsoup.Jsoup; import org.jsoup.safety.Whitelist; /** * XSS过滤工具类 */ public class XssUtils extends Whitelist { /** * XSS过滤 */ public static String filter(String html){ return Jsoup.clean(html, xssWhitelist()); } /** * XSS过滤白名单 */ private static Whitelist xssWhitelist(){ return new Whitelist() //支持的标签 .addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong","sub", "sup", "table", "tbody", "td","tfoot", "th", "thead", "tr", "u","ul", "embed","object","param","span") //支持的标签属性 .addAttributes("a", "href", "class", "style", "target", "rel", "nofollow") .addAttributes("blockquote", "cite") .addAttributes("code", "class", "style") .addAttributes("col", "span", "width") .addAttributes("colgroup", "span", "width") .addAttributes("img", "align", "alt", "height", "src", "title", "width", "class", "style") .addAttributes("ol", "start", "type") .addAttributes("q", "cite") .addAttributes("table", "summary", "width", "class", "style") .addAttributes("tr", "abbr", "axis", "colspan", "rowspan", "width", "style") .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width", "style") .addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope","width", "style") .addAttributes("ul", "type", "style") .addAttributes("pre", "class", "style") .addAttributes("div", "class", "id", "style") .addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen", "quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type") .addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase") .addAttributes("param", "name", "value") .addAttributes("span", "class", "style") //标签属性对应的协议 .addProtocols("a", "href", "ftp", "http", "https", "mailto") .addProtocols("img", "src", "http", "https") .addProtocols("blockquote", "cite", "http", "https") .addProtocols("cite", "cite", "http", "https") .addProtocols("q", "cite", "http", "https") .addProtocols("embed", "src", "http", "https"); } }
2.2 表单、URL、header传参方式提交数据的参数处理
创建XssFilter
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * XSS过滤 */ public class XssFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper( (HttpServletRequest) request); chain.doFilter(xssRequest, response); } @Override public void destroy() { } }
创建XssHttpServletRequestWrapper,重写请求参数处理函数,这是实现XSS过滤的关键,在其内重写了getParameter,getParameterValues,getHeader等方法,对http请求内的参数进行了过滤。
import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.LinkedHashMap; import java.util.Map; /** * XSS过滤处理 */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest orgRequest; public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; } @Override public String getParameter(String name) { String value = super.getParameter(xssEncode(name)); if (StringUtils.isNotBlank(value)) { value = xssEncode(value); } return value; } @Override public String[] getParameterValues(String name) { String[] parameters = super.getParameterValues(name); if (parameters == null || parameters.length == 0) { return null; } for (int i = 0; i < parameters.length; i++) { parameters[i] = xssEncode(parameters[i]); } return parameters; } @Override public Map<String,String[]> getParameterMap() { Map<String,String[]> map = new LinkedHashMap<>(); Map<String,String[]> parameters = super.getParameterMap(); for (String key : parameters.keySet()) { String[] values = parameters.get(key); for (int i = 0; i < values.length; i++) { values[i] = xssEncode(values[i]); } map.put(key, values); } return map; } @Override public String getHeader(String name) { String value = super.getHeader(xssEncode(name)); if (StringUtils.isNotBlank(value)) { value = xssEncode(value); } return value; } private String xssEncode(String input) { return XssUtils.filter(input); } /** * 获取最原始的request */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * 获取最原始的request */ public static HttpServletRequest getOrgRequest(HttpServletRequest request) { if (request instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) request).getOrgRequest(); } return request; } }
添加Filter配置
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; import javax.servlet.DispatcherType; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setOrder(Integer.MAX_VALUE); registration.setFilter(new XssFilter()); registration.addUrlPatterns("/*"); registration.setName("xssFilter"); return registration; } }
2.3 JSON方式提交数据的参数处理
Spring处理JSON是通过MappingJackson2HttpMessageConverter实现的,所以可以利用JSON反序列化时对JSON内容进行无害化处理。
注意:这种处理方式对 @RequestBody String params 的参数无效,如下:
@RequestMapping("test") public String test(@RequestBody String data) { }
接下来是实现方式:
首先创建 XssStringDeserializer
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; /** * JSON反序列化String类型时防XSS的处理器 */ public class XssStringDeserializer extends JsonDeserializer<String> { public static final XssStringDeserializer instance = new XssStringDeserializer(); @Override public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String source = jsonParser.getText().trim(); return XssUtils.filter(source); } }
将XssStringDeserializer注册到MappingJackson2HttpMessageConverter中
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Web配置 */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); ObjectMapper mapper = new ObjectMapper(); //String类型防XSS SimpleModule simpleModule = new SimpleModule(); simpleModule.addDeserializer(String.class, XssStringDeserializer.instance); mapper.registerModule(simpleModule); converter.setObjectMapper(mapper); return converter; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通