Защита от CSRF

csrfCSRF является одной из самых распространенных уязвимостей в современных веб-приложениях. Это связано с недооценкой разработчиками степени угрозы CSRF-атак, что выражается в недостаточных мерах защиты против CSRF или вообще в полном их отсутствии. На самом деле, эффективно обезопасить свое веб-приложение от CSRF совсем не сложно: уже достаточно давно практикуется универсальный способ, о котором я хотел бы рассказать в этом посте. Кроме того, будут затронуты уже готовые реализации этого приема: CSRFx и csrf-magic.

Общие сведения

CSRF-атака использует функцию браузера автоматической отправки идентификатора сессии с каждым GET/POST-запросом к веб-приложению. Атакующий находит ссылки, которые могут выполнять определенные операции, причем, как правило, эти функции доступны только для привилегированных пользователей. Это могут быть операции перевода денег, перенаправления почты на другой электронный ящик, сброса пароля, покупки каких-либо товаров и многое другое. Попав на страницу, находящуюся под контролем атакующего, жертва невольно совершит переход по ссылке, уязвимой к CSRF, и выполнит операцию в контексте целевого веб-приложения. Обычно при CSRF-атаках используют тэг <img>, при этом источником изображения является уязвимая ссылка, по которой браузер автоматически перейдет, добавив заголовок Cookie. Примеры:

  • <img src=”http://bank/transfer?account=Vasya&amount=10000&for=Oleg”>
  • <img src=”http://mail/forward?mail=mymail@mymail”>
  • <img src=”http://site/resetpassword?user=admin&password=123″>
  • <img src=”http://cms/admin/delete?article=5″>

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

Защита

Двумя самыми распространенными заблуждениями касательно способов защиты от CSRF являются:

  • Использование POST вместо GET: на самом деле совершить POST-запрос также легко как и GET.
  • Реализация страниц подтверждения: данный подход лишь ненамного затрудняет процесс атаки – необходимо делать два запроса вместо одного (переход по ссылке для подтверждения операции и собственно выполнение самой операции)

Грамотная защита основывается на использовании случайного идентификатора, который генерируется для каждой операции и необходим для ее выполнения. Данный идентификатор также называют маркером доступа (token, nonce); он помогает эффективно различать запросы.

Алгоритм защиты:

  1. создаем случайный маркер доступа и добавляем его в массив $_SESSION
    <?php
    $token = md5(uniqid(mt_rand() . microtime()));
    $_SESSION['token'] = $token;
    ?>
  2. к каждой форме добавляем hidden-поле с идентификатором
    <input name="token" type="hidden" value="<?php echo $token ?>" />
  3. теперь при каждом запросе необходимо проверять корректность маркера доступа
  4. <?php
    $token = $_POST['token'];
    if($_SESSION['token'] == $token) {
    	$_SESSION['token'] = '';
    	/* some actions */
    } else {
    	die("404 Not Found!");
    }
    ?>

Существуют и готовые реализации этого метода для PHP, которые надстраиваются над веб-приложениями, как вручную, так и автоматически с помощью директивы auto_prepend_file.

CSRFx
URL: http://code.google.com/p/csrfx/

  • Необходима база данных (MySQL)
  • Обрабатывает как GET, так и POST-запросы
  • Работает только с PHP5
  • Не поддерживает AJAX-вызовы

csrf-magic
URL: http://csrf.htmlpurifier.org/

  • Работает без БД
  • Обрабатывает только POST-запросы
  • Работает как с PHP4, так и с PHP5
  • Имеет обработчик метода XMLHttpResponse – поддерживает AJAX-вызовы

Тем не менее, начинать стоит с защиты против XSS, так как, обнаружив XSS-уязвимость, атакующий сможет выяснить значение маркера доступа с помощью AJAX.


12 comments:

  1. Best, 22. September 2008, 14:32

    Не совсем согласен с тобой, имея XSS можно выдернуть token из формы, и отправить его post’ом по средствам AJAX.

     
  2. Best, 22. September 2008, 15:04

    Вопрос снят))

     
  3. Best, 22. September 2008, 15:10

    <input name=”token” type=”hidden” value=”” />
    Наверно должно было быть так:
    <input name=”token” type=”hidden” value=”” />
    Или хотя бы так:
    <input name=”token” type=”hidden” value=”” />

     
  4. Best, 22. September 2008, 15:16

    Блин, срезался код.

    надо заменить на

    или на

     
  5. Best, 22. September 2008, 15:19

    Да ё маё)) второй пункт алгоритма защиты поправь. value=. в php нет принта, или <?=, поэтому токен не выведется на экран.

     
  6. Raz0r, 22. September 2008, 18:52

    спасибо, поправил

     
  7. Andrey, 6. October 2008, 15:42

    Здрасти.
    Автор слышал чтонибуть о Sessionid?

     
  8. Raz0r, 6. October 2008, 20:53

    Здравствуйте, Andrey. Попробуйте прочитать пост еще раз, более внимательно.

     
  9.  

    […] защиты против CSRF с помощью токенов, о чем я писал в одном из предыдущих постов. Кроме того, возможна (точнее была возможна в связи с […]

     
  10. Vinc, 6. November 2011, 4:15

    Зачем строка
    $_SESSION[‘token’] = ”;

    Зачем обнулять это, если понадобится ещё выводить формы?

     
  11. Vinc, 6. November 2011, 5:43

    Или token меняется при каждом обращении к странице?

     
  12. Alex, 22. January 2013, 13:36

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

     

Write a comment: