Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@InitBinder with @RequestBody escaping XSS in Spring 3.2.4

I am having a @RequestBody annotated argument in my method like this:

@RequestMapping(value = "/courses/{courseId}/{name}/comment", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public @ResponseBody CommentContainer addComment(@PathVariable Long courseId,
                          @ActiveAccount Account currentUser,
                          @Valid @RequestBody AddCommentForm form,
                          BindingResult formBinding,
                          HttpServletRequest request) throws RequestValidationException {

.....
}

Then I have a @InitBinder annotated method in the same controller:

@InitBinder
public void initBinder(WebDataBinder dataBinder) {
    dataBinder.registerCustomEditor(AddCommentForm.class, new StringEscapeEditor());
}

My StringEscapeEditor is not running. But my initBinder method is. So it does not mapping my form to the escape editor. This seems right after reading this thread (Where it seems like @RequestMapping is not supported by @InitBinder):

spring mvc @InitBinder is not called when processing ajax request

And i tested to map a @PathVariable string and then my editor is working.

This is a big deal in my application since most of my bindings is done with @RequestBody and it would be great if i could apply some custom bindings to it.

What is the most common way to solve this problem? and to escape my input data for script attacks.

like image 784
nilsi Avatar asked Feb 12 '23 05:02

nilsi


1 Answers

To escape XSS I suggest that escaping is done while outputting the data, because correct escaping depends on the output document.

If JSON response generated by @ResponseBody is consumed directly by the client and there is no opportunity to XSS escape the content, then JacksonMessageConverter can be customised to perform XSS escaping on strings.

One can customise JacksonMessageConverter like this:

1) First we create ObjectMapper factory that will create our custom object mapper:

public class HtmlEscapingObjectMapperFactory implements FactoryBean<ObjectMapper> {

    private final ObjectMapper objectMapper;

    public HtmlEscapingObjectMapperFactory() {
        objectMapper = new ObjectMapper();
        objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
    }

    @Override
    public ObjectMapper getObject() throws Exception {
        return objectMapper;
    }

    @Override
    public Class<?> getObjectType() {
        return ObjectMapper.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public static class HTMLCharacterEscapes extends CharacterEscapes {

        private final int[] asciiEscapes;

        public HTMLCharacterEscapes() {
            // start with set of characters known to require escaping (double-quote, backslash etc)
            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;
        }


        @Override
        public int[] getEscapeCodesForAscii() {
            return asciiEscapes;
        }

        // and this for others; we don't need anything special here
        @Override
        public SerializableString getEscapeSequence(int ch) {
            return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));

        }
    }
}

(inspiration for HtmlCharacterEscapes came from this question: HTML escape with Spring MVC and Jackson Mapper)

2) Then we register the message converter that uses our custom object mapper (example in xml config):

<bean id="htmlEscapingObjectMapper" class="com.example.HtmlEscapingObjectMapperFactory" />

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" p:objectMapper-ref="htmlEscapingObjectMapper" />
    </mvc:message-converters>
</mvc:annotation-driven>

Now all the JSON messages created by @ResponseBody should have strings escaped as specified in HTMLCharacterEscapes.

Alternative solutions to the problem:

  • XSS escape what you need in the controller body after the objects have been deserialised
  • maybe XSS escape in javascript on the client before outputting the content

In addition to doing output escaping, it may be useful to also do some input validation (using standard Spring validation methods) to block some of the content that you don't want to be entered into the system / database.

EDIT: JavaConfig

I haven't tried this out but in Java config it should work like this (you won't need Factory Bean from above because you can set up everything in config in this case):

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    super.configureMessageConverters(converters);
    converters.add(buildHtmlEscapingJsonConverter());

}

private MappingJacksonHttpMessageConverter buildHtmlEscapingJsonConverter() {
    MappingJacksonHttpMessageConverter htmlEscapingConverter = new MappingJacksonHttpMessageConverter();
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
    htmlEscapingConverter.setObjectMapper(objectMapper);
    return htmlEscapingConverter;       
}

Please be aware that any other non-json default message converters that would normally be configured will now be lost (e.g. XML converters etc..) and if you need them, you will need to add them manually (you can see what's active by default here in section 2.2: http://www.baeldung.com/spring-httpmessageconverter-rest)

like image 113
Krešimir Nesek Avatar answered Feb 14 '23 18:02

Krešimir Nesek