Защита от CSRF

17 мая 2018 г.

CSRF расшифровывается как cross-site request forgery, межсайтовая подделка запроса. Это один из типов эксплойтов, при котором некие несанкционированные пользователем действия выполняются от его имени.  Например, злоумышленник знает, какие поля отправляются в форме, позволяющей вам редактировать в какой-нибудь админке некие секретные данные. Тогда он может подсунуть вам где-то у себя на сайте скрытую форму и замаскированную сабмит-кнопку, нажав на которую вы отправите запрос на редактирование этих данных, и, если вы аутентифицированны в данный момент в админке, то такой запрос пройдет, и данные будут изменены на подсунутые злоумышленником.

Laravel автоматически генерирует уникальный CSRF-токен для каждой сессии каждого пользователя. Именно этот токен подтверждает вашу личность и должен быть включен в нашу форму. В форме злоумышленника он содержаться не может, потому что злоумышленник не может его знать. Таким образом, мы получаем гарантию, что форму отправляет именно тот пользователь, который открыл текущую сессию.

Для того, чтобы включить в форму скрытое поле с CRSF-токеном, при использовании шаблонизатора Blade (который включен в Laravel из коробки; мы познакомимся с ним в соответствующей главе) достаточно прописать директиву @csrf.

<form method="POST" action="/profile">
    @csrf
    ...
</form>

На сервере же проверкой токена занимается посредник VerifyCsrfToken.

Для некоторых URL может понадобиться выключить CSRF-защиту. Например, платежные системы ничего не знают о нашем CSRF-токене, и, соответственно, не могут его передать в своих коллбэках. Поэтому URL, обрабатывающие такие коллбэки, мы можем поместить в свойство except класса App\Http\Middleware\VerifyCsrfToken.

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ];
}

Еще вариант - вынести такие роуты из группы посредников web. Возможно, этот вариант даже правильнее, потому что в данном случае мы не имеем дело с публичными web-страницами.

Кроме того, фреймворк предоставляет возможность включить мета-тег csrf-token в заголовок каждого аякс-запроса нашего Javascript-приложения. Отметим, что такое приложение включено в Laravel из коробки и основано на библиотеке Vue.js. Для аякс запросов же используется библиотека Axios (которая по моему, и не только, мнению, удобнее и функциональнее, чем jQuery.ajax). Если мы заглянем в файл resources/assets/js/bootstrap.js, то увидим, что эта библиотека сконфигурирована для автоматического включения мета-тега csrf-token в заголовок X-CSRF-TOKEN запроса для библиотеки Axios. Сам же мета-тег включается следующим образом (опять-таки, только при использовании шаблонизатора Blade).

<meta name="csrf-token" content="{{ csrf_token() }}">

При желании использовать для аякс-запросов другую библиотеку, ее также нужно сконфигурировать для включения CSRF-заголовка. Например, для jQuery (которая также присутствует из коробки, в связи с использованием плагинов Bootstrap).

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

Некоторые JS-библиотеки (та же Axios, или, например, Angular) включают в запросы заголовок X-XSRF-TOKEN, который они берут из куки XSRF-TOKEN. Laravel автоматически записывает CSRF-токен в этот куки.

Напоследок заметим, что при тестировании приложения посредник VerifyCsrfToken автоматически выключается.