Django 1.2 и CSRF

Автор оригинала: Andrew Godwin
  • Перевод
CSRF, или Cross-Site Request Forgery (межсайтовая подделка запроса) — это, возможно, одна из самых забываемых уязвимостей. Разработчики, как правило, знают о SQL инъекциях и XSS атаках, но очень часто забывают о CSRF-атаках.

Для тех, кто не в курсе: CSRF-атака использует браузер пользователя — открытые сессии и сохранённые куки, — чтобы послать запрос с вредоносными данными. Они, как таковые, не являются серверными экплоитами; они могут только воздействовать на данные, хранимые на целевом сайте, к которым пользователь имеет доступ.

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

Но разработчики Django не дремлют. Благодаря им, в Django уже достаточно давно был CSRFMiddleware, хотя и не очень хороший. Но давайте сперва рассмотрим, как же делается CSRF-атака, дабы лучше понимать, о чём идёт речь.


Анатомия CSRF-атаки


CSRF-атака устроена таким образом, что она заставляет браузер пользователя послать запрос (GET или POST, в зависимости от цели) на атакуемый сайт с произвольным, угодным атакующему, содержимым.

Например, представьте систему онлайн банкинга, где есть форма, позволяющая делать денежные переводы. Я хочу послать Алисе (банковский аккаунт номер 00000001) немного денег. Заполняю на сайте форму, посылаю. Браузер делает следующий запрос (да, я знаю, что это невалидный HTTP, но я просто хочу показать суть):

    POST /banking/transfer/ HTTP/1.1
    Host: www.andrewsbank.com

    amount=100&recipient=00000001


Далее, представьте, я, не завершая сессию (многие люди просто закроют вкладку или перейдут на другой сайт), перехожу на зловредный сайт. На нём я вижу большую кнопку «Продолжить»; однако, не всё так просто:

    <form action="http://www.andrewsbank.com/banking/transfer/" method="POST">
        <input type="hidden" name="amount" value="100" />
        <input type="hidden" name="recipient" value="00000002" />
        <input type="submit" value="Продолжить" />
    </form>


Как вы видите, эта безобидная на первый взгляд кнопка посылает 100 условных единиц господину Бобу (номер 00000002).

Это не очень сложная атака, но и банк не очень хороший (я бы посоветовал Алисе перевести свои сбережения в другой банк). Но всё же, надеюсь, этого примера будет достаточно для понимания, что из себя представляют такого типа атаки, и на что они способны (в действительности, впервые такие атаки были обнаружены именно в банковских системах).

В интернете полно ресурсов, посвящённых CSRF-атакам; если вы хотите подробностей, можете начать со статьи в Википедии; также есть хорошая статья Jeff Atwood.


Защита от CSRF


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

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

Если вы проверяете сессию и смотрите, что токен был выдан относительно недавно, вы можете с достаточной степенью уверенности утверждать, что это настоящий запрос от пользователя. Вы никогда не можете быть уверены на 100 процентов в области информационной безопасности, так как постоянно появляются новые типы атак (например, зловредный сайт может подгрузить вашу страничку в iframe и показать пользователю только одну кнопку, скажем, «удалить»), но всё же это лучше, чем ничего.


Django и CSRF


Вернёмся к упомянутому ранее CSRFMiddleware. Версия, шедшая в поставке с Django 1.1, работала так: она брала результирующий HTML, пропускала через регулярку в поисках всех форм и добавляла в них <input>, содержащий токен, который потом проверялся у входящего POST запроса.

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

Были также и другие проблемы. Например, требовалось подключать SessionMiddleware и нельзя было активировать защиту от CSRF только для одного приложения, поскольку Middleware — глобальная штука.


Что же изменилось?


В Django 1.2 CSRFMiddleware теперь не довольствуется одними регулярками, более того, их там теперь вообще нет, зато появилось много новых возможностей.

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

Кроме того, новую защиту не требуется включать глобально. Теперь есть новый декоратор django.views.decorators.csrf.csrf_protect, который вы можете использовать для обеспечения защиты для конкретных вьюх. Разумеется, теперь этот декоратор используется во всех приложениях Django, что значит, что ваша админка теперь защищена, даже если вы забыли подключить CSRFMiddleware.

Также CSRF защита теперь является частью ядра Django, а не contrib приложением, как ранее. Сделали мы так, поскольку считаем, что защита от CSRF-атак, так же как и auto-escaping, должна быть неотъемлемой частью хорошего веб фреймворка.

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


Недостатки


Любое сколько-нибудь магическое решение имеет свои недостатки. Новая защита также не исключение.

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

