Saturday, August 29, 2015

Simple Inbound XSS Filter for Spring Security

This is a simple filter to look for XSS in requests. It throws an exception if it finds one.

It looks at all GET and POST parameter names and values, as well as all header names and values.

Examples:

GET /foo?name=<script>alert('');</script>
(exception)

POST /bar
<script>alert('');</script>=someValue
(exception)

PUT /baz
Accept-Language=<script>alert('')</script>
(exception)

Add a Maven dependency for JSoup:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.8.3</version>
</dependency>

SimpleInboundXssFilter.java:

public class SimpleInboundXssFilter extends GenericFilterBean {

    private Cleaner cleaner = new Cleaner(Whitelist.none());

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;

        Parser parser = Parser.xmlParser();

        /* GET and POST parameters: */
        Map params = servletRequest.getParameterMap();

        for(Map.Entry entry : params.entrySet()) {
            String key = entry.getKey();

            if(!cleaner.isValid(getFragmentAsDocument(key, parser))) {
                throw new InboundXssException();
            }

            String[] values = entry.getValue();
            for(String value : values) {
                if(!cleaner.isValid(getFragmentAsDocument(value, parser))) {
                    throw new InboundXssException();
                }
            }
        }

        Enumeration headerNames = request.getHeaderNames();
        while(headerNames.hasMoreElements()){
            String key = headerNames.nextElement();
            if(!cleaner.isValid(getFragmentAsDocument(key, parser))) {
                throw new InboundXssException();
            }

            Enumeration values = request.getHeaders(key);
            while(values.hasMoreElements()){
                String value = values.nextElement();
                if(!cleaner.isValid(getFragmentAsDocument(value, parser))) {
                    throw new InboundXssException();
                }
            }

        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    private Document getFragmentAsDocument(CharSequence value, Parser parser) {
        Document fragment = Jsoup.parse(value.toString(), "", parser);
        Document document = Document.createShell("");
        Iterator nodes = fragment.children().iterator();

        while(nodes.hasNext()) {
            document.body().appendChild((Node)nodes.next());
        }

        return document;
    }

    public class InboundXssException extends RuntimeException{}
}



In applicationContext-security.xml add:

<http>
...
<custom-filter ref="xssFilter" before="FIRST"/>
...
</http>

<beans:bean class="com.example.SimpleInboundXssFilter" id="xssFilter"/>