Recently I have had the opportunity to fix a cross-site-scripting problem.
The problem: a lot of JSON objects are being sent over the wire and the data is not being html escaped. This means that anyone who would put html data IN would get it out and make any user vulnerable for XSS attacks.
In this case, JSON objects are being created by using the MappingJacksonHttpMessageConverter. This is deliverd by the Spring framework. Normally it is instantiated when you use spring-mvc (using the mvc-annotation tag). This allowed us to just return an object and the MappingJacksonHttpMessageConverter would take care of translating it into a JSON object.
In order to influence the creation of the JSON object and make it encode HTML for String values we need to do a few things:
- Create a Custom Object Mapper and wire into Spring
- Create a JsonSerializer that encodes HTML (using ESAPI)
- Register the JsonSerializer in the CustomObjectMapper
Create a Custom Object Mapper and wire into Spring
However, now we want to change the value serialized by the MappingJacksonHttpMessageConverter. So how do we do that? The MappingJacksonHttpMessageConverter uses an ObjectMapper. So in order to get the ObjectMapper map the values to an encoded one we need to create a custom Object mapper.
public class CustomObjectMapper extends org.codehaus.jackson.map.ObjectMapper { }
At first we leave it empty. We want to make Spring make use of our CustomObjectMapper first. In order to do that you need to write out the mvc-annotation tag, because we need to inject our CustomObjectMapper in the the MappingJacksonHttpMessageConverter. This is a bit difficult to figure out at first, but for your convinience I have proveded the configuration you need.
<bean id="CustomObjectMapper" class="your custom object mapper"/> <bean id="MappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> <property name="objectMapper" ref="CustomObjectMapper"/> </bean> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="order" value="1" /> <property name="customArgumentResolver" ref="sessionParamResolver"/> <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <!-- <property name="conversionService" ref="conversionService" /> --> <property name="validator" ref="validator" /> </bean> </property> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" /> <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter" /> <bean class="org.springframework.http.converter.FormHttpMessageConverter" /> <ref bean="MappingJacksonHttpMessageConverter"/> </list> </property> </bean>
As you can see, the AnnotationMethodHandlerAdapter is the bean where everything is getting wired into. The messageConverters property contains a list of converters. The last entry is where the MappingJacksonHttpMessageConverter is referenced. Normally it is defined like the beans above it. But since we want to inject our own CustomObjectMapper it is done this way.
Create a JsonSerializer that encodes HTML (using ESAPI)
Now we know our CustomObjectMapper is used, we can start the part where we want to influence the way the jackson framework serializes our objects to JSON. In order to do that we create our own JsonSerializer. Then, we override the serialize method and make sure the provided jsonGenerator is writing our encoded value.
Code:
public class JsonHtmlXssSerializer extends JsonSerializer { public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { if (value != null) { String encodedValue = encodeHtml(value); jsonGenerator.writeString(encodedValue); } } }
So this class extends from org.codehaus.jackson.map.JsonSerializer, and implements its own serialize method. The method encodeHtml will use the ESAPI encoding functions to escape HTML.
Implementing this is very easy:
protected String encodeHtml(String html) { DefaultEncoder encoder = new DefaultEncoder(); return encoder.encodeForHTML(html); }
The defaultEncoder is from ESAPI allowing us to encode the HTML.
Register the JsonSerializer in the CustomObjectMapper
The final step is to register the JsonSerializer within the ObjectMapper used by Spring. Doing that using Jackson 1.7.0 is very easy. Using the provided SimpleModule class we can easily add our own Serializer to the ObjectMapper. We do that by creating our own default constructor, which will register our serializer. Like so:
@Component public class CustomObjectMapper extends org.codehaus.jackson.map.ObjectMapper { public CustomObjectMapper() { SimpleModule module = new SimpleModule("HTML XSS Serializer", new Version(1, 0, 0, "FINAL")); module.addSerializer(new JsonHtmlXssSerializer()); this.registerModule(module); } }