Pull to refresh

Django 1.2 и CSRF

Reading time 5 min
Views 11K
Original author: 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-защита — хороший шаг вперёд. Теперь вам только остаётся начать ей пользоваться!
Tags:
Hubs:
+41
Comments 22
Comments Comments 22

Articles