CSRF-атака: пример на PHP и способы защиты

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

В чем суть механизма CSRF

После того как пользователь авторизуется на определенном сайте, каждый его последующий запрос автоматически сопровождается данными, подтверждающими его личность. Эти данные, которые называются куки (cookies), сохраняются в браузере и содержат уникальные идентификаторы сессии. Браузер отправляет их вместе с каждым запросом, чтобы сервер мог идентифицировать пользователя без необходимости повторной авторизации.

Однако такой механизм авторизации может быть уязвим для CSRF-атак. Если сервер не применяет дополнительные меры защиты, любой запрос, отправленный через браузер, будет считаться легитимным, даже когда пользователь не инициировал его намеренно.

CSRF (Cross-Site Request Forgery) — это тип атаки, при котором злоумышленник обманом заставляет браузер пользователя выполнить нежелательные действия на доверенном сайте, где пользователь уже авторизован. Основная идея атаки заключается в том, что браузер отправляет запросы с теми же сессиями и куками, которые используются для авторизованных действий на целевом сайте, без ведома пользователя.

Чтобы глубже разобрать механизм CSRF, далее приведем ее пример.

Дисклеймер:

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

Пример CSRF-атаки

Создадим имитацию CSRF-атаки. Текущий пример подразумевает наличие трех сайтов (трех различных доменов):

  • уязвимый к атаке, на котором авторизован пользователь;
  • сайт хакера, который совершает CSRF-атаку на уязвимый сайт;
  • ресурс, на котором размещена вредоносная ссылка.

Но для удобства создания имитации CSRF-атаки мы разместим весь код на одном домене.

Суть примера

Суть примера состоит в том, что пользователь авторизован на одном сайте, который не защищен от подобного рода атак. При этом пользователь просматривает информацию на других сайтах и натыкается на сайт, где видит текст с интересующей его информацией. Этот текст содержит предложение перейти по ссылке для того, чтобы узнать подробности. Пользователь, ничего не подозревая, нажимает на эту ссылку, которая оказывается вредоносной и ведет на сайт злоумышленника. Код страницы на сайте мошенника, используя данные авторизации сайта пользователя, делает запрос на страницу смены пароля уязвимого сайта. Так как запрос уже считается авторизованным, то он обрабатывается и происходит смена пароля.

Таким образом, для нашего примера сделаем следующие сопоставления:

  • уязвимый сайт с формой смены пароля у нас будет представлен файлом vulnerable.php;
  • веб-ресурс хакера с вредоносным кодом — attacker.php;
  • сайт, на котором расположена вредоносная ссылка — news.php.

Разберем подробнее.

Уязвимый к CSRF-атаке сайт

Начнем с уязвимого сайта. Предположим, этот ресурс содержит страницу с формой смены пароля, которая не защищена от таких атак. Напишем простой код страницы, достаточный для проведения примера:

session_start();

// Если приходит авторизованный POST-запрос на эту страницу с параметром new_password, запрос обрабатывается — имитируем обработку выводом соответствующего сообщения
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $newPassword = $_POST['new_password'] ?? '';

    if (!empty($newPassword)) {
        echo "Ваш пароль был успешно изменён!";
    }
}
?>

// Форма смены пароля
<form method="POST">
    Новый пароль:
    <input type="text" name="new_password">
    <input type="submit" value="Сменить пароль">
</form>

При отправке формы происходит стандартный POST-запрос, поэтому хакеру хорошо известно, какой запрос нужно отправить, чтобы сменить пароль пользователя. Злоумышленнику просто необходимо авторизовать этот запрос и послать от имени жертвы.

Веб-ресурс с вредоносной ссылкой

Предположим, пользователь, будучи авторизованным на сайте vulnerable.php, заходит на какой-то новостной сайт news.php, где видит комментарий к какой-нибудь статье от неизвестного пользователя. Этот комментарий содержи призыв перейти по ссылке. Комментарий может быть написан, например, подобным образом:

<p>Я нашел полезную статью по этому вопросу, где подробно описаны все аспекты. Если кому интересно, можно почитать вот <a href="http://localhost/attacker.php">тут</a>.</p>

Так как пользователь заинтересован в информации статьи, он нажимает на эту ссылку и попадает на ресурс хакера.

Производящий CSRF-атаку сайт

Код страницы сайта злоумышленника содержит такую же форму, как и на уязвимом сайте. При этом форма отправляется автоматически с помощью JavaScript сразу после захода на эту страницу. В силу того что пользователь перешел на страницу со своего браузера, формируется авторизованный запрос на смену пароля и отправляется на уязвимый сайт. Такой же запрос, как если бы жертва отправляла форму смены пароля на уязвимом сайте.

Пример кода вредоносного сайта:

<!-- Форма для автоматической отправки сразу после открытия страницы -->
<form id="csrf-form" method="POST" action="http://localhost/vulnerable.php">
    <input type="hidden" name="new_password" value="hacked_password">
</form>

<script>
    // JavaScript-код для автоматической отправки формы
    document.getElementById('csrf-form').submit();
</script>

Так как на уязвимом сайте дополнительных проверок безопасности не производится, происходит смена пароля и пользователь теряет контроль над своим аккаунтом. Атака завершена успешно.

