preface
Project security requires xss filtering of global parameters
Introduction to Xss
Many people may know about Xss. Out of "Politeness",
Salted fish gentleman or simply take an example
Users can fill in their names when registering
At this point, I filled in "" and submitted it,
On the back end, it was saved without any detection
Then there may be a problem. Next time you visit this page, you will find a pop-up window "1"!
There are many kinds of Xss attacks. Those who are interested can consult the data by themselves. I won't say more here
So how to face Xss attack?
In fact, it is also very easy to solve. In a word, "don't trust any input of users"!
Programming is to filter the content submitted by users and "escape" illegal characters!
Implementation of Springboot Xss interceptor
Filtering and escaping the submission of the whole system, such as calling a method for each point to do this, will be very troublesome, and the repeated code looks ugly. Therefore, we use Springboot+Filter to implement an Xss global filter
Springboot implements an Xss filter in two common ways:
-
Override HttpServletRequestWrapper
Rewrite getHeader(), getParameter(), getParameterValues(), getInputStream() to filter the traditional "key value pair" parameter transfer methods
Rewrite getInputStream() to filter the parameters passed in Json mode, that is, the @ RequestBody parameter -
Customize the serializer and set the objectMapper of MappingJackson2HttpMessageConverter
Override jsonserializer Serialize() to filter the output parameters (PS: save the data as it is and escape it when it is taken out)
Override jsondeserializer Deserialize() to filter the input parameters (PS: save after data escape)
There are several precautions for the above two methods:
Problem 1: processing of json parameter (@ RequestBody)
For parameter transfer in Json mode,
Spring MVC uses Jack JSON for serialization by default
When you override getInputStream() to implement xss filtering,
If you replace the double quotation marks for the parameters, an error will be reported when the jackjson sequence / deserialization parameters,
Because it doesn't know this format!
Therefore, we should eliminate double quotation marks when processing parameters!
Alternatively, you can choose a custom serializer to handle json parameters
Problem 2: Custom serializer
Although we have written cases in both ways,
In fact, you only need to choose one of them! There is no need to filter both
It is recommended to handle the input parameters for the following reasons:
-
Overriding getHeader(), getParameter(), and getParameterValues() directly escapes the input parameters,
That is, the parameters obtained by your subsequent programs are escaped,
Therefore, for global unification, we also input parameters of @ RequestBody type -
Escaping the input parameter means that the data stored in the DB is "safe"
The implementation principle of this case is as follows:
- Override HttpServletRequestWrapper filtering for key value pair parameters
- Use a custom serializer to filter json parameters
PS: getInputStream() filtering is also implemented for json parameters (the code is in annotation state)
realization
First, let's introduce a toolkit, the famous "muddle headed" toolkit, which integrates a large number of easy-to-use tools! Highly recommended!
<!--HuTool tool kit --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.3</version> </dependency>
We use escape util in HuTool to escape special characters
Use HttpServletRequestWrapper to override Request parameters
package com.mrcoder.sbxssfilter.config.xss; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.EscapeUtil; import cn.hutool.core.util.StrUtil; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * XSS Filtering treatment */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { /** * Description: constructor * * @param request Request object */ public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getHeader(String name) { String value = super.getHeader(name); return EscapeUtil.escape(value); } //Override getParameter @Override public String getParameter(String name) { String value = super.getParameter(name); return EscapeUtil.escape(value); } //Override getParameterValues @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { int length = values.length; String[] escapseValues = new String[length]; for (int i = 0; i < length; i++) { escapseValues[i] = EscapeUtil.escape(values[i]); } return escapseValues; } return super.getParameterValues(name); } // //Rewrite getInputStream to filter json format parameters (that is, parameters of @ RequestBody type) // @Override // public ServletInputStream getInputStream() throws IOException { // //Non json type, return directly // if (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { // return super.getInputStream(); // } // // //Null, return directly // String json = IoUtil.read(super.getInputStream(), "utf-8"); // if (StrUtil.isEmpty(json)) { // return super.getInputStream(); // } // //It should be noted here that parameters in json format cannot directly use the escape util. Of hutool Escape, because it will escape "also, // //This makes @ RequestBody unable to resolve into a normal object, so we implement a filtering method ourselves // //Or you can customize your own objectMapper to handle the escape of json access parameters (recommended) // json = cleanXSS(json).trim(); // final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); // return new ServletInputStream() { // @Override // public boolean isFinished() { // return true; // } // // @Override // public boolean isReady() { // return true; // } // // @Override // public void setReadListener(ReadListener readListener) { // } // // @Override // public int read() { // return bis.read(); // } // }; // } // // public static String cleanXSS(String value) { // value = value.replaceAll("&", "%26"); // value = value.replaceAll("<", "%3c"); // value = value.replaceAll(">", "%3e"); // value = value.replaceAll("'", "%27"); // //value = value.replaceAll(":", "%3a"); // //value = value.replaceAll("\"", "%22"); // //value = value.replaceAll("/", "%2f"); // return value; // } }
Implement a filter
package com.mrcoder.sbxssfilter.config.xss; import cn.hutool.core.util.StrUtil; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Filters to prevent XSS attacks */ @Component public class XssFilter implements Filter { /** * Exclude links */ private List<String> excludes = new ArrayList<>(); /** * xss Filter switch */ private boolean enabled = false; @Override public void init(FilterConfig filterConfig) { String tempExcludes = filterConfig.getInitParameter("excludes"); String tempEnabled = filterConfig.getInitParameter("enabled"); if (StrUtil.isNotEmpty(tempExcludes)) { String[] url = tempExcludes.split(","); Collections.addAll(excludes, url); } if (StrUtil.isNotEmpty(tempEnabled)) { enabled = Boolean.valueOf(tempEnabled); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (handleExcludeUrl(req)) { chain.doFilter(request, response); return; } XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } /** * Judge whether the current path needs to be filtered */ private boolean handleExcludeUrl(HttpServletRequest request) { if (!enabled) { return true; } if (excludes == null || excludes.isEmpty()) { return false; } String url = request.getServletPath(); for (String pattern : excludes) { Pattern p = Pattern.compile("^" + pattern); Matcher m = p.matcher(url); if (m.find()) { return true; } } return false; } }
Configure and register filters
package com.mrcoder.sbxssfilter.config.xss; import javax.servlet.DispatcherType; import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.util.HashMap; import java.util.Map; /** * @Author xssfilter to configure */ @Configuration public class XssFilterConfig { @Value("${xss.enabled}") private String enabled; @Value("${xss.excludes}") private String excludes; @Value("${xss.urlPatterns}") private String urlPatterns; @SuppressWarnings({"rawtypes", "unchecked"}) @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setFilter(new XssFilter()); //Add filter path registration.addUrlPatterns(StrUtil.split(urlPatterns, ",")); registration.setName("xssFilter"); registration.setOrder(Integer.MAX_VALUE); //Set initialization parameters Map<String, String> initParameters = new HashMap<String, String>(); initParameters.put("excludes", excludes); initParameters.put("enabled", enabled); registration.setInitParameters(initParameters); return registration; } /** * Filter json type * * @param builder * @return */ @Bean @Primary public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) { //Parser ObjectMapper objectMapper = builder.createXmlMapper(false).build(); //Register xss parser SimpleModule xssModule = new SimpleModule("XssStringJsonDeserializer"); //Just select one of the input and output parameters for filtering. There is no need to add both //In order to unify with XssHttpServletRequestWrapper, it is recommended to handle the input parameters //Register parameter escape xssModule.addDeserializer(String.class, new XssStringJsonDeserializer()); //Register parameter escape //xssModule.addSerializer(new XssStringJsonSerializer()); objectMapper.registerModule(xssModule); //return return objectMapper; } }
Finally, we add
#Open xss.enabled=true #Unfiltered paths, separated by commas xss.excludes=/open/*,/open2/* //Filter path, comma separated xss.urlPatterns=/*
In addition, we need to implement an ObjectMapper to handle json format parameters
Handle the escape of json input parameters
package com.mrcoder.sbxssfilter.config.xss; import cn.hutool.core.util.EscapeUtil; 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; /** * Handle the escape of json input parameters */ public class XssStringJsonDeserializer extends JsonDeserializer<String> { @Override public Class<String> handledType() { return String.class; } //Escape of incoming parameter @Override public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String value = jsonParser.getText(); if (value != null) { return EscapeUtil.escape(value); } return value; } }
Handle the escape of json parameters
package com.mrcoder.sbxssfilter.config.xss; import cn.hutool.core.util.EscapeUtil; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; /** * Handle the escape of json parameters */ public class XssStringJsonSerializer extends JsonSerializer<String> { @Override public Class<String> handledType() { return String.class; } //Out parameter escape @Override public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { if (value != null) { String encodedValue = EscapeUtil.escape(value); jsonGenerator.writeString(encodedValue); } } }
Write a test
package com.mrcoder.sbxssfilter.model; import lombok.Getter; import lombok.Setter; @Setter @Getter public class People { private String name; private String info; }
package com.mrcoder.sbxssfilter.controller; import com.mrcoder.sbxssfilter.model.People; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class XssController { //Key value pair @PostMapping("xssFilter") public String xssFilter(String name, String info) { log.error(name + "---" + info); return name + "---" + info; } //entity @PostMapping("modelXssFilter") public People modelXssFilter(@RequestBody People people) { log.error(people.getName() + "---" + people.getInfo()); return people; } //No escape @PostMapping("open/xssFilter") public String openXssFilter(String name) { return name; } //No escape 2 @PostMapping("open2/xssFilter") public String open2XssFilter(String name) { return name; } }
json mode
Key value pair method