posted 17-Mar-2012 | 14 comments | , , , ,

Jul/4/2012: Updated the wrapper code to pre-compile the patterns (making them static) to improve performance by avoiding their re-compilation on each run.

Here is a good and simple anti cross-site scripting (XSS) filter written for Java web applications. What it basically does is remove all suspicious strings from request parameters before returning them to the application. It’s an improvement over my previous post on the topic.

You should configure it as the first filter in your chain (web.xml) and it’s generally a good idea to let it catch every request made to your site.

The actual implementation consists of two classes, the actual filter is quite simple, it wraps the HTTP request object in a specialized HttpServletRequestWrapper that will perform our filtering.

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);
    }

}

The wrapper overrides the getParameterValues(), getParameter() and getHeader() methods to execute the filtering before returning the desired field to the caller. The actual XSS checking and striping is performed in the stripXSS() private method.

import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class XSSRequestWrapper extends HttpServletRequestWrapper {

    private static Pattern[] patterns = new Pattern[]{
        // Script fragments
        Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
        // src='...'
        Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        // lonely script tags
        Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
        Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        // eval(...)
        Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        // expression(...)
        Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        // javascript:...
        Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
        // vbscript:...
        Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
        // onload(...)=...
        Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    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] = stripXSS(values[i]);
        }

        return encodedValues;
    }

    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);

        return stripXSS(value);
    }

    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        return stripXSS(value);
    }

    private String stripXSS(String value) {
        if (value != null) {
            // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to
            // avoid encoded attacks.
            // value = ESAPI.encoder().canonicalize(value);

            // Avoid null characters
            value = value.replaceAll("\0", "");

            // Remove all sections that match a pattern
            for (Pattern scriptPattern : patterns){
                value = scriptPattern.matcher(value).replaceAll("");
            }
        }
        return value;
    }
}

Notice the comment about the ESAPI library, I strongly recommend you check it out and try to include it in your projects.

If you want to dig deeper on the topic I suggest you check out the OWASP page about XSS and RSnake’s XSS (Cross Site Scripting) Cheat Sheet.

  • John

    Glorious!  Thank you Ricardo!

  • Vincent

    Actually, you’ve made a great filter that filters almost all XSS attacks. However, you miss some patterns. You’ll find these other patterns at http://ha.ckers.org/xss.html. This will for sure improve your XSS filter !

  • http://ricardozuasti.com/ Ricardo Zuasti

    Vincent, hi. Actually most of the patterns are based of RSnake’s list (I even included a link in the article itself :)), I’ll review it again though to see if I left something out.

  • http://profiles.google.com/sebbalex Alessandro Sebastiani

    Great article!
    by the way will be very useful something similar for sql injection for those who don’t use hibernate!
    Thanks!

  • Pingback: JavaPins

  • inProblem

    “;alert(‘XSS’);// this pattern is not included :(

  • chanakya

    Thank man…it worked like charm….

  • kumar

    hi, i am trying to implement the xss filter. When will the getParameterValues will be executed?
    how can i validate every form field regarding cross site scripting

  • http://ricardozuasti.com/ Ricardo Zuasti

    Kumar, hi. Once the filter is executed all the server side components that access the request will do so through the wrapper class, therefore using the controlled getParameter and getParameterValues functions.

    This implies that any servlet, JSP page, or other form of server side component that access a POST or GET parameter submitted to your server will be protected against the covered XSS attacks. The only components that may be unprotected are other filters that are configured to be executed prior to the XSS one on the app chain.

    rgds,
    r.

  • kumar

    Hi Ricardo,
    Thanks for the quick reply.

    we are using spring web flows. I configured the two files(Filter and request Wrapper) as suggested by you. I have a form with 5 fileds and i entered hello in one of the fields. I can see the logs in getParameter,getHeader of ReqeustWrapper.But am unable to see the logs from getParameterValues.. where i think the elimination of patterns from the form fields takes  place.
    Can you please help me on this issue..
    Regards
    Surendra Batchu

  • http://ricardozuasti.com/ Ricardo Zuasti

    Kumar, which method of the wrapper gets executed depends on the calling party, usually a component prefers one way to access parameters and therefore doesn’t use the other(s). If your problematic input (hi) makes it through to the spring component, maybe its because the framework (spring) is using a method other than getParameter or getParameterValues to access the request data.

    The most likely option that comes to mind is that spring is using the getParameterMap method, if so, the fix is easy, just override that function in the request wrapper class and execute the stripXSS method on each map value before returning the map.

    cheers
    r.

  • Gopi

    It won’t works for event handlers…. XSS

  • martin

    Why do you create a new XSSRequestWrapper on every doFilter call? Wouldn’t it improve performance to create the class in the init method? Or am I missing something here?

  • http://ricardozuasti.com/ Ricardo Zuasti

    Martin, each request wrapper instance “holds” a single http request representing a users request received by the web server. Then its important each instance is isolated from the others to keep your app safe (both from a security and functional consistency perspectives).

    cheers,
    r.