Monday, November 08, 2010

XSS and CSRF protection in Spring MVC Framework

UPDATE: Use UUID.randomUUID().toString() instead of trying to build the token yourself. No need to explain what these vulnerabilities are about. As developers it is important to understand them and to protect the software we create against them.

At the time of this writing Spring MVC Framework is still not providing an out-of-the-box protection

XSS protection can be achieved sanitizing the requests and the responses. In Java world you use a filter for the first. For the second XML must be escaped. JSTL has broad support for it as Spring tags has. If you are using scriptlets be sure to come up with your own ecaping mechanism (I love scriptlets but certainly you are safer using taglibs. Beware if you use taglibs to sanitize your resulting-from-request-responses.

There are several samples of XSS filters out there. I have used some variations of Stripes XSSFilter in the past. There are also several libraries that provide XSS protection but they will commonly push for you to use their own taglibs. I prefer to do it myself while I wait for native Spring support.

CSRF protection can be achieved (or at least mitigated) through the use of the Synchronizer Token Pattern. While again a filter or even a listener could be used I prefer to rely on simple Controller logic. In reality I prefer to protect my BHUB single entry points.

So I have a RootController from where I inherit (I am excluding my internal wrappers around getting and setting in session and request attributes but the below should be self explanatory):

@Controller
public class RootController {
...
    protected void init(ControllerContext ctx) {
        ctx.setRequestAttribute("module", ctx.getModuleNameFromCurrentUrl());
        setAdvancedSearchAvailable(ctx, false);
        initializeCsfrToken(ctx);
    }

    private void initializeCsfrToken(ControllerContext ctx) {
       String csrfToken = ctx.getSessionAttribute(ControllerContext.CSRF_TOKEN, "");
       if(Utils.isEmpty(csrfToken)) {
           ctx.setSessionAttribute(ctx, ControllerContext.CSRF_TOKEN, generateCsrfToken(ctx));
       }
       ctx.setRequestAttribute(ControllerContext.CSRF_TOKEN, csrfToken);
    }
    
    private String generateCsrfToken(ControllerContext ctx) {
        //long seed = System.currentTimeMillis(); 
        //Random r = new Random();
        //r.setSeed(seed);
        //return Long.toString(seed) + Long.toString(Math.abs(r.nextLong()));
        return java.util.UUID.randomUUID.toString();
    }
    
    protected boolean isValidCsrfToken(ControllerContext ctx) {
        String csrfParamToken = ctx.getParameter(ControllerContext.CSRF_TOKEN);
        String csrfSessionToken = ctx.getSessionAttribute(ControllerContext.CSRF_TOKEN, "");
        if(!Utils.isEmpty(csrfParamToken) && !Utils.isEmpty(csrfSessionToken) && csrfParamToken.equals(csrfSessionToken)) {
            return true;
        } else {
            //Log this as this can be a security threat
            Log.warn("Invalid security Token. Supplied token: " + csrfParamToken + ". Session token: " + csrfSessionToken + ". IP: " + ctx.request.getRemoteAddr());
            return false;
        }
    }
...

From individual Controllers methods (I am excluding my wrapper around applicationContext#getMessage()) we have:
...
@RequestMapping("/list")
public ModelAndView list(HttpServletRequest request,
           HttpServletResponse response,
           //This is a hack just to provide an uniform way to present global errors in the front end. @ModelAttribute should never be used in this method
           @ModelAttribute("employee") Employee employee,
           BindingResult result) throws ServletException, IOException {

       ControllerContext ctx = new ControllerContext(request, response);
       init(ctx);
       if (!isValidCsrfToken(ctx)) {
           result.addError(new ObjectError("employee", getMessage("error.invalidCsrfToken")));
           return getModelAndView(ctx, "employee/list");
       }
...

Some JSP samples:
...
<div id="global_error"><form:errors path="employee"
             cssClass="errors" /></div>
...
<input type="hidden" name="ctoken" id="ctoken" value="${ctoken}"/>
...
<li><a href="<spring:url value="/employee/list?ctoken=${ctoken}"/>" class="navLink"><span>Employee</span></a></li>

In file message.properties (for internationalization):
error.invalidCsrfToken=Invalid Security Token!

Note that this solution creates a token for the whole session duration. I do not like breaking the back button functionality so I take especial care when submitting so the user lands in a different than submission-page. In addition the ctoken is gotten in JSP from an attribute instead of a param. It is assumed that the param was sanitized before.

Again it would be ideal to have this protection provided out of the box by Spring. A combination of the form tag with annotations in a per Controller method basis (with the help of AOP) is certainly possible. While I wait for that I keep on going the old school way instead of adding just another framework to protect my apps from CSRF attacks.
Finally it is better to use a header instead of a param in the case you must use GET protection. Of course this demands an AJAX driven app (AKA SPA).

7 comments:

Bar said...

Interesting! Could you share the code for ControllerContext too? Thanks!

Nestor Urquiza said...

@Bar ControllerContext is just a POJO that passes the servlet request and other domain specific objects between Controller methods.

This is a Design decision to use Singleton Controllers totally managed by Spring in a multithreading environment.

Hope that answer your question.

Przemyslaw said...

Grails supports this out of the box: http://grails.org/doc/2.0.x/ref/Controllers/withForm.html

Blaufish said...

Random r = new Random(); r.setSeed(seed);

is not random (unpredictable), enabling seed guessing attacks.

new SecureRandom() will produce a better Random object.

gregory said...

This won't work. Hacker can get the token with a GET request.

Nestor Urquiza said...

@Blaufish, good point.Thanks!

@Gregory, can you please elaborate? As far as I understand someone can get a token only if you provide one. In my case I generate a token once the user is authenticated.

Eyal Lupu said...

Thank you for the post.
I want to mention that latest Spring release (3.1) provides a new interface (RequestDataValueProcessor ) which can be combined with an HandlerInterceptor to automatically included CSRF token in forms and automatically validate it.

Once configured this process is fully automatic without the need of developers to do anything.I elaborated on that here in my blog.

Followers