Skip to content

Latest commit

 

History

History
executable file
·
136 lines (91 loc) · 19.5 KB

xsrf.md

File metadata and controls

executable file
·
136 lines (91 loc) · 19.5 KB

Уязвимость XSRF

Эта уязвимость (XSRF, CSRF, Cross-Site Request Forge - межсайтовая подделка запросов) позволяет злоумышленнику, заманив пользователя на свою страницу, отправлять запросы на уязвимый сайт от его имени. Чтобы понять то, что ниже написано, тебе надо знать, как делаются HTML-формы и как они обрабатываются на стороне PHP (что такое $_POST и $_GET).

Допустим, у тебя на сайте (bank.example.com) есть какая-то форма, доступная только залогиненным пользователям. Допустим, ты делаешь сайт банка и это форма перевода денег. Пользователь вводит сумму, номер счета и отправляет форму для выполнения перевода. Она может выглядеть примерно так:

<h1>Перевести деньги со счета</h1>
<form action="send.php" method="POST">
    <label>Сумма: <input name="sum" value=""></label>
    <label>Номер счета получателя: <input name="target" value=""></label>
    <input type="submit" value="Отправить">
</form>

Скрипт-обработчик этой формы (send.php) проверяет, залогинен ли пользователь, есть ли у него на счету нужная сумма, и, если все в порядке, то переводит ее на счет получателя.

Ничего подозрительного, верно? Неужели в такой простой форме из двух полей может быть уязвимость? А она есть.

Разработчик этой формы не учел, что она может быть отправлена с любого сайта, а не только с сайта банка. Форму можно разместить на любой странице в Интернете и указать в ней в качестве адреса для отправки сайт банка. Это особенность языка HTML и протокола HTTP - форма не обязана находиться на том же сайте, что и обработчик данных из нее. Злоумышленник может сделать на своем сайте (evil.example.com) страницу с заполненной формой, сделать ее невидимой и добавить яваскрипт-код (яваскрипт это язык, программы на котором встраиваются в HTML-страницу и выполняются в браузере), который автоматически отправляет эту форму на сайт банка при заходе пользователя на страницу:

<!-- Указываем в качестве адреса отправки сайт банка и делаем форму 
     невидимой (display: none), чтобы не вызывать подозрений у пользователя -->
<form action="https://bank.example.com/send.php" style="display: none;">
    <input type="hidden" name="sum" value="9000">
    <input type="hidden" name="target" value="счет злоумышленника">
</form>
<script>
// код на языке яваскрипт, автоматически отправляющий форму
// при заходе на эту страницу пользователем
document.forms[0].submit();
</script>

После этого злоумышленнику достаточно любым способом заманить пользователя банка на свою страницу (например скинув ему ссылку со словами "ты будешь в шоке, когда это увидишь!", выложив ссылку на популярном сайте или купив размещение в рекламной сети, которая отобразит страницу злоумышленника с формой на большом числе партнерских сайтов за плату). Если пользователь перейдет на страницу злоумышленника, сработает яваскрипт-код на ней и отправит заполненную форму на сайт банка от имени пользователя. Если пользователь был в этот момент залогинен в банке, то форма будет обработана и деньги переведены. Разумеется, злоумышленник может предусмотреть отправку формы в цикле, до тех пор, пока счет не будет опустошен.

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

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

XSRF может использоваться не только с формами. Допустим, на некотором форуме переход по ссылке http://forum.example.com/logout.php разлогинивает пользователя. Допустим, на этом форуме в текст сообщения можно вставлять картинки с любым URL. Злоумышленник может запостить «картинку» с URL, ведущим на ссылку разлогинивания, и когда пользователь зайдет на страницу с такой картинкой, его браузер отправит запрос и автоматически разлогинит его.

Атака XSRF может применяться и на сайтах, где нет авторизации пользователей. Допустим, у нас есть сайт, где разыгрывается приз и победитель выбирается голосованием. Голосовать может любой пользователь, без регистрации, но только 1 раз с 1 IP-адреса. Если форма голосования не защищена от CSRF, то злоумышленник может сделать свою страницу с формой, заманивать на нее пользователей и тем самым накручивать голоса.

Надеюсь, ты понял, что это довольно серьезная уязвимость, и надо уметь от нее защищаться.

Способ борьбы

Чтобы бороться с отправленной без ведома пользователя формой, скрипт-обработчик на сервере должен отличить запрос, отправленный со своего сайта от запроса, отправленного с чужого. Один из способов сделать это — перед показом формы выдать каждому пользователю индивидуальный код (токен), который сохраняется в куке в браузере, и вставить копию этого токена в форму. А при получении данных из формы сравнить токены в куках и в $_POST. Так как злоумышленник не знает токена пользователя, он не может вставить его в форму на своем сайте и пройти проверку.

То есть непустой токен в данных формы, совпавший с токеном в куках подтверждает, что форма была отправлена с нашего, а не чужого сайта.

Вот примерный алгоритм. Прежде всего, мы должны при заходе на страницу с формой выдать пользователю куку с токеном (а если она у него есть, продлить ей время жизни):

Если (у пользователя нет куки с токеном) {
    $token = генерируем случайный код;
    сохраняем код в куки пользователю;
} иначе {
    $token = код из куки;
    Продлеваем время жизни куки;
}

