Совсем недавно у меня появилась задача защитить web-приложение полностью построенное на ajax от CSRF-атак.
Каков же механизм такой атаки? Суть заключается в выполнении запроса с другого сайта под авторизационными данными пользователя. Например, у нас есть действие удаления своего аккаунта example.com/login/dropme. Если защиты от CSRF атаки нет, мы можем на нужном нам сайте разместить тег:
<img src="http://example.com/login/dropme">
Сразу после того как пользователь зайдет на приготовленную нами страницу и подгрузит содержимое img, его аккаунт на example.com будет удален. О защите от этого я расскажу под катом.
Механизм атаки
Суть атаки заключается в установке тега, подгружающего контент по url с другого сайта. Таким тегом может быть img, script, link(для css), iframe и возможно другие, которые сразу не пришли мне в голову.
Стандартные способы защиты и чем они меня не устроили
Есть простой способ защиты: проверка HTTP_REFERER. Он меня не устроил потому как браузер в анонимном режиме может и не посылать этот заголовок. В таком случае все «анонимные» пользователи будут подвержены такой атаке.
Есть более продвинутый способ защиты: добавление токена к url и проверка токена при выполнении действия. Чем же меня это не устроило? Есть готовое приложение в котором уже более 100 ссылок на различные страницы и действия, они выполнены в коде в виде <a href="..."></a>, а не в виде вызова функции с передачей url, соответственно править придется более 100 мест. Есть риск что то забыть.
Решение
Решение было найдено быстро. Поскольку всё в приложении работает через ajax, мы можем добавлять в ajax-запрос заголовок с токеном. В нашем случае, на jQuery это делается так:
$.ajaxSetup({
headers: {
'X-Csrf-Token':token
}
});
Помимо ajax-запросов могут быть и открытия в новой вкладке, и самое важное — первый заход в приложение. Эти запросы не содержат токена, но мы должны их обработать. Для их обработки мы используем следующее решение. Если токена в запросе нет, отдавать следующий html-код:
<html>
<body>
<?php echo $loading_text; ?>
<script type='text/javascript'> <?php /** check img and other left src tags **/ ?>
if (parent.document.location.href == document.location.href){ <?php /** check iframe **/ ?>
document.location.href='<?php echo $url; ?>';
}
</script>
</body>
</html>
Здесь $url — это тот урл по которому был сделан запрос с подписанным в конце &csrf_token. Если такой код будет отдан в тег img, script, link — он выполнен не будет и злоумышленник не добьется цели. Если же код будет встроен в iframe то условие в if отсечет его выполнение.
Соответственно нам осталось научиться обрабатывать токен из параметра GET и отдавать его на клиентскую сторону для нашего заголовка. Собственно отдавать его не особенно и проблематично. После загрузки страницы, например example.com/profile мы всегда попадаем на страниу example.com/profile?csrf_token=...
соответственно нам осталось вытащить с помощью js токен из get параметра.
Послесловие
Всё что описано выше я организовал в библиотеку и выложил на github. В той библиотеке есть несколько недоработок, но я исправлю их как только появиться время. Данная библиотека уже работает на реальном проекте.
Автор: piromanlynx