Что такое CSRF?
CSRF (Cross-Site Request Forgery) – это вид уязвимости сайта. Когда может произойти подмена данных.
Простым языком, это подмена данных (как правило форм), отравляемых авторизованным пользователем через вредоносный ресурс (сайт, приложение).
Пример CSRF атаки
У нас есть HTML-форма, которую пользователь должен заполнить: ввести адрес и нажать кнопку «Сохранить». В результате в бэкенд полетит POST-запрос с HTML-формой. Мы видим, что браузер автоматически поставил туда сессионные куки пользователя. Бэкенд, когда получит такой запрос, посмотрит, что есть такая сессия, это легитимный пользователь, и изменит ему адрес доставки.
Как это может произойти?
Пользователь получил email, перешел по ссылке на вредоносный сайт. Вредоносный сайт отправляет запрос с сессионными данными пользователя для изменения/подмены каких-то данных на атакуемом сайте.
Защита
Наиболее популярный метод защиты от такой атаки – использование уникального токена (генерируемого нашим сайтом и меняющегося с сессией пользователя или со временем) в составе полей формы на изменение данных. Это будет гарантировать, что форма отправлена с нашего сайта, а не вредоносного.
Для этого нужно изменить данные формы, добавить поле с csrf-ключем и сделать проверку csrf-ключа на стороне обработчика формы.
В Laravel
В laravel это будет выглядеть так:
<form action="/somewhere" method="POST">
@csrf
<!-- эквивалент для html, который генерирует @csrf -->
<input name="_token" type="hidden" value="{{ csrf_token() }}" />
</form>
Код генерирует csrf-токен для каждой активной сессии на веб-сайте.
Проверяется токен через специальный middleware VerifyCsrfToken
, входящий в состав прослоек для группы web.
В WordPress
В WordPress используется функция wp_nonce_field();
которая также генерируется html поле в составе формы. Проверка в обработчике осуществляется функцией wp_verify_nonce()
, куда передаются данные запроса. Дополнить защиту, можно функцией wp_referer_field()
, если запрос должен приходить только с определенной, заранее известной страницы.
Любой php код
В PHP вы можете использовать функцию uniqid()
для генерации токена CSRF:
$csrf_token = uniqid();
Затем вы можете отправить токен CSRF клиенту в виде скрытого поля формы:
<form> <input type="hidden" name="_csrf_token" value="<?php echo $csrf_token; ?>"> <!-- другие поля формы --> </form>
Когда форма отправляется на сервер, вы можете проверить токен CSRF, сравнив его с тем, который был отправлен клиенту:
if ($_POST['_csrf_token'] !== $csrf_token) { // CSRF-атака, отклоняем запрос exit; }
2. Использование заголовка запроса
Вместо использования скрытого поля формы, вы можете отправлять токен CSRF в заголовке запроса. В PHP вы можете использовать функцию header()
для отправки заголовка:
header('X-CSRF-Token: ' . $csrf_token);
Затем вы можете проверить заголовок запроса на сервере:
if ($_SERVER['HTTP_X_CSRF_TOKEN'] !== $csrf_token) { // CSRF-атака, отклоняем запрос exit; }
3. Использование сессии
Вы можете хранить токен CSRF в сессии и проверять его при каждом запросе. В PHP вы можете использовать функцию session_start()
для начала сессии и функцию $_SESSION
для хранения токена CSRF:
session_start(); $csrf_token = uniqid(); $_SESSION['csrf_token'] = $csrf_token;
Затем вы можете проверить токен CSRF при каждом запросе:
if ($_POST['_csrf_token'] !== $_SESSION['csrf_token']) { // CSRF-атака, отклоняем запрос exit; }
После успешной проверки формы, необходимо обновить токен.
4. Использование библиотеки OWASP CSRFGuard
OWASP CSRFGuard – это библиотека, которая помогает защитить ваш сайт от CSRF. Она генерирует токен CSRF и проверяет его при каждом запросе.
В PHP вы можете использовать библиотеку OWASP CSRFGuard, включив ее в ваш проект и настроив ее в соответствии с документацией.
Вот несколько примеров кода, которые помогут вам защитить ваш сайт от CSRF:
// Генерация токена CSRF $csrf_token = OWASP_CSRFGuard::generateToken(); // Отправка токена CSRF клиенту echo '<input type="hidden" name="_csrf_token" value="' . $csrf_token . '">'; // Проверка токена CSRF на сервере if (!OWASP_CSRFGuard::validateToken($_POST['_csrf_token'])) { // CSRF-атака, отклоняем запрос exit; }