Код должен быть сложным, чтобы злоумышленник не мог его угадать. 32 символа [a-zA-Z0-9] дают около 6232 = 2×1057 комбинаций, что делает подбор нереальным.

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

Далее, мы должны при выводе формы добавить скрытое поле с токеном в форму:

<input type="hidden" name="token" value="<?= htmlspecialchars($token, ENT_QUOTES) ?>">

Ну и наконец при обработке данных формы мы должны сравнить токен в куках и в данных формы:

Если (токен в куках пуст или токен в форме пуст или они не равны) {
    добавляем сообщение об ошибке и не принимаем данные;
}

В случае ошибки токена можно просто вывести форму с введенными данными и вывести сообщение с просьбой проверить введенные данные и отправить форму повторно, если они верны.

Точно так же с помощью токена можно защитить и действия, выполняемые по ссылке (хотя по идее для таких вещей должны использоваться формы). Например, ссылка на страницу разлогинивания с сайта с токеном может выглядеть как /logout.php?token=abcdef123456 (именно так выглядят ссылки на многих сайтах).

У кук есть флаг httpOnly, который запрещает доступ к ним из яваскрипта. Если его включить для куки с токеном, защита будет более надежной.

Если ты используешь фреймворк (вроде Yii или Symfony 2), возможно в его реализации классов для обработки форм есть защита от XSRF. Но ты должен внимательно проверить документацию и HTML код — может быть, защита по умолчанию отключена или требует отдельной настройки.

Этот способ называется "Double Submit Cookie" в статье OWASP https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet.

Другие методы

Ты можешь увидеть где-то неправильный совет генерировать токен не случайно, а из IP-адреса пользователя (чтобы не использовать куки). Это может приводить к багам, например если у мобильного пользователя или пользователя с динамическим IP адресом в процессе заполнения формы разорвалось соединение (и телефон переподключился, получив другой IP) то при отправке формы токен не совпадет. Некоторые провайдеры раз в N часов меняют IP адреса своим пользователям. Такие проблемы, которые происходят иногда, и не у всех пользователей, очень сложно обнаружить, повторить и исправить.

Также, некоторые советуют проверять заголовок Referer (а при его отсутствии - Origin). Referer содержит URL страницы, с которого была отправлена форма, Origin - это новый заголовок, который содержит только протокол и имя хоста страницы (но не полный URL), с которого отправлены данные. Стоит помнить, что некоторые пользователи отключают передачу заголовка Referer, чтобы сайты не знали, с какой страницы пришел пользователь. Также, заголовок Referer могут удалять некоторые прокси-серверы (например, в организациях).

Можно проверить эти заголовки на соответствие домену сайта. Это не всегда надежно, так как пользователь мог отключить передачу Referer (в этом случае можно проверить Origin), а также, есть способы отправлять форму без него (внизу есть ссылка с описанием). В случае, если оба заголовка отсутствуют, нужно отказаться принимать данные формы (чтобы злоумышленник не мог обойти защиту, передав запрос без этих заголовков), но стоит логгировать такие факты, чтобы убедиться, что таких пользователей нету.

Вот примерый алгоритм проверки:

Если (оба Referer и Origin пусты, неправильно заполнены или не содержат имени хоста) {
    отказать в приеме данных формы, так как невозможно установить, откуда отправлен запрос;
}

Если (Referer не пуст и в Referer указан чужой сайт) {
    отказать в приеме данных формы, так как она отправлена с чужого сайта;
}

Если (origin не пуст и в Origin указан чужой сайт) {
    отказать в приеме данных формы, так как она отправлена с чужого сайта;
}

Иначе все в порядке, форма отправлена с нашего сайта.

Можно ли сделать защиту, работающую без кук?

Способ, описанный выше, подразумевает хранение токена в 2 местах: в форме в виде скрытого поля и в куках. Если у пользователя по какой-то причине отключены куки, то он не сможет пройти проверку. В принципе, делать с этим ничего не надо - без кук и так большинство форм и авторизация работать не будет. Но что, если нас одолел приступ перфекционизма и мы хотим сделать, чтобы защита от XSRF работала бы без поддержки кук?

Я не нашел надежного способа это сделать. На OWASP есть статья с описанием методов защиты от CSRF https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet (англ.), и там нету вариантов без использования кук.

Домашнее задание

Зайди на свои любимые сайты (где есть авторизация), открой инструменты разработчика в браузере (Ctrl + Shift + I), изучи формы и ссылки на сайте, свои куки, и попробуй понять, есть на них защита от XSRF или нет.

Ссылки

  • http://habrahabr.ru/post/235247/ - ошибки при реализации защиты от CSRF
  • http://habrahabr.ru/post/21626/ - CSRF на вконтакте в 2008 (тогда у них много уязвимостей было)
  • http://intsystem.org/812/stripping-referer-in-redirect/ - некоторые варианты отключить передачу Referer
  • http://www.securiteam.com/securityreviews/5KP0M1FJ5E.html - здесь описана возможность отправить запрос на произвольный домен с добавлением произвольных значений заголовкам Referer и Origin, что позволяет обойти защиту с проверкой этих заголовков. Уязвимость существовала в Adobe Flash версий 7 и 8, выпущенных до 2006 года.
  • https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) - подробное и сложное описание этой уязвимости на OWASP (OWASP - это Открытый Проект по Защите Веб-приложений, содержит описания различных уязвимостей и методы борьбы с ними)