Fixing XSS in JSP 2
Way back in 2007, I wrote about Java Web Frameworks and XSS. My main point was that JSP EL doesn't bother to handle XSS.
Of course, the whole problem with JSP EL could be solved if Tomcat (and other containers) would allow a flag to turn on XML escaping by default. IMO, it's badly needed to make JSP-based webapps safe from XSS.
A couple months later, I proposed a Tomcat enhancement to escape JSP's EL by default. I also entered an enhancement request for this feature and attached a patch. That issue has remained open and unfixed for 3 and 1/2 years.
Yesterday, Chin Huang posted a handy-dandy ELResolver that XML-escapes EL values.
I tried Chin's resolver in AppFuse today and it works as well as advertised. To do this, I copied his EscapeXML*.java files into my project, changed the JSP API's Maven coordinates from javax.servlet:jsp-api:2.0 to javax.servlet.jsp:jsp-api:2.1 and added the listener to web.xml.
With Struts 2 and Spring MVC, I was previously able to have ${param.xss} and pass in ?xss=<script>alert('gotcha')</script> and it would show a JavaScript alert. After using Chin's ELResolver, it prints the string on the page instead of displaying an alert.
Thanks to Chin Huang for this patch! If you're using JSP, I highly recommend you add this to your projects as well.
Posted by Jay Spring on March 01, 2011 at 08:18 PM MST #
Posted by Chin Huang on March 01, 2011 at 08:36 PM MST #
This is a really clever use of an ELResolver to solve the JSP unescaped content problem. Nice work.
The big problem I see with this approach is that there is no way to disable the XML escaping of content. On every project I've worked on there are always a few use cases where some of the content being rendered in a JSP is HTML content, requiring unescaped strings to be rendered. If *all* EL resolving is escaped then it becomes impossible to ever render any unescaped content. I don't think resorting an a JSP scriptlet is a good solution, as normally my projects will have scripting-invalid=true. Even if you implemented a custom tag specifically to render HTML content such as this, you wouldn't be able to use any EL to pass the property into the tag from a JSP!
The c:out tag is also broken by this approach, whereby the default behaviour of escapeXml=true now double-escapes any HTML content, and escapeXml=false is ignored (as the EL has already been resolved, and escaped, prior to the value being passed in to the tag), effectively now behaving as escapeXml=true.
Do you have any clever ideas or suggestions of how to circumvent this problem? A few options I've come up with, none of which I'm too enamoured with, are...
- Allow a page-scoped property 'el.escapeXml=false' to be set. Check for this in the ELResolver and if set and false, don't do the escaping.
- Allow all EL expressions to be suffixed with 'noEscapeXml' i.e. ${someProperty.noEscapeXml} - Have the ELResolver wrap the String that is currently being escaped in a custom object 'EscapableStringWrapper' whose toString() method performs the escaping. The resolver could then check for such an object being passed in as the 'base' object in a getValue() call.
These approaches all seem quite hacky to me - perhaps you can think of some clever alternative?
Posted by James Wiltshire on March 04, 2011 at 04:52 AM MST #
Posted by Chin Huang on March 08, 2011 at 03:59 AM MST #
Posted by casopi on April 26, 2012 at 03:56 PM MDT #
You could change the JSP compiler to write escaped value when using ${} to output on a jsp page and thus not breaking EL functions nor JSTL.
e.g. Using this option, <c:out will still work fine
DrawBacks :
- This a vendor specific solution and can not be done without modifying compiler code
Posted by Benoit Prat on May 30, 2012 at 04:19 PM MDT #
Unfortunately, HTML escaping isn't enough so this approach is fundamentally broken. All you're getting is a false sense of security, because EL can be used in many places where HTML escaping simply doesn't work to prevent XSS. See:
http://www.coverity.com/srl/a-guide-to-fixing-xss-for-devs.html
Posted by Andy on April 04, 2013 at 05:06 PM MDT #