Symfony2 Cross Site Request Forgery protection for controller actions

CSRF protection

The CSRF, after XSS and SQL Injections, is one of the most common vulnerability. At the same time it can cause very dangerous effects such as performing actions without the user’s knowledge.

CSRF is a method of attack on a website, based on impersonating a specific user and performing specific actions on his behalf.

The easiest and also the most frequently encountered type of CSRF attack is based on the usage of the HTML tag <img /> used to display graphic. Instead of the tag containing the URL of an image, the striker uses an URL of the website action which carries side-effects. This allows to perform various operations within the user’s session.

Example

Via the forum or blog type applications which allows users to embed images in their statements using a <img /> tag

  1. An attacker adds comment with embeded image with src attribute set to:

    http://blog.com/admin/posts/delete?id=1

  2. When an user loads comments’ page, browser’ll try to load an image and perform GET request to “delete” action. If the administrator is currently logged in and the removal action is not CSRF aware, the post with ID 1 will be deleted.

There are many methods that make it difficult to carry out a successful CRSF attack:

  • Time passwords
  • Brief period of validity of log-in and allowable idle time
  • Confirmation requests
  • Hidden form input containing a random value or the contents of cookies used to authenticate

Symfony2 Protection

Like those adopted in Symfony2 we’re able to secure a controller action in two ways:

  • Separate page request confirmation of critical actions with an empty form that has a CSRF token
  • Checking the validity of a custom CSRF token passed as a GET parameter in a controller action

As an example, we’ll use simple action of disabling user without using dedicated form.

In the view we can set up a block generating CSRF token:

{% set csrfDisable = csrf_token('disable') %}

And sending it as a request parameter to the controller action:

<a href="{{ path('user_disable', { 'id': user.id, '_token': csrfDisable }) }}">Disable</a>

in which at the beginning, we check the validity of the token by calling our own method checkCsrf:

/**
 * @Template
 * @Route("/disable/{id}", name="user_disable")
 */
public function disableAction($user)
{
    $this->checkCsrf('disable'); // name of the generated token, must be equal to the one from Twig

    $em = $this->getEntityManager();

    $user->disable();

    $em->persist($user);
    $em->flush();

    return $this->redirect($this->generateUrl('user_list'));
}

While checkCsrf might look like:

protected function checkCsrf($name, $query = '_token')
{
    $request = $this->getRequest();
    $csrfProvider = $this->get('form.csrf_provider');

    if (!$csrfProvider->isCsrfTokenValid($name, $request->query->get($query))) {
        throw new \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('CSRF token is invalid.');
    }

    return true;
}

Keep in mind that the pages of the GET method should be safe for the visitor – safe interactions, should not induce binding to the effects.

However, for a less critical tasks, the additional form based confirmation may be unnecessary. In that case the simple CSRF token passing through GET action will be sufficient.

Photo by Perspecsys Photos

Looking to scale-out your
web application?

Hire Octivi!

Web developer