renanleandrof
renanleandrof

Reputation: 6997

Spring Boot escape characters at Request Body for XSS protection

I'm trying to secure my spring boot application using a XSSFilter like this:

public class XSSFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }

    @Override
    public void destroy() { }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
    }

}

And the wrapper:

public class XSSRequestWrapper extends HttpServletRequestWrapper {

    public XSSRequestWrapper(HttpServletRequest servletRequest) {
        super(servletRequest);
    }

    @Override
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);

        if (values == null) {
            return null;
        }

        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = replaceXSSCharacters((values[i]));
        }

        return encodedValues;
    }

    private String replaceXSSCharacters(String value) {
        if (value == null) {
            return null;
        }

        return value
                .replace("&","&#38;")
                .replace("<", "&#60;")
                .replace(">","&#62;")
                .replace("\"","&#34;")
                .replace("'","&#39;");
    }

    @Override
    public String getParameter(String parameter) {
        return replaceXSSCharacters(super.getParameter(parameter));
    }

    @Override
    public String getHeader(String name) {
        return replaceXSSCharacters(super.getHeader(name));
    }

}

The problem is, that only secures the Request parameters and Headers, not the Request body, and sometimes my Controller receive data using @RequestBody.

So, if i submit to my controller a json like this:

{"name":"<script>alert('hello!')</script>"}

The html chars at the name property doesn't get escaped like i need. How can i escape the RequestBody?

EDIT: This is different from the "duplicated" question. My question is very Specific. How to escape characters on Request Body.

Upvotes: 4

Views: 17251

Answers (3)

renanleandrof
renanleandrof

Reputation: 6997

I resolved with a custom class:

@Configuration
public class AntiXSSConfig  {

    @Autowired()
    public void configeJackson(ObjectMapper mapper) {
        mapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public static class HTMLCharacterEscapes extends JsonpCharacterEscapes {

        @Override
        public int[] getEscapeCodesForAscii() {
            int[] asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
            // and force escaping of a few others:
            asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
            asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
            asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
            asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
            asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
            return asciiEscapes;
        }

        @Override
        public SerializableString getEscapeSequence(int ch) {
            switch (ch) {
                case '&' : return new SerializedString("&#38;");
                case '<' : return new SerializedString("&#60;");
                case '>' : return new SerializedString("&#62;");
                case '\"' : return new SerializedString("&#34;");
                case '\'' : return new SerializedString("&#39;");
                default : return super.getEscapeSequence(ch);
            }
        }
    }
}

It covers all the cases.

Upvotes: 2

Kevin
Kevin

Reputation: 11

To remove XSS characters you just override AbstractJackson2HttpMessageConverter - this converter has responsibility to read request.inputStream to RequestBody object

@Component
public class XSSRequestBodyConverter extends AbstractJackson2HttpMessageConverter {
    public XSSRequestBodyConverter(ObjectMapper objectMapper) {
        super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
    }

@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {

    Object requestBody = super.read(type, contextClass, inputMessage);
    //Remove xss from requestBody here
    String requestInStr = objectMapper.writeValueAsString(requestBody);
    return objectMapper.readValue(replaceXSSCharacters(requestInStr), Object.class);
}


}

Upvotes: 1

Vasan
Vasan

Reputation: 4956

  1. Have a local String field in XSSRequestWrapper which holds the cleaned-up body (probably not suitable for large bodies).
  2. Populate this field in the constructor by reading request.getInputStream() and cleaning up the body the same way as parameters.
  3. Override getInputStream and getReader methods of HttpServletRequestWrapper, and construct an InputStream (string -> byte array -> ByteArrayInputStream) and Reader (StringReader) from the String field and return them respectively. Maybe cache the constructed InputStream and Reader objects for better performance for when the methods are called repeatedly.

You may also be interested in cleaning up JSON when it is being deserialized into Java object.

Upvotes: 0

Related Questions