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

About Ricardo Zuasti

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.

(0 rating, 0 votes)
You need to be a registered member to rate this.
27 Comments Views Tweet it!
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 ....
I agree to the Terms and Privacy Policy

27
Leave a Reply

avatar
22 Comment threads
5 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
23 Comment authors
Mani.zaeimArunabhJimgusg3Nik Recent comment authors

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

  Subscribe  
newest oldest most voted
Notify of
José Rodolfo Freitas
Guest

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
Guest
Gonji

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

Dinesh Ghutake
Guest
Dinesh Ghutake

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

Haitham Raik
Guest
Haitham Raik

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

Donnerbart
Guest
Donnerbart

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
Guest
Sandeep Kumar

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?

Peter Jurkovic
Guest
Sahil Verma
Guest
Sahil Verma

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
Guest
Gauri shankar

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

avgvstvs
Guest
avgvstvs

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

avgvstvs
Guest
avgvstvs

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
Guest
Venkat

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
Guest
avgvstvs

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
Guest
Venkat

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
Guest
avgvstvs

“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
Guest
joohyun

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
Guest
Deep

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
Guest
Guillaume

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
Guest
Amit Parashar

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
Guest
Fidel

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
Guest
avgvstvs

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
Guest
Romina

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

gusg3
Guest
gusg3

And then … what is your suggestion ?

Nik
Guest
Nik

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
Guest
Jim

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