CSRF protection in CubicWeb

added in 3.31

Introduction

To quote OWASP, a CSRF is:

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to
execute unwanted actions on a web application in which they’re currently
authenticated. With a little help of social engineering (such as sending a
link via email or chat), an attacker may trick the users of a web
application into executing actions of the attacker’s choosing. If the
victim is a normal user, a successful CSRF attack can force the user to
perform state changing requests like transferring funds, changing their
email address, and so forth. If the victim is an administrative account,
CSRF can compromise the entire web application.

This is especially dangerous for every website where the users are able to do modification requests (for exemple by submitting a form).

The way the protection is done is:

  • the user asks a page with a form
  • the website generates a unique token for this user
  • the token is then inserted in the form as a hidden field
  • the user gets the page with the form
  • when the user clics on the “submit” button of the form, the token is sent to the server
  • there, the server checks that the token is the same as the one generated
  • this ensures that this is the user that has made the request and has not been tricked in doing it without its knowledge

In an AJAX/frontend context, the behavior is a bit different and is handled by how the frontend has built its strategy.

Implementation in CubicWeb

CubicWeb offers a CSRF protection by default by integrating Pyramid’s CSRF middleware.

The CSRF Policy used by CubicWeb is CookieCSRFStoragePolicy which inserts the CSRF token in:

  • every CubicWeb forms by default (except if you have been doing weird things in the way forms are rendered),
  • in the cookie at the “csrf_token” key, which can be useful if you are doing ajax requests without first getting a form.

Adding the CSRF token in a CubicWeb Form

By default you don’t have to do anything as it is added in all forms, but if for any reason you need to do it, it’s done like this in CubicWeb:

from pyramid.csrf import get_csrf_token


# in the form generation part which here is Form.__init__
# here req is a CubicWeb request in the context of CubicWeb using pyramid
# here req._request is the pyramid request
token = get_csrf_token(req._request)
self.add_hidden("csrf_token", token)

Using the CSRF token in a pyramid context

Your main source of information is the Pyramid documentation. on csrf. It explains how to get the token, verify the token and integrate the token in a jinja2 template and also disabling the CSRF protection if needed.

Using the CSRF token in a pyramid context depends on how views are built. To get the token, one can use get_csrf_token(request) method from pyramid.csrf.

Submitting the CSRF token using AJAX

Once you have grab the CSRF token like describe in the previous section, you need to submit it as HTTP Header X-CSRF-Token.

To quote Pyramid documentation, if you are using jQuery here is how you can do it:

var csrfToken = "${get_csrf_token()}";

$.ajax({
  type: "POST",
  url: "/myview",
  headers: { 'X-CSRF-Token': csrfToken }
}).done(function() {
  alert("Deleted");
});

Submitting the CSRF token for a request with the content/type “application/json”

Exactly like an AJAX request, once you have the CSRF token as explained in the previous section, you need to submit it as HTTP Header X-CSRF-Token

Manually checking the CSRF token

If for wathever reason you need to manually check the CSRF token, here is how it’s done in CubicWeb (in cubicweb.pyramid.bwcompat.CubicWebPyramidHandler.__call__):

from pyramid.csrf import check_csrf_token, check_csrf_origin

safe_methods = frozenset(["GET", "HEAD", "OPTIONS", "TRACE"])
if request.method not in safe_methods and getattr(
    controller, "require_csrf", True
):
    check_csrf_token(request)
    check_csrf_origin(request)

Here check_csrf_origin is a complementary CSRF protection.

Disable CSRF for a CubicWeb View

By default, all CubicWeb views need a validation by CSRF token for every HTTP request which are not GET, HEAD, OPTIONS or TRACE. To disable this comportment in one view, you have to define a new Controller with a require_csrf attribute at False. Every route using this controller will be able to be called without CSRF tokens. Of course, you can be more precise by adding more rules in your controller, for instance adding condition on specific view registry id, or connected user.

Here is an example on how to register a controller that disable csrf for all views:

from cubicweb.web.views.basecontrollers import ViewController


class ControllerWithCSRFCheckDisabled(ViewController):
    require_csrf = False

def registration_callback(vreg):
    self.vreg.register_and_replace(ControllerWithCSRFCheckDisabled, ViewController)

For a more specific behavior you’ll need to overwrite the publish method and decide which view needs a csrf protection.