Anti cross-site scripting (XSS) filter for Java web apps

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 {

    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("", "");

            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid anything in a src='...' type of expression
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Remove any lonesome </script> tag
            scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid eval(...) expressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid expression(...) expressions
            scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid javascript:... expressions
            scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid vbscript:... expressions
            scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid onload= expressions
            scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            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.

Reference: Stronger anti cross-site scripting (XSS) filter for Java web apps from our JCG partner Ricardo Zuasti at the Ricardo Zuasti’s blog blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

14 Responses to "Anti cross-site scripting (XSS) filter for Java web apps"

  1. Awesome post, I see you mentioned that one should configure the filter
    as the first in the chain. Why is that? I can think that the reason is
    to also protect the other filters but I’m not sure if that’s the main reason.

    I was thinking about creating a jar with a web-fragment.xml and use it
    in my web applications, but then the filter wouldn’t be the first.

  2. Gonji says:

    Wouldn’t you also want to override getParameterMap and getQueryString?

  3. Dinesh Ghutake says:

    Very good post , that is exactly what i was looking for. Thanks a lot!

  4. Haitham Raik says:

    Very good post. you can also use AntiSamy to sanitize the user input (https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project)

  5. Donnerbart says:

    I think you want to pre-compile your Pattern just once.

    Instances of this (Pattern) class are immutable and are safe for use by multiple concurrent threads. Instances of the Matcher class are not safe for such use.
    http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html

    private static Pattern PATTERN_SCRIPT = Pattern.compile(“(.*?)”, Pattern.CASE_INSENSITIVE);

    private String stripXSS(String value) {
    if (value == null) {
    return;
    }
    value = PATTERN_SCRIPT.matcher(value).replaceAll(“”);
    return value;
    }

    etc.

  6. Sandeep Kumar says:

    Hey Ricardo, I read somewhere that blacklist approach is not a right approach to secure against XSS. How to solve this by whitelisting? also how to do this in multilingual applications?

  7. Sahil Verma says:

    Hi,

    I have followed all mentioned steps but i see HP Fortify is still raising XSS attacks issues after scanning my entire application. am i missing something?

    Thanks!

  8. avgvstvs says:

    Major problem here, this line of code is a NO-OP.

    value = value.replaceAll(“”, “”);

    Consider the following test case:

    @Test
    public void testNullStripWithEmptyString() {
    String input = “foo” + ”;
    String input2 = “foo”;
    println(input);
    println(“input:”);
    printBytes(input.getBytes());
    println(“input2:”);
    printBytes(input2.getBytes());
    String testValue = input.replaceAll(“”, “”);
    println(“testValue:”);
    printBytes(testValue.getBytes());
    String testvalue2 = input2.replaceAll(“”,””);
    println(“testvalue2″);
    printBytes(testvalue2.getBytes());
    assertFalse(input.equals(input2));
    assertFalse(testValue.equals(testvalue2));
    }

    public void printBytes(byte[] foo) {
    for(byte item:foo) {
    System.out.print(“” + item);
    }
    println(“”);
    }

    public static void println(String s) {
    System.out.println(s);
    }

    This test case demonstrates first, that in the byte representations of the two input strings, that the null byte appears in the first, but not in the second. We then proceed to call *.replaceAll(“”,””); and store the values into two new variables, testValue and testvalue2.

    This then leads to the first assert, which asserts that the two values should not be equal calling the normal String equals method. This is trivally true, because we DO have a nonprinting null byte appended to the string. However, the nail in the coffin is in demonstrating that this condition still holds after calling *.replaceAll(“”,””); on the two testValue strings.

    This is the second time that I’ve seen this use case on the internet, and I’m squashing it here. (The other was on stackoverflow.)

    The only way to prevent non-printing or NULL bytes would be to implement the following test case:

    @Test
    public void testNullStripWithNullUnicodeEscape(){
    String input = “foo” + ”;
    String input2 = “foo”;
    println(input);
    println(“input:”);
    printBytes(input.getBytes());
    println(“input2:”);
    printBytes(input2.getBytes());
    String testValue = input.replaceAll(“\u0000″, “”);
    println(“testValue:”);
    printBytes(testValue.getBytes());
    String testvalue2 = input2.replaceAll(“\u0000″,””);
    println(“testvalue2″);
    printBytes(testvalue2.getBytes());
    assertFalse(input.equals(input2));
    assertTrue(testValue.equals(testvalue2));
    }

    Its probably a good bet that char hex sequences would work too, BUT Java’s native encoding is UTF-16, so its FAR safer to implement using unicode escaping for this kind of work.

    Reference to the stackoverflow question is here: http://stackoverflow.com/questions/23587519/esapi-and-using-replaceall-for-blank-strings/23587531#23587531

    that test case just demonstrates that for the entire legal character set in Java that String.replaceAll(“”,””); is a NOP.

  9. avgvstvs says:

    http://stackoverflow.com/questions/23587519/esapi-and-using-replaceall-for-blank-string%E2%80%8C%E2%80%8Bs

    The piece of code value = value.replaceAll(“”, “”); is a NO-OP, please check the test cases in the above link for the appropriate method of stripping “null” or “nonprinting” characters.

  10. Venkat says:

    Hi,

    We have configured the filter in our web application but after the security scan it still shows some XSS vulnerabilities. Client is using BURP tool.

    Page:
    /ep2/applicationform/submitForm.do

    Parameters:
    PTL_ALIAS
    PTL_BOOKMARK_ID
    PTL_FORM_STATUS
    PTL_NUMBER
    closeFlag
    submitterName
    userType

    Thanks,
    Venkat

  11. avgvstvs says:

    Venkat, (and everyone else) its going to.

    It is patently NOT possible to input-validate away XSS attacks. The sheer amount of different browsers and encoding schemes means that you are ALWAYS going to leave some stone unturned.

    The only way to prevent XSS is to ensure that you’re escaping output for the correct context(s), and doing basic input validation on the front end.

    At no point do you EVER consider user input “trusted.”

    Burp Intruder + FuzzDB will unravel virtually ANY XSS-filter scheme. There’s a reason that OWASP has refused to write an XSS-Filtering library.

    This cheat sheet will show you many reasons why filtering is hard:

    https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet

    • Venkat says:

      avgvstvs,

      Earlier we used the filter you provided in your previous post and we were able to get through scan, can you please let me know what is the difference between these two filters.

      “It is patently NOT possible to input-validate away XSS attacks.” does this mean we cannot prevent XSS attacks completely by using this filter and it is better to do output escaping and basic input validations?

      Please do suggest.

      Thanks,
      Venkat

Leave a Reply


8 × nine =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close