Что такое CSRF атака?


Ознакомиться с самой идеей атаки CSRF можно на классических ресурсах:



Выдержка из ответа на SO:

Причина CSRF кроется в том, что браузеры не понимают, как различить, было ли действие явно совершено пользователем (как, скажем, нажатие кнопки на форме или переход по ссылке) или пользователь неумышленно выполнил это действие (например, при посещении bad.com, ресурсом был отправлен запрос на good.com/some_action, в то время как пользователь уже был залогинен на good.com).


Как от нее защититься?


Эффективным и общепринятым на сегодня способом защиты от CSRF-Атаки является токен. Под токеном имеется в виду случайный набор байт, который сервер передает клиенту, а клиент возвращает серверу.


Защита сводится к проверке токена, который сгенерировал сервер, и токена, который прислал пользователь.


А что, собственно, защищать?


Если вы пишете свой Web-Сервис в соотвествии со стандартом RFC7231, то методы GET, HEAD, OPTIONS и TRACE являются безопасными: они предназначены только для получения информации и не должны изменять состояние сервера.


Таким образом, защищать необходимо небезопасные методы, к которым относятся: POST, PUT, DELETE, PATCH.


На Habrahabr опубликована статья от Yandex, в которой описано, почему писать свои сервисы нужно, руководствуясь стандартом.


Требования к токену:


  • Уникальный токен для каждой операции
  • Действует единожды
  • Имеет размер, устойчивый к подбору
  • Сгенерирован криптографически стойким генератором псевдослучайных чисел
  • Имеет ограниченное время жизни

На первом MeetUp'е PDUG Тимур Юнусов (руководитель отдела безопасности банковских
систем Positive Technologies
) рассказывал, почему именно такие требования предъявляются к CSRF-Токену и чем грозит пренебрежение ими.


Требования к Web-Сервису и окружению:


  • Отсутствие XSS уязвимостей


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


    Таким образом, XSS уязвимости могут быть использованы для получения текущего токена.


  • Отсутствие malware на машине клиента


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



Методы защиты


Существует 3 метода использования токенов для защиты web-сервисов от CSRF атак:



Synchronizer Tokens


Простой подход, использующийся повсеместно. Требует хранения токена на стороне сервера.


Суть:

  1. При старте сессии на стороне сервера генерируется токен.


  2. Токен кладется в хранилище данных сессии (т.е. сохраняется на стороне сервера для последующей проверки)


  3. В ответ на запрос (который стартовал сессию) клиенту возвращается токен.


    Если рендеринг происходит на сервере, то токен может возвращаться внутри HTML, как, например, одно из полей формы, или внутри <meta> тега.


    В случае, если ответ возвращается для JS приложения, токен можно передавать в header (часто для этого используют X-CSRF-Token)


  4. При последующих запросах клиент обязан передать токен серверу для проверки.


    При рендере контента сервером токен принято возвращать внутри POST данных формы.


    JS приложения обычно присылают XHR запросы с header (X-CSRF-Token), содержащим токен.


  5. При получения запроса небезопасным методом (POST, PUT, DELETE, PATCH) сервер обязан проверить на идентичность токен из данных сессии и токен, который прислал клиент.


    Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.



На выходе имеем:

  • Защита от CSRF на хорошем уровне


  • Токен обновляется только при пересоздании сессии, а это происходит, когда сессия истекает


    Во время жизни одной сессии все действия будут проверяться по одному токену.


    Если произойдет утечка токена, то злоумышленник сможет выполнить CSRF-Атаку на любой запрос и в течение долгого срока. А это не есть хорошо.


  • Бесплатная поддержка multi-tab в браузере.


    Токен не инвалидируется после выполнения запроса, что позволяет разработчику не заботиться о синхронизации токена в разных табах браузера, так как токен всегда один.




Этот подход не требует хранения данных на стороне сервера, а значит, является Stateless. Используется, если вы хотите уметь быстро и качественно масштабировать свой Web-сервис горизонтально.
Идея в том, чтобы отдать токен клиенту двумя методами: в куках и в одном из параметров ответа (header или внутри HTML).


Суть:

  1. При запросе от клиента на стороне сервера генерируется токен. В ответе токен возвращается в cookie (например, X-CSRF-Token) и в одном из параметров ответа (в header или внутри HTML).


  2. В последующих запросах клиент обязан предоставлять оба полученных ранее токена. Один как cookie, другой либо как header, либо внутри POST данных формы.


  3. При получении запроса небезопасным методом (POST, PUT, DELETE, PATCH) сервер обязан проверить на идентичность токен из cookie и токен, который явно прислал клиент.


    Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.



