Enterprise Java

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.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

31 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
José Rodolfo Freitas
11 years ago

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.

Gonji
Gonji
11 years ago

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

Dinesh Ghutake
Dinesh Ghutake
11 years ago

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

Haitham Raik
Haitham Raik
10 years ago

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

Donnerbart
Donnerbart
10 years ago

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.

Sandeep Kumar
Sandeep Kumar
9 years ago

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?

Sahil Verma
Sahil Verma
9 years ago

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!

Gauri shankar
Gauri shankar
9 years ago
Reply to  Sahil Verma

@sahil
I am also facing same issue. Have you found any solution to fix alerts raised by fortify

avgvstvs
avgvstvs
9 years ago

Major problem here, this line of code is a NO-OP. value = value.replaceAll(“”, “”); Consider the following test case: @wong wong 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… Read more »

avgvstvs
avgvstvs
9 years ago

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.

Venkat
Venkat
9 years ago

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

avgvstvs
avgvstvs
9 years ago

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… Read more »

Venkat
Venkat
9 years ago
Reply to  avgvstvs

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

avgvstvs
avgvstvs
9 years ago
Reply to  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… Read more »

joohyun
joohyun
9 years ago

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

Deep
Deep
9 years ago

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

Guillaume
Guillaume
9 years ago

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.

Amit Parashar
Amit Parashar
8 years ago

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

Fidel
Fidel
7 years ago

Hi it works great!

i would like to know how can i redirect to another page in my aplication if some value match in a pathern. i mean a page with a warning message.

thankyou

avgvstvs
avgvstvs
7 years ago

No, it does not work great, and you all who think it does need to heed both my words and the words of Guillaume and myself. I am a developer on the ESAPI project and have worked as a security engineer for 7 years. Guillaume contributes to find-sec-bugs and at least one other OWASP project. This filter as written is false security.

Romina
Romina
7 years ago
Reply to  avgvstvs

Hey avgvstvs! So the better approach to avoid this kind of attacks is use directly Antisamy? What is your suggestion? Thanks!

gusg3
gusg3
7 years ago
Reply to  avgvstvs

And then … what is your suggestion ?

Nik
Nik
7 years ago

I have an issue with the code. On my Web Project in local, I am able to register a user, login but search is sending null input after adding this XSS filter.

Jim
Jim
6 years ago

This function is being copied into real projects. Can you add a warning that it’s insecure and shouldn’t be relied upon? Probably link to OWASP instead.

https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet

Back to top button