Во-вторых, эта защита не сработает на кастомные рендереры шаблонов. Если вы используете их, вам придётся вручную встраивать в HTML токены. Для этого вы можете использовать метод django.middleware.csrf.get_token().


Заключение


CSRF — это сложная проблема, которая ещё не полностью решена в Django (и, возможно, никогда целиком не будет). Однако, как и с auto-escaping'ом, цель — дать твёрдую основу, от которой можно оттолкнуться. Новая CSRF-защита — хороший шаг вперёд. Теперь вам только остаётся начать ей пользоваться!

Комментарии 22

    +2
    Спасибо, большое!

    Я раньше совсем не задумывался над этой проблемой, приятно, что Django становится еще удобнее и дружественнее к разработчику.
      +2
      Да, спасибо. Полезно.
        +1
        Спасибо!
          –1
          а не лучше ли как в RoR? По умолчанию(если не отключено в конфиге) токен в любой форме, если не указано обратное. Сдается мне — форм внутри сайта гораздо больше, чем форм на внешние ресурсы(которых у большинства сайтов вообще нету). Зачем делать лишнюю работу и засорять шаблон?
            –7
            Это же пейтон, ничего не поделать. Они и логику в шаблонах злом считают.
              +1
              вы таки удивитесь, но не только они оО
              0
              Чем декоратор-то не устраивает?
                0
                тем что:
                1) О нем надо знать и помнить. Комментарии наверху показывают что этого большинство не знает;
                2) По умолчанию оно правильней. Посудите сами — все или почти все формы указывают на сам сайт;
                На ум приходит только один случай, когда я отключал токены — морда для части функционала на flex(flash). Хотя и там можно найти выход.
                  +1
                  Как гласит Дао — «Явное лучше неявного» :)
                    –1
                    По умолчанию защита CSRF включается глобально для всех view через middleware (эта middleware есть во вновь создаваемом через startproject settings.py), и в своем коде правильно — это отключать ее через декоратор, а не включать. Это дает то преимущество, что нельзя случайно забыть про защиту где-то: отправка форм просто не будет работать и выдаст подробную ошибку с инструкцией, что делать.
                  –1
                  Первое предложение было именно такое, как Вы говорите. Оно даже было реализовано (см. github.com/simonw/django-safeform).

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

                  Если интересно, почитайте дискуссию и это сообщение в ней.
                  0
                  Спасибо за статью.
                    –2
                    >Новая защита требует от вас теперь вручную добавлять {% csrf_token %} во все ваши формы

                    Я и со старой всегда так делал :) А вообще — меньше, меньше форм!
                      0
                      Полезно. Спасибо.
                        +2
                        Плохо: «апельсин сок», «трамвай поездка», «CSRF атака».
                        Хорошо: «апельсиновый сок», «поездка на трамвае», «CSRF-атака», «атака через CSRF».
                        Не будем путать грамматику русского и английского языков.

                        За перевод спасибо, всегда было лень разобраться в этой штуковине (опасный подход «работает-и-ладно»), а с 1.2 необходимость изучения вопроса стала актуальнее. На исходный материал без Вашего перевода не скоро наткнулся бы, хоть сайт и знаком.

                        Кстати, а не перевести ли token как маркер?
                          +1
                          Token как маркер — однозначно нет. Никто не поймёт. Так и nonce можно перевести. :)

                          Атаку поправил, спасибо за замечание.
                          +1
                          спасибо за перевод, уже совсем скоро бум отмечать релиз)
                            0
                            О, спасибо за обзор. Как раз просил, чтобы кто-нибудь написал. Держите плюс в карму.

                            Радует, что нет глобального требования обновления форм, а то я боялся, что после миграции на новую версию джанго вся работа остановится пока не добавим во все формы токен (а у меня формы не всегда используются по прямому назначению — в некоторых случаев для работы с аяксовыми запросами).
                              0
                              Как вы считаете — можно-ли положиться на этот механизм и отказаться от каптчи?
                                0
                                Всё-таки эти вещи предназначены для разных задач. Каптча (Completely Automated Public Turing test to tell Computers and Humans Apart) предназначена для различения роботов и людей. Ей, конечно, можно защититься от CSRF атак, но вы же не станете закрывать каждую вашу форму на сайте каптчей даже для залогиненых пользователей?
                                  0
                                  Нет. Я имел ввиду наоборот, что положиться на csrf-защиту и не ставить капчу.
                                  Позволит ли этот механизм от ботов защититься.
                                  .
                                    0
                                    Нет. Бот точно так же, открыв страничку на вашем сайте, получит токен и, если он достаточно хорош и поддерживает куки, отправит запрос на сервер с валидным токеном.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое