- PVSM.RU - https://www.pvsm.ru -
Совсем недавно у меня появилась задача защитить web-приложение полностью построенное на ajax от CSRF-атак [1].
Каков же механизм такой атаки? Суть заключается в выполнении запроса с другого сайта под авторизационными данными пользователя. Например, у нас есть действие удаления своего аккаунта example.com/login/dropme. [2] Если защиты от 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 [3] мы всегда попадаем на страниу example.com/profile?csrf_token=... [4]
соответственно нам осталось вытащить с помощью js токен из get параметра.
Всё что описано выше я организовал в библиотеку и выложил на github [5]. В той библиотеке есть несколько недоработок, но я исправлю их как только появиться время. Данная библиотека уже работает на реальном проекте.
Автор: piromanlynx
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/8375
Ссылки в тексте:
[1] CSRF-атак: http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D0%BB%D0%BA%D0%B0_%D0%BC%D0%B5%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D1%8B%D1%85_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2
[2] example.com/login/dropme.: http://example.com/login/dropme.
[3] example.com/profile: http://example.com/profile
[4] example.com/profile?csrf_token=...: http://example.com/profile?csrf_token=...
[5] github: https://github.com/piroman-lynx/lpg_csrf_token
Нажмите здесь для печати.