76

How can I prevent XSS attacks in a JSP/Servlet web application?

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
newbie
  • 22,918
  • 75
  • 190
  • 297
  • The great post how to prevent XSS attacks in different situations is posted there: http://stackoverflow.com/questions/19824338/avoid-xss-and-allow-some-html-tags-with-javascript/19943011#19943011 – user1459144 Nov 13 '13 at 00:49

9 Answers9

115

XSS can be prevented in JSP by using JSTL <c:out> tag or fn:escapeXml() EL function when (re)displaying user-controlled input. This includes request parameters, headers, cookies, URL, body, etc. Anything which you extract from the request object. Also the user-controlled input from previous requests which is stored in a database needs to be escaped during redisplaying.

For example:

<p><c:out value="${bean.userControlledValue}"></p>
<p><input name="foo" value="${fn:escapeXml(param.foo)}"></p>

This will escape characters which may malform the rendered HTML such as <, >, ", ' and & into HTML/XML entities such as &lt;, &gt;, &quot;, &apos; and &amp;.

Note that you don't need to escape them in the Java (Servlet) code, since they are harmless over there. Some may opt to escape them during request processing (as you do in Servlet or Filter) instead of response processing (as you do in JSP), but this way you may risk that the data unnecessarily get double-escaped (e.g. & becomes &amp;amp; instead of &amp; and ultimately the enduser would see &amp; being presented), or that the DB-stored data becomes unportable (e.g. when exporting data to JSON, CSV, XLS, PDF, etc which doesn't require HTML-escaping at all). You'll also lose social control because you don't know anymore what the user has actually filled in. You'd as being a site admin really like to know which users/IPs are trying to perform XSS, so that you can easily track them and take actions accordingly. Escaping during request processing should only and only be used as latest resort when you really need to fix a train wreck of a badly developed legacy web application in the shortest time as possible. Still, you should ultimately rewrite your JSP files to become XSS-safe.

If you'd like to redisplay user-controlled input as HTML wherein you would like to allow only a specific subset of HTML tags like <b>, <i>, <u>, etc, then you need to sanitize the input by a whitelist. You can use a HTML parser like Jsoup for this. But, much better is to introduce a human friendly markup language such as Markdown (also used here on Stack Overflow). Then you can use a Markdown parser like CommonMark for this. It has also builtin HTML sanitizing capabilities. See also Markdown or HTML.

The only concern in the server side with regard to databases is SQL injection prevention. You need to make sure that you never string-concatenate user-controlled input straight in the SQL or JPQL query and that you're using parameterized queries all the way. In JDBC terms, this means that you should use PreparedStatement instead of Statement. In JPA terms, use Query.