Методы защиты от CSRF-атак

CSRF-токены

Одним из самых эффективных методов защиты от CSRF-атак является использование CSRF-токенов (Cross-Site Request Forgery tokens). Этот метод позволяет предотвратить подделку запросов, требуя, чтобы каждый критически важный запрос к серверу был подтверждён уникальным токеном, который злоумышленник не может предсказать или подделать.

Как работают CSRF-токены? Когда пользователь открывает страницу с формой, сервер генерирует уникальный токен (случайную строку), который устанавливается в скрытое поле формы. При отправке формы токен передаётся вместе с данными формы на сервер. Сервер, получив запрос, проверяет переданный токен и сравнивает его с тем, который был сгенерирован для текущего пользователя. Если токен отсутствует или не совпадает, сервер отклоняет запрос как подозрительный.

Давайте попробуем защитить форму с помощью CSRF-токена из примера предыдущего раздела. Сгенерируем уникальный токен и добавим его в сессию и новое поле для формы. При каждом запросе будем проверять идентичность пришедшего в запросе токена и сохраненного в сессии. Изменим код vulnerable.php на следующий:

session_start();

// Если в текущей сессии нет CSRF-токена, сгенерируем его и сохраним в сессии
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Начнем обработку POST-запроса (отправки формы)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Возьмем из запроса значения нового пароля и CSRF-токена
    $newPassword = $_POST['new_password'] ?? '';
    $csrfToken = $_POST['csrf_token'] ?? '';

    // Сравним значения токена, пришедшего в запросе, и токена, хранящегося в сессии
    // Если совпадают, запрос пришел с нужного браузера - допускаем этот запрос
    if (hash_equals($_SESSION['csrf_token'], $csrfToken)) {
        if (!empty($newPassword)) {
            // Меняем пароль
            echo "Ваш пароль был успешно изменён!";
        }
    } else { // Если токены не совпадают - отклоняем запрос
        echo "Неверный CSRF токен.";
    }
}
?>

<form method="POST">
    <!-- Для формы добавим новое скрытое поле, где установим сгенерированный CSRF-токен -->
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
    Новый пароль: <input type="text" name="new_password">
    <input type="submit" value="Сменить пароль">
</form>

Так как злоумышленник не знает, какой уникальный токен установлен у пользователя, ему не удастся совершить взлом с помощью метода, описанного в предыдущем разделе.

Проверка заголовков Origin/Referer

Одним из дополнительных методов защиты от CSRF-атак является проверка заголовков Origin и Referer. Эти заголовки указывают, откуда был отправлен запрос, и могут помочь серверу определить, был ли запрос сделан с доверенного источника.

Заголовок Origin содержит домен, с которого был отправлен запрос. А заголовок Referer также указывает источник запроса, но включает полный URL страницы, откуда был отправлен запрос. Оба заголовка могут быть использованы для того, чтобы сервер проверил, действительно ли запрос пришёл с того же домена, что и сам сервер. Если источник запроса отличается от ожидаемого, то запрос может быть отклонён как подозрительный.

Флаг Same-Site Cookies

Флаг Same-Site Cookies используется для ограничения передачи кук при межсайтовых запросах. Когда этот флаг установлен, куки передаются только при запросах, сделанных с того же домена, на котором они были созданы. Существует несколько режимов: наиболее строгий — Strict, блокирующий передачу кук при любых межсайтовых запросах, и более гибкий — Lax, при котором куки передаются только при безопасных действиях, таких как переход по ссылке. Этот метод обеспечивает дополнительную защиту, уменьшая риск использования кук в CSRF-атаках.

CAPTCHA

CAPTCHA — это тест, используемый для определения, является ли пользователь человеком или автоматизированной программой. Этот тест заставляет пользователя подтвердить, что он человек (например, решить задачу или выбрать правильные изображения), и это может предотвратить осуществление CSRF-атак, так как злоумышленник не сможет автоматически пройти проверку.

Multi-Factor Authentication (MFA)

Multi-Factor Authentication (MFA) — это метод защиты, который требует от пользователя пройти несколько этапов проверки для подтверждения своей личности. В отличие от обычной аутентификации с использованием только пароля, MFA добавляет второй (или более) фактор, например, одноразовый код из SMS, мобильного приложения или специального устройства. MFA защищает от CSRF-атак, добавляя барьер на случай, если злоумышленник попытается выполнить важные действия от имени пользователя.

Заключение

Подводя итог, отметим, что CSRF-атакам важно уделять внимание, поскольку они могут привести к серьёзным последствиям для безопасности веб-приложений. К счастью, многие современные фреймворки, такие как Laravel и Symfony, уже включают встроенные механизмы защиты от этих атак, что значительно упрощает их реализацию. Однако это не отменяет необходимости понимать, как работают эти методы. Особенно важно внедрять защиту в тех случаях, когда вы не используете фреймворк или разрабатываете сайт с нуля. В таких ситуациях вам необходимо самостоятельно применять проверенные методы, чтобы обезопасить свой сайт от возможных атак.

Оцените статью
DevReflex
Добавить комментарий