Защита ajax-приложения от Cross Site Request атак (CSRF)

Совсем недавно у меня появилась задача защитить web-приложение полностью построенное на ajax от CSRF-атак.

Каков же механизм такой атаки? Суть заключается в выполнении запроса с другого сайта под авторизационными данными пользователя. Например, у нас есть действие удаления своего аккаунта example.com/login/dropme. Если защиты от 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 мы всегда попадаем на страниу example.com/profile?csrf_token=
соответственно нам осталось вытащить с помощью js токен из get параметра.

Послесловие


Всё что описано выше я организовал в библиотеку и выложил на github. В той библиотеке есть несколько недоработок, но я исправлю их как только появиться время. Данная библиотека уже работает на реальном проекте.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 31

    +16
    Вопрос сразу и «в лоб», а зачем Вы потенциально опасное действие «dropme» передаете в виде адреса или параметра GET (если используется mod-rewrite)?
    Не лучше ли воспользоваться POST-запросом? Тогда и «встраивание» в img не будет работать.
      –4
      Делать можно и POST-ом, но это не спасёт от post запроса с помощью ajax с другого сайта.
        +4
        Так а зачем вы заголовок «Access-Control-Allow-Origin: *» пишете? Срочно уберите.
          –3
          Спасибо! только где он пишется? что то я не вижу этого в коде библиотеке и похоже на дефолтное значение которое подсовывает Ваш браузер…
            +2
            Этот заголовок необходим для кроссдоменных POST-ajax запросов.
            Поэтому если у вас идут POST ajax с других сайтов — то вы его где-то написали.

            en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing
              +1
              afaik, если создать <form> в iframe и там сделать submit, то ошибки same origin policy не будет.
        +3
        От фреймов не спасёт.
        +3
        У вас ошибка. Методом GET нельзя менять состояние. Прочитайте наконец спецификации HTTP(там написано, для чего какой метод служит).
          0
          Я то это знаю, но тем не менее многие этого не знают и так делают.

          +Данная библиотека защищает не только от опасных действий но и от вытаскивания контента страницы ( например с персональными данными пользователя), например в iframe или в div с помощью $('div.hidden').load('http://example.com/profile/');.
            +2
            Данные нельзя вытащить согласно запрету «same origin policy»
            +2
            Ну… нельзя так категорично рассуждать. Если есть возможность и она себя зарекомендовала, то почему бы ей не пользоваться?
              +1
              Это основы HTTP протокола. Там четко прописано, что GET такого делать не должен.
              Нарушать *базовые* правила можно только когда понимаешь, зачем это делается, с полным осознанием последствий. А в данном случае от нарушений плюсов быть не может, только подобные минусы.
                +3
                Ну вот, так всегда:
                > Методом GET нельзя менять состояние.
                > Нарушать *базовые* правила можно
            –3
            Мне кажется, что с удалением не совсем в кассу пример. Как ни удаляй (аякс либо с перезагрузкой страницы) данные, всё равно было бы неплохо проверить в сессии есть ли у пользователя права на удаление будь это личный эккаунт либо любая другая запись.
              0
              Так смысл такой атаки заключается в том что на действие у пользователя есть всё права и он в данный момент авторизован и выполняет это под своим логином и из своей сессии.

              Если у пользователя есть действие для удаления собственного аккаунта и права на это, но нет защиты от Cross Site Request — злоумышленнику ничего не мешает это сделать
                –6
                И он одновременно лезет в том же браузере на сайт злоумышленника который производит атаку? Это накладывает ряд ограничений. Тут наиболее уязвимы сайты связанные с соц сетями на атаку от себе подобных, либо какие нибудь аггрегаторы спец предложений туроператоров. Для которых модель поведения типичного пользователя включает вариант открытия сразу нескольких сайтов параллельно, т.о. чтобы и «жертва» и «атакующий» были открыты одновременно.
                  +4
                  К слову, это довольно распространенная ситуация и здесь вовсе необязательно, чтобы все сайты были явно открыты у пользователя, есть такая штука как куки и храниться они могут долго.
              0
              Два минуса для такого решения:

              1. Даёт только снижение уровня угрозы, а не защиту. Всё ещё остаётся возможность просто подсунуть ссылку пользователю.
              2. Увеличивается нагрузка.
                –1
                А почему не использовать токен, как это делается обычно?

                Токен, в отличие от вашего решения, позволит защитить не только ajax, но и все другие запросы в одном стиле.
                  +1
                  Если я вас правильно понял, автор отказывается от токена в силу:
                  Есть готовое приложение в котором уже более 100 ссылок на различные страницы и действия, они выполнены в коде в виде , а не в виде вызова функции с передачей url, соответственно править придется более 100 мест. Есть риск что то забыть.
                    –1
                    А, и вправду.

                    Позволю себе перефразировать: вместо того, чтобы использовать проверенные временем решения — давайте выдумаем что-нибудь новое, а то с проверенными можем ошибиться
                      +1
                      Это не новое — это ровно то же токен, только токен передается не в тексте url, а в заголоке.
                        +1
                        Ох, пардон, прочитал невнимательно код :-S Почему-то думал что там просто передаётся флажок :-( Простите
                  +1
                  Разве x-requested-with недостаточно?
                    –3
                    СОГЛАСЕН — Проверка HTTP_REFERER — простое и удобное решение!

                    А в остальном — совершенству нет предела)

                    Спасибо за интересный пост!)
                    • UFO just landed and posted this here
                        0
                        Согласен, от роботов защищаться надо и token нужен.

                        Итого:

                        Безопасный AJAX:

                        1) HTTP_REFERER
                        2) token
                        3) Защита от роботов

                        Верно?
                        • UFO just landed and posted this here
                      0
                      TL;DR использовать токены
                        0
                        А есть у кого-то класс или библиотека, для легкой организации этих трех китов?

                        Если есть альтруисты, предлагаю поделиться с хабраюзерами)
                          0
                          Вот интересно, есть ли где-нибудь полный перечень того, что должен знать web разработчик о безопасности разрабатываемого приложения? Просто, чтобы идти по пунктам и проверять реализовано ли у тебя и ничего не забыть.

                          Only users with full accounts can post comments. Log in, please.