An alternative would be to migrate from JSP/Servlet to Java EE's MVC framework JSF. It has builtin XSS (and CSRF!) prevention over all place. See also CSRF, XSS and SQL Injection attack prevention in JSF.

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • 1
    Just because you're using Hibernate, doesn't mean you're safe from SQL injection. See http://blog.harpoontech.com/2008/10/how-to-avoid-sql-injection-in-hibernate.html for example. – MatrixFrog Sep 16 '11 at 23:07
  • @chad: that's not true. It's only the case when you're string-concatenating user-controlled input straight in the SQL/HQL/JPQL query like so `"SELECT ... WHERE SOMEVAL = " + someval` instead of using parameterized queries as you've shown. No one ORM can safeguard against this kind of developer mistakes. – BalusC Feb 10 '12 at 21:33
  • @BalusC - Doh! I had that reversed. Vulnerable example is: Query query = session.createQuery("SELECT * FROM TABLE WHERE SOMEVAL = " + someval); Using the binding syntax ":" in Hibernate (like my example above) prevents SQL injection. Deleting comment to prevent someone using my bad example. – chadmaughan Feb 10 '12 at 21:40
  • 5
    I think you DO have to validate in the server aswell. All the validation can be bypassed by altering the HTTP parameters. And sometimes, the data that you persist can be consumed by other applications in an enterprise app. Sometimes you dont have access to the views of the other applications so you need to sanitze the input before persisting in the database. – Guido Celada Oct 09 '14 at 14:38
  • 1
    @Guido: you're not understanding the problem. – BalusC Oct 09 '14 at 15:10
  • @BalusC: please expand. – Guido Celada Oct 09 '14 at 15:18
  • @BalusC: Hi Bal, by using yours suggestion I get removed 4 XSS issues in my app thanks a lot for useful solution. Here I need yours suggestion again as I displaying dom object using jstl espression like `
    ` here `htmlContent`value is something like `

    Hi

    ` (this value coming from database) now if I remove `escapeXml="false"` from `c:out` then its displays as it is on page then if keep `escapeXml="false"` its parsing the dom object properly but when my htmlContent having some script code then xss issue is coming.
    – Venki Jan 04 '17 at 13:57
  • @BalusC: if `htmlContent` is `

    ` then if I use `` in jsp then on browser user will get alert box so it may leades XSS issue. Please suggest me what I can do in this situation.
    – Venki Jan 04 '17 at 14:00
  • @Venki: Just read 4th paragraph of answer which tells something about dealing with user-controlled HTML. – BalusC Jan 04 '17 at 14:47
  • Great answer @BalusC Thanks a bunch! Quick question though if you don't mind. So i shouldn't escape html on user input, only when displaying it? Is that what you mean by double escaping? And if not, how does one go about escaping user input using a textarea, not an input field? Is ( htmlEscape="true" ) correct? – Jonathan Laliberte Jul 22 '17 at 00:00
  • Please note: "HTML entity encoding doesn't work if you're putting untrusted data inside a – peater Jun 06 '19 at 14:52
  • 2
    @peater: Yup, when putting untrusted data inside JS code, you need to [JS-encode](https://stackoverflow.com/q/9708242) instead of HTML-encode. And, when putting untrusted data inside CSS code, you need to CSS-encode instead of HTML-encode. And, when putting untrusted data inside URLs, you need to [URL-encode](https://stackoverflow.com/q/10786042/) instead of HTML-encode. HTML encoding should only be used for putting untrusted data inside HTML code. – BalusC Jun 06 '19 at 15:00
  • Just to add some info here, escapeXml default property in c:out tag is 'true' – Raphael Onofre Oct 01 '19 at 12:25
12

The how-to-prevent-xss has been asked several times. You will find a lot of information in StackOverflow. Also, OWASP website has an XSS prevention cheat sheet that you should go through.

On the libraries to use, OWASP's ESAPI library has a java flavour. You should try that out. Besides that, every framework that you use has some protection against XSS. Again, OWASP website has information on most popular frameworks, so I would recommend going through their site.

Sripathi Krishnan
  • 29,496
  • 4
  • 69
  • 81
  • 1
    The OWASP cheat sheets have moved to GitHub. Here is the link for the XSS Prevention Cheat Sheet https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md – peater Jun 06 '19 at 14:54
12

I had great luck with OWASP Anti-Samy and an AspectJ advisor on all my Spring Controllers that blocks XSS from getting in.

public class UserInputSanitizer {

    private static Policy policy;
    private static AntiSamy antiSamy;

    private static AntiSamy getAntiSamy() throws PolicyException  {
        if (antiSamy == null) {
            policy = getPolicy("evocatus-default");
            antiSamy = new AntiSamy();
        }
        return antiSamy;

    }

    public static String sanitize(String input) {
        CleanResults cr;
        try {
            cr = getAntiSamy().scan(input, policy);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return cr.getCleanHTML();
    }

    private static Policy getPolicy(String name) throws PolicyException {
        Policy policy = 
            Policy.getInstance(Policy.class.getResourceAsStream("/META-INF/antisamy/" + name + ".xml"));
        return policy;
    }

}

You can get the AspectJ advisor from the this stackoverflow post

I think this is a better approach then c:out particular if you do a lot of javascript.

Community
  • 1
  • 1
Adam Gent
  • 44,449
  • 20
  • 142
  • 191
  • The normal practice is to HTML-escape any user-controlled data during redisplaying, not during processing the submitted data in servlet nor during storing in DB. If you HTML-escape it during processing the submitted data and/or storing in DB as well, then it's all spread over the business code and/or in the database. That's only maintenance trouble and you will risk double-escapes or more when you do it at different places. The business code and DB are in turn not sensitive for XSS. Only the view is. You should then escape it only right there in view. – Shubham Maheshwari Aug 29 '15 at 13:28
  • 1
    Yes and no. Although the general practice is to escape on display there are many reasons you might want to sanitize on write. There are some cases where you do want your users to enter a subset of HTML and although you could sanitize on display this is actually rather slow and even confusing to users. Second if you share the data with 3rd party services like external APIs those services may or may not do the proper sanitizing themselves. – Adam Gent Aug 30 '15 at 12:14
  • as you and me both mentioned, the "normal practice" is to escape on display. What you have mentioned in you above comment are more specific use cases and hence would agreeably require specific solutions. – Shubham Maheshwari Sep 30 '15 at 18:06
  • Yes I perhaps should make my use case more clear. I work on mainly content management (HTML editing) things. – Adam Gent Sep 30 '15 at 18:14
8

Managing XSS requires multiple validations, data from the client side.

  1. Input Validations (form validation) on the Server side. There are multiple ways of going about it. You can try JSR 303 bean validation(hibernate validator), or ESAPI Input Validation framework. Though I've not tried it myself (yet), there is an annotation that checks for safe html (@SafeHtml). You could in fact use Hibernate validator with Spring MVC for bean validations -> Ref
  2. Escaping URL requests - For all your HTTP requests, use some sort of XSS filter. I've used the following for our web app and it takes care of cleaning up the HTTP URL request - http://www.servletsuite.com/servlets/xssflt.htm
  3. Escaping data/html returned to the client (look above at @BalusC explanation).
MasterV
  • 1,132
  • 13
  • 17
3

I would suggest regularly testing for vulnerabilities using an automated tool, and fixing whatever it finds. It's a lot easier to suggest a library to help with a specific vulnerability then for all XSS attacks in general.

Skipfish is an open source tool from Google that I've been investigating: it finds quite a lot of stuff, and seems worth using.

Sean Reilly
  • 20,366
  • 3
  • 46
  • 61
3

There is no easy, out of the box solution against XSS. The OWASP ESAPI API has some support for the escaping that is very usefull, and they have tag libraries.

My approach was to basically to extend the stuts 2 tags in following ways.

  1. Modify s:property tag so it can take extra attributes stating what sort of escaping is required (escapeHtmlAttribute="true" etc.). This involves creating a new Property and PropertyTag classes. The Property class uses OWASP ESAPI api for the escaping.
  2. Change freemarker templates to use the new version of s:property and set the escaping.

If you didn't want to modify the classes in step 1, another approach would be to import the ESAPI tags into the freemarker templates and escape as needed. Then if you need to use a s:property tag in your JSP, wrap it with and ESAPI tag.

I have written a more detailed explanation here.

http://www.nutshellsoftware.org/software/securing-struts-2-using-esapi-part-1-securing-outputs/

I agree escaping inputs is not ideal.

brett.carr
  • 159
  • 1
  • 3
2

If you want to automatically escape all JSP variables without having to explicitly wrap each variable, you can use an EL resolver as detailed here with full source and an example (JSP 2.0 or newer), and discussed in more detail here:

For example, by using the above mentioned EL resolver, your JSP code will remain like so, but each variable will be automatically escaped by the resolver

...
<c:forEach items="${orders}" var="item">
  <p>${item.name}</p>
  <p>${item.price}</p>
  <p>${item.description}</p>
</c:forEach>
...

If you want to force escaping by default in Spring, you could consider this as well, but it doesn't escape EL expressions, just tag output, I think:

http://forum.springsource.org/showthread.php?61418-Spring-cross-site-scripting&p=205646#post205646

Note: Another approach to EL escaping that uses XSL transformations to preprocess JSP files can be found here:

http://therning.org/niklas/2007/09/preprocessing-jsp-files-to-automatically-escape-el-expressions/

Brad Parks
  • 54,283
  • 54
  • 221
  • 287
  • Hi Brad, Above use case is what i am looking for.could you please help explain how can you prevent xss in case of above scenario(foreach one) – Samir Vasani May 08 '20 at 03:21
  • The only one I really remember now is using the EL resolver - that's what we ended up using at our company. Basically it automatically escapes everything, and if you really really dont want something escaped, you can wrap it in `` as detailed in the article. – Brad Parks May 08 '20 at 11:37
2

My personal opinion is that you should avoid using JSP/ASP/PHP/etc pages. Instead output to an API similar to SAX (only designed for calling rather than handling). That way there is a single layer that has to create well formed output.

Tom Hawtin - tackline
  • 139,906
  • 30
  • 206
  • 293
0

If you want to make sure that your $ operator does not suffer from XSS hack you can implement ServletContextListener and do some checks there.

The complete solution at: http://pukkaone.github.io/2011/01/03/jsp-cross-site-scripting-elresolver.html

@WebListener
public class EscapeXmlELResolverListener implements ServletContextListener {
    private static final Logger LOG = LoggerFactory.getLogger(EscapeXmlELResolverListener.class);


    @Override
    public void contextInitialized(ServletContextEvent event) {
        LOG.info("EscapeXmlELResolverListener initialized ...");        
        JspFactory.getDefaultFactory()
                .getJspApplicationContext(event.getServletContext())
                .addELResolver(new EscapeXmlELResolver());

    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOG.info("EscapeXmlELResolverListener destroyed");
    }


    /**
     * {@link ELResolver} which escapes XML in String values.
     */
    public class EscapeXmlELResolver extends ELResolver {

        private ThreadLocal<Boolean> excludeMe = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return Boolean.FALSE;
            }
        };

        @Override
        public Object getValue(ELContext context, Object base, Object property) {

            try {
                    if (excludeMe.get()) {
                        return null;
                    }

                    // This resolver is in the original resolver chain. To prevent
                    // infinite recursion, set a flag to prevent this resolver from
                    // invoking the original resolver chain again when its turn in the
                    // chain comes around.
                    excludeMe.set(Boolean.TRUE);
                    Object value = context.getELResolver().getValue(
                            context, base, property);

                    if (value instanceof String) {
                        value = StringEscapeUtils.escapeHtml4((String) value);
                    }
                    return value;
            } finally {
                excludeMe.remove();
            }
        }

        @Override
        public Class<?> getCommonPropertyType(ELContext context, Object base) {
            return null;
        }

        @Override
        public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base){
            return null;
        }

        @Override
        public Class<?> getType(ELContext context, Object base, Object property) {
            return null;
        }

        @Override
        public boolean isReadOnly(ELContext context, Object base, Object property) {
            return true;
        }

        @Override
        public void setValue(ELContext context, Object base, Object property, Object value){
            throw new UnsupportedOperationException();
        }

    }

}

Again: This only guards the $. Please also see other answers.

Alireza Fattahi
  • 33,509
  • 12
  • 96
  • 140