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

About Ricardo Zuasti

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 our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

19 comments

  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. Wouldn’t you also want to override getParameterMap and getQueryString?

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

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

  5. 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. 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. 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. 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. 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. 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. 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

    • 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

      • “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?”

        Yes, that’s exactly what I mean, and the reason why goes back to CS theory.

        Input validation in every practical usage I’ve experienced utilizes regular expressions, however, HTML and Javascript are not regular languages. Because of this, its mathematically impossible to write an input filter that really lets you treat your data as “safe.” Even after being run through the filter, data should still be treated as dirty. its MUCH more important to do output-escaping whenever you’re crossing application boundaries, such as when data is being output to the user, or when passing data to another interpreter, for example, when passing data to a webservice/database query.

  12. XSS filter applied after error (MultipartHttpServletRequest )
    Why?
    Thank you.

    @RequestMapping(value=”/site/updateLogoproc.do”, method=RequestMethod.POST)
    public String updateLogo(MultipartHttpServletRequest mpRequest, @ModelAttribute(“logoVO”) LogoVO logoVO) throws Exception {

    File dir = new File(prop.getProperty(“LOGO_PATH”));

    if (!dir.isDirectory()) {
    dir.mkdirs();
    }

    Iterator it = mpRequest.getFileNames();

    System.out.println(it.hasNext()); // this false

  13. How to getParameter of hidden field and validate it, I tried to get parameter of hidden filed using getPatarmeter(String s) but it is not taking value of hidden field and hence I am not able to solve xss vulnerability of hidden field.
    Please help

  14. This leaves a lot of XSS attack go through.
    A simple will fly through without problems. A simple regular expression is way too weak to fix these issues.

  15. You can attempt to create pattern list on class load ( it is thread safe) and then use this :
    private String stripXSS(String value) {
    if (value != null) {
    for (Pattern scriptPattern : patternList) {
    value = scriptPattern.matcher(value).replaceAll(“”);
    }
    }
    return value;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

*


7 + = thirteen

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Do you want to know how to develop your skillset and become a ...

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!
Get ready to Rock!
To download the books, please verify your email address by following the instructions found on the email we just sent you.

THANK YOU!

Close