На выходе имеем:

  • Stateless CSRF защиту.


  • Необходимо учитывать, что поддомены могут читать cookie основного домена, если явно это не запрещать (т.е. если cookie установлена на .site.ru, то её могут прочитать как a.site.ru, так и b.site.ru).


    Таким образом, если ваш сервис доступен на домене 3-го уровня, а злоумышленник имеет возможность зарегистрировать свой ресурс на вашем домене 2-го уровня, то устанавливайте cookie на свой домен явно.


  • Нюансы зависят от реализации

Encrypted Token


Так же как и Double Submit, является Stateless подходом. Основная — если вы зашифруете надежным алгоритмом какие-то данные и передадите их клиенту, то клиент не сможет их подделать, не зная ключа. Этот подход не требует использования cookie. Токен передаётся клиенту только в параметрах ответа.


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


Суть:

  1. При запросе от клиента на стороне сервера генерируется токен.


    Генерация токена состоит в зашифровке фактов, необходимых для валидации токена в дальнейшем.


    Минимально необходимые факты — это идентификатор пользователя и timestamp. В ответе токен возвращается в одном из параметров ответа (В header или внутри HTML).


  2. В последующих запросах клиент обязан предоставлять полученный ранее токен.


  3. При получения запроса небезопасным методом (POST, PUT, DELETE, PATCH) сервер обязан валидировать токен, полученный от клиента.


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


    Если расшифровать не удалось либо факты не совпадают, считается, что запрос подвергся CSRF-Атаке.



На выходе имеем:

  • Stateless CSRF защиту


  • Нет необходимости хранить данные в cookie


  • Нет нюансов с поддоменами.

О реализации


  • Давайте генерировать новый токен на каждый запрос, не важно, каким HTTP-методом и с какой целью этот запрос сделан.


    Таким образом мы получаем токен, который меняется постоянно.


    Конечно, возникает вопрос организации multi-tab работы.


    Синхронизация токенов между табами может быть реализована с использованием localStorage и его StorageEvent


  • Ограничиваем время жизни cookie, которое содержит токен, разумным значением. Например 30 минут.


  • Делаем cookie недоступной из JS (ставим HTTPOnly=true)


  • Используем TLS для предотвращения MITM


    При этом отправляем cookie только по HTTPS (ставим Secure=true)


  • Размер токена не менее 32 байт.


  • Генерируем токен криптографически стойким генератором псевдослучайных чисел.


    Для этого можно использовать системные функции:


    Linux => getrandom(2) если возможно, /dev/urandom иначе
    OpenBSD => getentropy(2)
    На других Unix-like системах => /dev/urandom
    Windows => CryptGenRandom API


Что еще нужно знать?


Токены — обязательная защита от CSRF.


  • Проверяйте, но не полагайтесь только на X-Requested-With: XMLHttpRequest


  • Проверяйте, но не полагайтесь только на заголовки: Host, Origin, Referer


  • Не передавайте токены в URL


  • Защищайте все запросы.

Same Site


Сейчас идет работа над спецификацией атрибута "Same-Site" у cookies (последняя версия на момент написания статьи).


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


Браузер Chrome уже сейчас поддерживает эту возможность.


Чуть больше информации о том, как и почему доступно на Stack Exchange.

Поделиться с друзьями
-->

