Menu

Official website

Play Framework 1.2 Security- Fighting Cross Site Request Forgery


22 Feb 2011

min read

Play Framework 1.2 has built-in protection against cross-site request forgery (CSRF). Unfortunately, it is not enabled by default and the developer of a Play application has to take steps to be fully protected. This article explains how to utilize Play’s protection in a proper way and the pitfalls to avoid.

Theoretical solution to CSRF

To prevent CSRF, an application must verify that a request that modifies the application state is the result of a user interacting with the application, and not the result of a malicious webpage that tricks the user’s browser into sending requests to the application.

A common solution is to put a secret token as a hidden form field in every form, that is only valid for the current session. When the form is submitted, the application verifies the token. An attacker website can not obtain a valid token for the user’s session.

Play’s implementation

If you create a form using Play’s \{form} tag, Play will automatically add such a token in a hidden form field with the name authenticityToken:

{form @User.create}
<input type="text" name="name" />
{/form}

This template gives the following html output:

<form action="/users/create" method="POST" accept-charset="utf-8"
 enctype="application/x-www-form-urlencoded" >
  <input type="hidden" name="authenticityToken" value="b0b42c41099a959fdd199181c81931a5ef5548d1">
  <input type="text" name="user.name" />
  <input type="submit" />
 </form>

Now when the form is submitted, the authenticityToken value is sent to the application, where it can be verified. Unfortunately, Play does not verify the token by default. You have to explicitly check for it in your controller, like this:

public class Users extends Controller {

        ⋮

    public static void create(User user) {
        checkAuthenticity();
        user.save();
        flash.success("Created a user!");
        list();
    }
}

The main problem with this implementation is that it is dead easy to forget a checkAuthenticity() somewhere, leaving your application vulnerable to CSRF. If your forget the check, everything works fine during development, you are just not protected.

A better approach

A well designed application should not change state on GET requests. Web applications typically only use GET and POST requests, so all state modifications should be done with POST requests. If we automatically check the authenticity token on all POST requests, our application is safe against CSRF. The automatic check can be implemented using an interceptor on either a superclass of your controllers, or on a controller class whose interceptors are added to your controllers using an @With annotation. This might seem to reintroduce the problem we are trying to solve: if we forget to inherit from this superclass or forget the @With annotation, we are still not protected. That is true. The probability of that can however be minimized by adding the interceptor to a base class that also does stuff that you would notice. In one of our recent projects, the parent class of all the controllers adds some variables to the renderArgs that are shown in the template; something that would surely be spotted if we forget to inherit from it in a controller.

An example of the base controller:

public class Base extends Controller {

    @Before
    public static void csrfProtection() {
    if(request.method == "POST") {
            checkAuthenticity();
    }
    }
}

Now all our POST requests to subclasses of this base controller are automatically protected, without us having to remember to add a checkAuthenticity() call to every method.

If we try a post without the authenticityToken value present, Play responds with:

Bad authenticity token

Ajax

For Ajax requests, it is not required to check for the authenticity token, as cross site Ajax requests are prevented by the same origin check of the browser. Many Javascript libraries, including JQuery, set the X-Requested-With header to XMLHttpRequest, and Play has a built-in check for this: request.isAjax(). To exclude Ajax requests from our token checking interceptor, modify it as follows:

@Before
    public static void csrfProtection() {
    if(request.method == "POST" && !request.isAjax()) {
            checkAuthenticity();
    }
    }

Now we don’t have to add the authenticity token to all the Ajax requests we use in our application.

Pitfall

We are using the following controller and action to create users:

public class Users extends Base {

        ⋮

    public static void create(User user) {
        user.save();
        flash.success("Created a user!");
        list();
    }
}

The Play routes file has the following line in it by default:

# Catch-all
*       /{controller}/{action}                  {controller}.{action}

Now, while our action is protected against malicious POST requests, a user can still be created without an authenticity token present with a GET request!

The solution is to remove the catch-all route and explicitly add all your application’s routes, with an explicit HTTP method (GET or POST). This forces you to think about whether your action changes state or not and thereby whether it should use a POST or GET request. You can not forget to think about it, as your action just won’t work without the explicit route, which is a huge advantage over the manual checkAuthenticity() call, which you can easily forget without it getting noticed.

Conclusion

The play framework has a decent CSRF protection built in, but it is not enabled by default. For the best security, write an interceptor that automatically checks the authenticity of every POST request. The default catch-all route should be removed from routes.conf to prevent the accessing of state-modifying actions with GET requests.

expand_less