Статьи

Предотвращение подделки межсайтовых запросов (CSRF)

Подделка межсайтовых запросов (CSRF) — это распространенный и серьезный эксплойт, при котором пользователь вынужден выполнить действие, которое он явно не собирался делать. Это может произойти, например, когда пользователь заходит на один из своих любимых веб-сайтов и переходит по ссылке, которая кажется безобидной. В фоновом режиме информация его профиля молча обновляется адресом электронной почты злоумышленника. Затем злоумышленник может использовать функцию сброса пароля на веб-сайте, чтобы отправить по электронной почте новый пароль, и она только что успешно украла учетную запись. Любое действие, которое пользователь может выполнять при входе на веб-сайт, злоумышленник может выполнять от его / ее имени, будь то обновление профиля, добавление товаров в корзину, размещение сообщений на форуме или практически что-либо еще.

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

Как это устроено

Чтобы понять, как работает атака CSRF, лучше всего увидеть ее в действии. Чтобы проиллюстрировать атаку, я хотел бы создать простой пример, который может выйти из активного сеанса. Нам понадобится страница входа в систему ( login.php ), сценарий обработки для входа в систему и выхода из сеанса ( process.php ), и, наконец, пример атаки ( harmless.html ).

Во-первых, вот код для login.php :

 <?php session_start(); ?> <html> <body> <?php if (isset($_SESSION["user"])) { echo "<p>Welcome back, " . $_SESSION["user"] . "!<br>"; echo '<a href="process.php?action=logout">Logout</a></p>'; } else { ?> <form action="process.php?action=login" method="post"> <p>The username is: admin</p> <input type="text" name="user" size="20"> <p>The password is: test</p> <input type="password" name="pass" size="20"> <input type="submit" value="Login"> </form> <?php } ?> </body> </html> 

Сценарий login.php начинается с инициализации данных сеанса. Затем он проверяет, было ли установлено значение $_SESSION["user"] , и, если это так, отображает приветственное сообщение вместе со ссылкой на выход из системы. В противном случае он отображает форму входа.

Это сценарий обработки process.php :

 <?php session_start(); switch($_GET["action"]) { case "login": if ($_SERVER["REQUEST_METHOD"] == "POST") { $user = (isset($_POST["user"]) && ctype_alnum($_POST["user"]) ? $_POST["user"] : null; $pass = (isset($_POST["pass"])) ? $_POST["pass"] : null; $salt = '$2a$07$my.s3cr3t.SalTY.str1nG$'; if (isset($user, $pass) && (crypt($user . $pass, $salt) == crypt("admintest", $salt))) { $_SESSION["user"] = $_POST["user"]; } } break; case "logout": $_SESSION = array(); session_destroy(); break; } header("Location: login.php"); ?> 

Сценарий process.php также начинается с инициализации данных сеанса, а затем проверяет, есть ли действие для работы с ним. Мы выполняем некоторую базовую проверку входных данных, используя троичный оператор PHP вместе с ctype_alnum() и crypt() , а затем соответствующим образом устанавливаем или уничтожаем переменную сеанса. Пользователь перенаправляется обратно в login.php в конце скрипта.

Теперь давайте сосредоточимся на файле, который злоумышленник может создать, чтобы использовать код в наших предыдущих примерах. Это код эксплойта , harmless.html :

 <html> <body> <p>This page is harmless... Or is it?</p> <!-- Address to target website --> <img src="process.php?action=logout" style="display: none;"> </body> </html> 

Если вы login.php сайт login.php и login.php в свою учетную запись, а затем login.php систему и login.php на страницу злоумышленника, вы автоматически выйдете из системы, даже если вы не нажали ссылку выхода из системы. Браузер отправляет запрос на сервер для доступа к сценарию process.php , ожидая, что это будет файл изображения. Сценарий обработки не может отличить действительный запрос, инициированный пользователем, щелкающим ссылку выхода из системы, и умно созданный запрос, который браузер обманным путем отправил.

harmless.html может быть размещен на совершенно другом сервере, чем тот, на котором вы вошли, и он все равно будет работать, поскольку страница злоумышленника делает запрос от вашего имени, используя сеанс, который вы открыли в фоновом режиме. Даже не имеет значения, если веб-сайт, в который вы вошли, находится в частной сети, запрос будет отправлен с вашего IP-адреса, как если бы вы сделали запрос самостоятельно, что делает практически невозможным отслеживание источника атаки.

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

Несмотря на то, что выход пользователя с веб-сайта не так уж впечатляет, на harmless.html так же легко можно было бы найти скрытый встроенный фрейм (в отличие от тега изображения) с формой, которая автоматически отправляется при загрузке страницы, что из атак, упомянутых в начале этого руководства честной игры.

Надеюсь, теперь вы понимаете, насколько серьезными могут быть CSRF-атаки, поэтому давайте посмотрим, как вы можете их предотвратить.

Защита ваших пользователей

Чтобы гарантировать, что действие фактически выполняется пользователем, а не третьей стороной, вам необходимо связать его с каким-то уникальным идентификатором, который затем можно будет проверить. Чтобы предотвратить атаку, мы можем изменить login.php следующим образом:

 <?php // make a random id $_SESSION["token"] = md5(uniqid(mt_rand(), true)); echo '<a href="process.php?action=logout&csrf=' . $_SESSION["token"] . '">Logout</a></p>'; 

Затем, чтобы проверить идентификатор, мы можем изменить process.php следующим образом:

 case "logout": if (isset($_GET["csrf"]) && $_GET["csrf"] == $_SESSION["token"]) { $_SESSION = array(); session_destroy(); } break; 

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

 <input type="hidden" name="csrf" value="<?php echo $_SESSION["token"]; ?>"> 

По моему собственному мнению, несмотря на преследования моих уважаемых друзей и коллег из лучших побуждений, я предпочитаю использовать PHP session_id() а не генерировать случайный токен, так как я не особенно люблю подход «безопасность через неизвестность». В дополнение к использованию session_id() , я также использую session_regenerate_id() при каждом входе в систему или обновлении конфиденциальной информации, чтобы снизить риск любых атак фиксации сеанса, и я никогда не добавляю идентификатор к URL-адресу, который будет сохранен в истории браузеров. , Произвольное выставление идентификатора сеанса более, чем необходимо, никогда не является хорошей идеей, но пока вы осторожны, я думаю, что это более элегантный подход. Конечно, если ваш веб-сайт использует какой-либо тип аутентификации, который не использует сеансы, вам все равно нужно будет создать свой собственный идентификатор.

Вывод

К настоящему времени вы должны понимать основные принципы, лежащие в основе атаки CSRF, и какие шаги вы можете предпринять, чтобы защитить свой сайт и своих пользователей. Как сказал Бен Франклин, «унция профилактики стоит фунта лечения». Я уверен, что все мы предпочли бы потратить время на то, чтобы убедиться, что код, который мы пишем, безопасны, чем справляться со стрессом, головными болями и возможными судебными исками, окружающими компромисс.

Изображение через Blazej Lyjak / Shutterstock