Комментарии (49)


  1. dvmedvedev
    29.12.2016 15:47

    Шифрация/дешифрация ресурсоемкие операции. Гораздо проще использовать хэширование. В простейшем случае у нас будет код наподобие:

    $time = time();
    $token = md5(SECRET_KEY . $userData . $time) . ':' . $time;
    

    Как проверить такой токен, думаю, понятно.


    1. andreymal
      29.12.2016 15:50
      +1

      Боюсь, так можно достаточно быстро подобрать SECRET_KEY


      1. dvmedvedev
        29.12.2016 15:53

        Зря боитесь:

        $time = time();
        $secretKey = "33Sht?U<up-~f=>@xy8sah3uwA?T(<E8gE92vh5]rs4M3%-EbX,u9SqCk6jQ)}-J";
        // $userData тоже может содержать неизвестные злоумышленнику данные. 
        // Вы свой числовой id на хабре знаете?
        $token = md5($secretKey . $userData . $time) . ':' . $time;
        


        1. andreymal
          29.12.2016 15:59

          (да, мой числовой id 260599 :)
          Хорошо, я забыл, что SECRET_KEY не пароль и может быть длинным)


          1. dvmedvedev
            29.12.2016 16:09
            +1

            Указанный способ работы с токенами часто применяется для генерации временной ссылки на CDN, например.

            (да, мой числовой id 260599 :)

            спасибо)


        1. inwady
          29.12.2016 19:04

          Лучше не стоит писать подобный код. Здесь может помочь злоумышленнику "Атака удлинением сообщения". То есть ваш secret_key просто «проигнорируется» при проверке.


          1. dvmedvedev
            29.12.2016 19:32

            Я, собственно, не для прода код писал, лишь обозначить алгоритм, но ваш комментарий, действительно, полезен в данном случае.
            Flacker ниже уже указал на необходимость использования HMAC в подобных решениях.


    1. xRay
      29.12.2016 16:28
      -1

      Про md5

      В связи с быстрой природой хэширующего алгоритма не рекомендуется использовать эту функцию для обеспечения безопасности паролей.
      http://php.net/manual/ru/function.md5.php

      Вместо md5 рекомендуется использовать password_hash


      1. xRay
        30.12.2016 16:08

        Любопытно за что минус-то?


    1. Flaker
      29.12.2016 16:44

      Да, всё верно. Поэтому, надо четко понимать, где и что использовать. В случае подхода Encrypted Token шифрование обязательно. В остальных случаях нужно просто подписать данные. Для этого, может быть использован механизм HMAC.

      Используемая хеш-функция — дело личное. От её выбора будет зависеть скорость генерации токена.
      Конечно, при прочих равных, подписать быстрее, чем зашифровать.


  1. ultrinfaern
    29.12.2016 16:59
    -1

    Каждый раз читая такие статьи я все время прокручиваю у себя такой сценарий.
    Предположим что я захожу на сайт good.com в результате которого мне в куки записывается номер сессии и csrf токен. Дальше я в новой вкладке открываю bad.com, который делает запрос к good.com и так как у меня в куках есть валидные номер сессии и валидный csrf, то запрос успешно выполняется.
    Вопрос — где же тут защита?


    1. Flaker
      29.12.2016 17:14
      +3

      Еще раз уточню: защищать необходимо небезопасные методы, к которым относятся: POST, PUT, DELETE, PATCH.

      В целом, всё сводится к тому, что, кроме данных в сookies, необходимо послать данные о текущем CSRF-токене вторым путём. Это может быть post-data (для форм) или доп. заголовок.

      Если вы говорите о подходе Synchronizer Tokens, то тут токен-эталон просто храним на сервере (в данных сессии), а токен-валидатор посылаем клиенту например в header или, если шаблон генерируется на сервере, прямо в html'e.

      Т.е. при таком подходе явно хранить в cookie CSRF-токен не нужно.

      Таким образом, в запросе с другого ресурса будет ID сессии, но не будет самого CSRF-токена, так как он не должен приходить внутри cookies.

      P.S. Если не очень понятна идея, то говорите, не стесняйтесь, я попытаюсь написать подробнее.


      1. ultrinfaern
        29.12.2016 19:52

        Если страница вставлена в виде фрейма, или вообще просто вогнана в какой-то див с помощью ajax запроса (предположим что так позволит сайт сделать), и она генерировалась на сервере то bad.com получит валидную форму с валидным встроенным в html csrf токеном, и соответственно прекрасно сможет сделать POST.

        Поэтому я и не понимаю данный вид защиты с помощью csrf токена хотелось бы узнать, где я ошибаюсь.

        Пока я вижу только один метод — https плюс фиксация идентификатора сессии на идентификаторе самого https соединения. Тогда у нас на каждую закладку будет своя сессия и соответственно новая сессия в новом окне, плюс не нужно возиться с токенами.


        1. mayorovp
          30.12.2016 10:11

          Существует такая штука как Same Origin Policy. bad.com может сделать любой запрос к good.com — но он не сможет получить результат запроса.


      1. ultrinfaern
        29.12.2016 20:40
        -1

        Перечитав все, я наконец-то понял. Если на сайте bad.com вставлена СТАТИЧЕСКАЯ ссылка на good.com тогда csrf прекрасно сработает. Но извините, веб 1.0 умер лет 10 назад, и сейчас нет ни одного сайта без JavaScript. А если есть возможность сделать несколько запросов (форму и отсылку ее например) то csrf никак вам не поможет. Получается это какая-то защита от юных хакеров, которая еще и требует кучи дополнительного кода на стороне сервера, чтобы втыкивать этот токен во все места. Вывод — cors + https вполне достаточно.


        1. amakhrov
          30.12.2016 06:20
          -2

          CORS ограничивает только ajax-запросы, чего явно недостаточно. На сайте bad.com злоумышленник вставит <form action='good.com' method='post'> и инициирует отправку (сабмит) формы — CORS тут не поможет.


    1. herrjemand
      30.12.2016 02:49

      Автор пропустил Same-Origin-Policy. Если SOP настроен не правильно, на пример "Access-Control-Allow-Origin: *
      " то любая CSRF защита будет в пустую. По-умолчанию SOP только Same-Origin, так что если вы не настраиваете хедер в ручную, проблем у вас не должно быть.


  1. Razaz
    29.12.2016 17:18

    А если Ajax + CORS?


  1. Blumfontein
    29.12.2016 17:31

    >> Токены — обязательная защита от CSRF.

    Слишком категорично. Есть методы попроще.

    1) Можно не использовать POST. Атаковать PUT, DELETE, PATCH невозможно без специальных условий.

    2) «JS приложения обычно присылают XHR запросы с header (X-CSRF-Token), содержащим токен.». Если вы все равно требуете кастомный хедер от клиента, не проще ли просто принимать саму сессию в кастомном хедере, а не куки?


    1. Dreyk
      29.12.2016 17:36

      в чем проблема атаковать put, delete и patch? их точно так же можно послать используя js в браузере


      1. Blumfontein
        29.12.2016 17:38

        CORS не позволит


        1. Dreyk
          29.12.2016 17:41

          а POST позволит?


          ну и jsonp обходит cors же. нам же не ответ получить надо, а главное, чтобы действие исполнилось


          1. Blumfontein
            29.12.2016 17:46

            POST позволит. jsonp обходит, но это и есть «специальные условия».


    1. Razaz
      29.12.2016 17:57

      Сессия в куки должна быть httponly + secure. В скрипте не получить. Ну это ИМХО :)


      1. Blumfontein
        03.01.2017 12:53

        >> Сессия в куки должна быть httponly.

        А чего бояться? XSS? Ну если есть XSS, то у вас и так все плохо.


        1. Razaz
          03.01.2017 13:41
          +1

          А чтоб не лазали куда не надо ;) Скрипт не должен иметь доступа к тому, чему не должен. В определенной ситуации может быть дополнительным фактором защиты.


        1. mayorovp
          03.01.2017 17:28

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


  1. david_mz
    29.12.2016 19:52

    “Проверяйте, но не полагайтесь только на X-Requested-With: XMLHttpRequest” — почему, если его можно поставить только через явное разрешение в CORS?


    1. amakhrov
      30.12.2016 06:29

      Присоединяюсь. Я довольно часто использую только проверку на X-Requested-With — дешево, сердито.
      Чем это грозит?


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


    1. Flaker
      30.12.2016 13:54

      На самом деле, OWASP подтверждает, что такая защита имеет место быть (раздел: Protecting REST Services: Use of Custom Request Headers)

      Но есть уточнение, что такой вид защиты был уязвим с использованием Flash. И прилагается ссылка на репорт об уязвимости на Vimeo: https://hackerone.com/reports/44146

      Таким образом, по версии OWASP, для обеспечения необходимого уровня защиты необходимо дополнительно проверять заголовки Origin и Referer (впрочем, как и при любом другом методе).

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

      Другой вопрос, а у всех ли пользователей софт действительно новейший… (http://caniuse.com/#search=CORS)

      В интернетах можно увидеть разные мысли на тему такой защиты.

      А по поводу CORS, нужно понимать, что сам по себе он не предоставляет защиты от CSRF. Вот, например, ответ на SO о том, почему CORS не совсем верно интерпретируют: http://security.stackexchange.com/a/97938


  1. lehha
    29.12.2016 23:20

    Если использовать Double Submit Cookie, то менять куку в другой вкладке — опасно. Запросы из первой вкладки сломаются и клиенты начнут ругаться. А не менять или менять «раз в час» — как и без защиты толку не добавит. Или я ошибаюсь?


    1. Flaker
      30.12.2016 14:32

      Да, именно так. Вы всё верно поняли.

      В статье предложил, как можно синхронизировать:

      Синхронизация токенов между табами может быть реализована с использованием localStorage и его StorageEvent


  1. mayorovp
    30.12.2016 10:20

    Если вы пишете свой Web-Сервис в соотвествии со стандартом RFC7231, то ...

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


    В таком случае можно забыть про CSRF, XSS и другие страшные слова, а также смело прописать Access-Control-Allow-Origin: *. Если у злоумышленника нет токена — он ничего не сможет сделать. А если у него есть токен — значит, он никакой не злоумышленник, а валидный юзер.


    1. Loki3000
      30.12.2016 11:52

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

      А идентификатор сессии разве не является подобным токеном?


      1. Razaz
        30.12.2016 11:59

        Полный токен содержит набор данных по которым можно провести аутентификацию и авторизацию. В id сессии этого нет и вам придется разыменовывать этот id для получения информации. Если у вас не монолитное приложение — это отдельный геморрой :)
        Хотя по тому же OpenID Connect можно использовать ReferenceTokens + интроспекцию.


      1. mayorovp
        30.12.2016 12:00

        Идентификатор сессии при передаче через куки подвержен CSRF-атаке: злоумышленнику не обязательно знать его чтобы отправить, браузер сам его подставляет.


        Если не завязываться на магию кук в браузере — то и вектор атаки пропадает.


        1. Loki3000
          30.12.2016 12:09

          Ну если вспомнить недавнее прошлое, то идентификатор сессии так же подклеивался к GET запросам. И все понимали какая это дыра в безопасности. Если я правильно понимаю, то вы предлагаете тоже самое делать с токеном?


          1. Razaz
            30.12.2016 12:12

            Тут речь о простом Bearer токене в заголовке. Http verb без разницы.


            1. Loki3000
              30.12.2016 12:18

              А как быть если на странице используется какой-нибудь, прости господи, флеш? Что-то я сомневаюсь что он добровольно отправит правильные заголовки.


              1. Razaz
                30.12.2016 12:23

                How can i send custom headers with URLRequest. Хотя там проблемы с Get вроде. Но тут уж только вариант — выносить его, либо делать отдельные эндпоинты, чтоб другие не страдали :)


                1. Loki3000
                  30.12.2016 12:36

                  Это если есть исходники flash-приложения. Если же нет, то все равно придется передавать токен через параметры. Значит в тот или иной момент он будет присутствовать внутри html страницы.
                  Вариант с заголовками хороший, но не универсальный. Понятно что эта неуниверсальность касается процентов 5 случаев, но нет гарантии что завтра заказчик не захочет именно эту экзотику у себя на сайте.


                  1. Razaz
                    30.12.2016 13:06

                    К сожалению не весь легаси может в токены :( Остается надеяться на более агрессивную политику вендоров браузеров.


          1. mayorovp
            30.12.2016 12:12

            Дырой в безопасности это становится когда идентификатор сессии оказывается в адресной строке. Для AJAX-запросов такой проблемы нет.


    1. Razaz
      30.12.2016 11:54

      С XSS спорно. Если еще CSP докинуть сверху — то вполне.


      1. mayorovp
        30.12.2016 11:58

        О каком CSP в веб-сервисе речь? CSP применяется в html, а веб-сервис должен работать только с json или xml.


        1. Razaz
          30.12.2016 12:01

          А о каком CSRF тут обсуждение идет если это просто сервис? :) Вроде как обсуждается связка — страница в браузере + REST сервис.


          1. mayorovp
            30.12.2016 12:04

            А о каком CSRF тут обсуждение идет если это просто сервис?

            Вот именно это я у вас и спросил!


            Вроде как обсуждается связка — страница в браузере + REST сервис.

            В случае когда страница на сервере — статическая и вся динамика реализуется на стороне браузера, CSP при прямых руках не требуется (но и не помешает).


            1. Razaz
              30.12.2016 12:09

              Обсуждение CSRF сразу подразумевает наличие веб страницы. Иначе смысл всего этого топика :)))

              Если у вас только статика, если вы контролируете целостность ресурсов, тогда нет вопросов.


  1. Loki3000
    30.12.2016 12:25

    Уникальный токен для каждой операции
    Действует единожды

    Я правильно понимаю, stateless решения не удовлетворяют этим требованиям?
    И насколько вообще эти требования критичны?