Что такое csrf токен
Атака CSRF
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Нельзя говорить про AJAX и не упомянуть про важнейшую деталь его реализации – защиту от CSRF-атак.
CSRF (Cross-Site Request Forgery, также XSRF) – опаснейшая атака, которая приводит к тому, что хакер может выполнить на неподготовленном сайте массу различных действий от имени других, зарегистрированных посетителей.
Какие это действия – отправка ли сообщений, перевод денег со счёта на счёт или смена паролей – зависят от сайта, но в любом случае эта атака входит в образовательный минимум веб-разработчика.
Злая форма
«Классический» сценарий атаки таков:
Вася попал на «злую страницу», например хакер пригласил его сделать это письмом или как-то иначе.
На злой странице находится форма такого вида:
Сайт mail.com проверяет куки, видит, что посетитель авторизован и обрабатывает форму. В данном примере форма предполагает посылку сообщения.
Итог атаки – Вася, зайдя на злую страницу, ненароком отправил письмо от своего имени. Содержимое письма сформировано хакером.
Защита
В примере выше атака использовала слабое звено авторизации.
Куки позволяют сайту mail.com проверить, что пришёл именно Вася, но ничего не говорят про данные, которые он отправляет.
Иначе говоря, куки не гарантируют, что форму создал именно Вася. Они только удостоверяют личность, но не данные.
Типичный способ защиты сайтов – это «секретный ключ» ( secret ), специальное значение, которое генерируется случайным образом при авторизации и сохраняется в сессии посетителя. Его знает только сервер, посетителю мы его даже не будем показывать.
Разумеется, для разных посетителей secret будет разным.
Формула вычисления токена:
Далее, токен добавляется в качестве скрытого поля к каждой форме, генерируемой на сервере.
Такой токен также называют «подписью» формы, которая удостоверяет, что форма сгенерирована именно на сервере.
Эта подпись говорит о том, что автор формы – сервер, но ничего не гарантирует относительно её содержания.
Есть ситуации, когда мы хотим быть уверены, что некоторые из полей формы посетитель не изменил самовольно. Тогда мы можем включить в MD5 для формулы токена эти поля, например:
Токен и AJAX
Теперь перейдём к AJAX-запросам.
Что если посылка сообщений в нашем интерфейсе реализуется через XMLHttpRequest?
Как и в случае с формой, мы должны «подписать» запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не «злой страницей».
Здесь возможны варианты, самый простой – это дополнительная кука.
Код, осуществляющий XMLHttpRequest, получает куку и ставит заголовок X-CSRF-TOKEN с ней:
Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.
Защита действует потому, что прочитать куку может только JavaScript с того же домена. «Злая страница» не сможет «переложить» куку в заголовок.
Если нужно сделать не XMLHttpRequest, а, к примеру, динамически сгенерировать форму из JavaScript – она также подписывается аналогичным образом, скрытое поле или дополнительный URL-параметр генерируется по куке.
Итого
CSRF-атака – это когда «злая страница» отправляет форму или запрос на сайт, где посетитель, предположительно, залогинен.
Если сайт проверяет только куки, то он такую форму принимает. А делать это не следует, так как её сгенерировал злой хакер.
Для подписи XMLHttpRequest токен дополнительно записывается в куку. Тогда JavaScript с домена mail.com сможет прочитать её и добавить в заголовок, а сервер – проверить, что заголовок есть и содержит корректный токен.
Динамически сгенерированные формы подписываются аналогично: токен из куки добавляется как URL-параметр или дополнительное поле.
Шпаргалки по безопасности: CSRF
Не смотря на то, что в последнем публиковавшемся перечне уязвимостей OWASP Top 10 2017 CSRF атаки отнесены к разряду “Удалены, но не забыты”, мы решили, что не будет лишним еще раз напомнить о том, как защититься от CSRF атак, опираясь на те же правила, предоставляемые OWASP.
Использование CSRF токена
Использование токена (как stateless, так и statefull методов) является первичным и самым популярным способом защиты. Токен должен быть уникален для каждой пользовательской сессии, сгенерирован криптографически стойким генератором псевдослучайных чисел. OWASP при этом рекомендует для шифрования использовать алгоритм AES256-GCM и SHA256/512 при использовании HMAC.
Существует несколько подходов к работе с токенами: Synchronizer Token, Encryption based Token Pattern, HMAC Based Token
При использовании подхода Synchronizer Token (statefull метод) подразумевается отправка токена при каждом запросе, подразумевающем какие-то изменения на стороне сервера. Если токен не является валидным, то сервер отклоняет запрос.
При отправке запроса на сервер рекомендуется добавлять токен в параметры запроса, нежели в заголовок. Если все же вставляете токен в заголовок запроса, то убедитесь, что он не логируется на сервере. Полученный токен можно сохранять на стороне клиента в скрытом поле:
OWASP рекомендует хранить токен в заголовках, объясняя это тем, что даже если токен раскрыт или у него истек срок действия, то атакующий все равно не сможет подделать запрос, благодаря особенностям работы браузеров.
Также для повышения уровня безопасности предложенного метода, предлагается генерировать случайное имя параметра токена и/или сам токен для каждого запроса. При таком подходе время, в течение которого злоумышленник может воспользоваться украденным токеном, минимально. Однако это может привести к проблемам с юзабилити. Например, нажатие на кнопку “Назад” может привести к отправке на сервер невалидного токена, который содержался на предыдущей странице.
Отправка токена, используя GET запрос, не рекомендуется, так как при таком подходе токен может быть раскрыт: в истории браузера, лог файлах, referrer заголовках.
Encryption based Token
Данный подход является stateless, так как использует шифрование/дешифрование для валидации токена, а значит не требует хранения токена на стороне сервера.
Сервер генерирует токен, состоящий из идентификатора сессии и timestamp (для предотвращения атаки повторного воспроизведения). Для шифрования рекомендуется использовать алгоритм шифрования AES256 в режиме блочного шифрования GSM/GSM-SIV. Использование режима ECB строго не рекомендуется. Зашифрованный сервером токен возвращается клиенту так же, как и в случае с «Synchronizer Token» в скрытом поле формы или же в заголовке/параметре ответа. При получении токена сервер должен расшифровать его, после чего сверить идентификатор сессии, а также сверить timestamp с текущим временем и убедиться, что оно не превышает установленного времени жизни токена.
Если сверка идентификатора сессии проходит успешно, а timesmap – нет, то запрос может считаться валидным. Во всех остальных случаях рекомендуется отклонять запрос и регистрировать его, чтобы в дальнейшем понять как реагировать на подобные запросы.
HMAC Based Token
Также не требует хранения токена, принцип работы похож на Encryption based Token, за исключением того, что вместо шифрования токена используется HMAC (hash-based message authentication code) функция для генерации токена (рекомендуется использовать SHA256 или более сильный алгоритм). При этом токен представляет из себя результат HMAC функции от идентификатора сессии пользователя+ timestamp.
Автоматизация работы с токенами
Главная проблема в противодействии CSRF атакам заключается в том, что разработчики часто просто забывают добавлять функционал обеспечивающий работу с токенами. Чтобы избежать подобных проблем стоит автоматизировать данный процесс:
• напишите обертку, автоматически добавляющую токен к запросам через тег form или при использовании ajax. Например, Spring Security использует подобный подход каждый раз, когда используется тег
• напишите хук, который перехватывает трафик и добавляет токены ко всем уязвимым ресурсам. Так как довольно трудно проанализировать, какой запрос выполняет изменение состояния, нуждаясь в токене, рекомендуется включать токены во все POST ответы, но стоит при этом учитывать затраты на производительность
• автоматически добавлять токен при рендере страницы. Данный подход используется CSRF Guard: токены добавляются ко всем href и src атрибутам, скрытым полям и во все формы
Прежде чем пытаться написать собственную систему автоматической генерации токена, рекомендуется уточнить, имеет ли используемый вами фреймворк возможность по умолчанию обеспечить защиту от CSRF атак. Например, тот же фреймворк Django реализует защиту от CSRF.
Воспользовавшись CSRF в форме входа, злоумышленник может войти в систему,
под видом жертвы. С такой уязвимостью сталкивались такие гиганты как PayPal и Google.
Бороться с CSRF в форме входа можно путем создания пре-сессий, которые создаются до того, как пользователь прошел аутентификацию, и включением токенов в форму входа.
SameSite Cookie – атрибут, описанный в RFC6265bis, цель которого – противодействие CSRF атакам. Работает это следующим образом. Один из способов защиты – проверка заголовков origin и referer, по которым можно понять откуда пришел запрос, но такой подход требует внедрения механизма проверки. Используя атрибут SameSite, мы ограничиваем отправку куки с запросом с посторонних ресурсов. У данного атрибута есть несколько возможных значений: Strict, Lax и None.
Использование значения strict подразумевает, что браузер не будет отправлять куки с любых источников, не совпадающих с доменным именем текущего ресурса.
Значение lax дает возможность не блокировать куки с внешних ресурсов, переход с которых был осуществлен безопасным способом – по протоколу HTTPS. Lax обеспечивает баланс между удобством использования ресурса для пользователей и безопасностью.
Выставить атрибут довольно просто:
На момент написания статьи поддержка атрибута браузерами выглядит вот так:
Важно помнить, что данный атрибут стоит использовать как дополнительную меру защиты, а не как способ обойтись без использования CSRF токена.
Как уже упоминалось выше, один методов защиты – проверка значений referrer и origin заголовка запроса.
Суть данной проверки сводится к проверке значений заголовков на стороне сервера. Если они совпадают с ресурсом, то запрос считается корректным, в противном случае отклоняется. Если заголовок Origin отсутствует, то нужно убедиться, что значение Referrer соответствует текущему ресурсу. OWASP рекомендует отклонять запросы, которые не содержат Origin или Referrer заголовки. Также можно логгировать все подобные запросы, чтобы после проанализировать их и принять решение о том как с ними поступать.
Однако все усложняется, если ваше приложение находится за прокси-сервером, поскольку URL в заголовке при этом будет отличаться. В таком случае есть несколько вариантов:
• сконфигурируйте ваше приложение таким образом, чтобы всегда знать происхождение запроса. Проблема подобного подхода заключается в установке правильного значения, если ваше приложение развернуто на нескольких окружениях (например, dev, QA, production), что приводит проблеме поддержки
• используйте заголовок Host. Данный заголовок даст возможность определять источник запроса независимо от окружения
• используйте заголовок X-Forwarded-Host, цель которого — хранение исходных заголовков, полученных прокси-сервером
Все описанные методы работают только тогда, когда присутствуют заголовки origin и referer. Но встречаются случаи, когда данные заголовки отсутствуют. Вот ряд случаев, когда эти заголовки не включаются в запрос:
• IE 11 не включается заголовок Origin для доверенных сайтов. Остается полагаться только на заголовок Referer
• в случае перенаправления Origin не включен в запрос, так как считается, что он может содержать конфиденциальную информацию, которую не следует отправлять другому источнику
• заголовок Origin включен для всех межсайтовых запросов, но большинство браузеров добавляют его только для POST/DELETE/PUT запросов
Как правило, незначительное количество трафика попадает под описанные категории, но часто не хочется терять даже эту малую часть пользователей, поэтому принято считать валидным запрос со значением null для origin/referrer или со значением, соответствующим списку доверительных доменов.
Double Submit Cookie
Данный подход довольно прост в реализации и не требует хранения токена на стороне сервера (stateless). Суть метода заключается в отправке пользователем токена в параметре запроса и в куках. Каждый запрос, требующий изменения состояния, сверяем значение токена в куках и в запросе. Если сверка идентификатора сессии проходит успешно, а timesmap – нет, то запрос может считаться валидным
Методы защиты от CSRF-атаки
Что такое CSRF атака?
Ознакомиться с самой идеей атаки CSRF можно на классических ресурсах:
Выдержка из ответа на SO:
Как от нее защититься?
Эффективным и общепринятым на сегодня способом защиты от CSRF-Атаки является токен. Под токеном имеется в виду случайный набор байт, который сервер передает клиенту, а клиент возвращает серверу.
Защита сводится к проверке токена, который сгенерировал сервер, и токена, который прислал пользователь.
А что, собственно, защищать?
На Habrahabr опубликована статья от Yandex, в которой описано, почему писать свои сервисы нужно, руководствуясь стандартом.
Требования к токену:
На первом MeetUp’е PDUG Тимур Юнусов (руководитель отдела безопасности банковских
систем Positive Technologies) рассказывал, почему именно такие требования предъявляются к CSRF-Токену и чем грозит пренебрежение ими.
Требования к Web-Сервису и окружению:
Отсутствие XSS уязвимостей
Внедренный злоумышленником скрипт имеет возможность отправлять запрос к серверу от имени пользователя и читать его без каких-либо препятствий.
Таким образом, XSS уязвимости могут быть использованы для получения текущего токена.
Отсутствие malware на машине клиента
Если злоумышленник имеет возможность запускать софт на машине клиента, то он может получить любые данные, имеющиеся в браузере.
Методы защиты
Существует 3 метода использования токенов для защиты web-сервисов от CSRF атак:
Synchronizer Tokens
Простой подход, использующийся повсеместно. Требует хранения токена на стороне сервера.
При старте сессии на стороне сервера генерируется токен.
Токен кладется в хранилище данных сессии (т.е. сохраняется на стороне сервера для последующей проверки)
В ответ на запрос (который стартовал сессию) клиенту возвращается токен.
Если рендеринг происходит на сервере, то токен может возвращаться внутри HTML, как, например, одно из полей формы, или внутри тега.
В случае, если ответ возвращается для JS приложения, токен можно передавать в header (часто для этого используют X-CSRF-Token )
При последующих запросах клиент обязан передать токен серверу для проверки.
При рендере контента сервером токен принято возвращать внутри POST данных формы.
JS приложения обычно присылают XHR запросы с header ( X-CSRF-Token ), содержащим токен.
Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.
На выходе имеем:
Защита от CSRF на хорошем уровне
Токен обновляется только при пересоздании сессии, а это происходит, когда сессия истекает
Во время жизни одной сессии все действия будут проверяться по одному токену.
Если произойдет утечка токена, то злоумышленник сможет выполнить CSRF-Атаку на любой запрос и в течение долгого срока. А это не есть хорошо.
Бесплатная поддержка multi-tab в браузере.
Токен не инвалидируется после выполнения запроса, что позволяет разработчику не заботиться о синхронизации токена в разных табах браузера, так как токен всегда один.
Double Submit Cookie
Этот подход не требует хранения данных на стороне сервера, а значит, является Stateless. Используется, если вы хотите уметь быстро и качественно масштабировать свой Web-сервис горизонтально.
Идея в том, чтобы отдать токен клиенту двумя методами: в куках и в одном из параметров ответа (header или внутри HTML).
При запросе от клиента на стороне сервера генерируется токен. В ответе токен возвращается в cookie (например, X-CSRF-Token ) и в одном из параметров ответа (в header или внутри HTML).
В последующих запросах клиент обязан предоставлять оба полученных ранее токена. Один как cookie, другой либо как header, либо внутри POST данных формы.
Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.
На выходе имеем:
Stateless CSRF защиту.
Таким образом, если ваш сервис доступен на домене 3-го уровня, а злоумышленник имеет возможность зарегистрировать свой ресурс на вашем домене 2-го уровня, то устанавливайте cookie на свой домен явно.
Encrypted Token
Так же как и Double Submit, является Stateless подходом. Основная — если вы зашифруете надежным алгоритмом какие-то данные и передадите их клиенту, то клиент не сможет их подделать, не зная ключа. Этот подход не требует использования cookie. Токен передаётся клиенту только в параметрах ответа.
В данном подходе токеном являются факты, зашифрованные ключом. Минимально необходимые факты — это идентификатор пользователя и timestamp времени генерации токена. Ключ не должен быть известен клиенту.
При запросе от клиента на стороне сервера генерируется токен.
Генерация токена состоит в зашифровке фактов, необходимых для валидации токена в дальнейшем.
Минимально необходимые факты — это идентификатор пользователя и timestamp. В ответе токен возвращается в одном из параметров ответа (В header или внутри HTML).
В последующих запросах клиент обязан предоставлять полученный ранее токен.
Валидация токена заключается в его расшифровке и сравнения фактов, полученных после расшифровки, с реальными. (Проверка timestamp необходима для ограничения времени жизни токена)
Если расшифровать не удалось либо факты не совпадают, считается, что запрос подвергся CSRF-Атаке.
На выходе имеем:
Stateless CSRF защиту
Нет необходимости хранить данные в cookie
О реализации
Давайте генерировать новый токен на каждый запрос, не важно, каким HTTP-методом и с какой целью этот запрос сделан.
Таким образом мы получаем токен, который меняется постоянно.
Конечно, возникает вопрос организации multi-tab работы.
Синхронизация токенов между табами может быть реализована с использованием localStorage и его StorageEvent
Ограничиваем время жизни cookie, которое содержит токен, разумным значением. Например 30 минут.
Делаем cookie недоступной из JS (ставим HTTPOnly=true)
Используем TLS для предотвращения MITM
При этом отправляем cookie только по HTTPS (ставим Secure=true)
Размер токена не менее 32 байт.
Генерируем токен криптографически стойким генератором псевдослучайных чисел.
Для этого можно использовать системные функции:
Что еще нужно знать?
Токены — обязательная защита от CSRF.
Проверяйте, но не полагайтесь только на X-Requested-With: XMLHttpRequest
Не передавайте токены в URL
Same Site
Сейчас идет работа над спецификацией атрибута «Same-Site» у cookies (последняя версия на момент написания статьи).
Такой атрибут даст возможность разработчикам явно указывать, что cookie не нужно передавать, если запрос идет с сайта, отличного от того, на котором cookie была установлена. А, значит, у нас появится возможность защищать ресурсы от CSRF без использования дополнительных инструментов.
Браузер Chrome уже сейчас поддерживает эту возможность.
Чуть больше информации о том, как и почему доступно на Stack Exchange.
CSRF-токен
В этой статье рассмотрим, что такое Сross-Site Request Forgery и как включить в Spring Boot приложение CSRF-токен — защиту от этого мошенничества. Назвать этот токен можно было бы «анти-CSRF-токен».
Same Origin Policy и CSRF (cross-site request forgery)
Обычно запросы, сделанные в браузере с одного домена на другой, не проходят, поскольку браузер придерживается Same Origin Policy. Это политика безопасности, защищающая одни сайты от других.
Например, пусть пользователь случайно заходит на домен evil.com (мошеннический сайт) и щелкает там яркую кнопку, которая выполняет либо PUT-запрос, либо DELETE-запрос на bank.com. И так случайно получилось, что этот пользователь зарегистрирован на bank.com и в браузере хранятся куки к нему. Благодаря политике безопасности браузера, ничего плохого не случится. Потому что браузер сначала вышлет так называемый «preflight», то есть предварительный OPTIONS-запрос на bank.com с заголовком
и затем отправит за ним настоящий PUT/DELETE-запрос, но только в том случае, если в ответе пришло разрешение на отправку запросов от evil.com. В противном случае в метод PUT/DELETE банковского сайта мы даже не попадем.
Но есть запросы, которые отправляются сразу, без предварительного запроса, так называемые simple requests. Это GET, HEAD, POST с определенным Content-Type. В частности, POST-запросы с формы. Такой запрос сразу идет в контроллер. А учитывая, что куки браузер отправляет автоматически, запрос попадет в защищенный контроллер и выполнит действие на банковском сайте. Хотя ответ получить и распарсить нельзя, действие будет выполнено.
Ниже рассмотрим, как это происходит. В примере одно приложение на одном домене делает POST-запрос на другой домен, где работает второе приложение. Рассмотрим, как защититься от таких запросов с помощью CSRF-токена.
Пример жульничества
Создадим два приложения на Spring Boot:
Поскольку в примере мы будем делать запрос на «другой домен», пропишем для localhost:8080 новое имя в файле hosts:
А именно, добавим в файл hosts строку:
Теперь к приложению-мишени будем обращаться по адресу http://bank-server:8080/..вместо http://localhost:8080..
Приложение-мишень
Итак, пусть в нашем приложении есть контроллер и форма, с которой что-то добавляется:
Добавление
Форма на Thymeleaf выглядит так:
При этом адрес http://bank-server:8080/add защищен, доступ к нему возможен только благодаря куки JSESSIONID после входа с помощью формы логина.
В приложении задан единственный in-memory пользователь с именем user и паролем user:
Выше прописано, что все запросы доступны только аутентифицированным пользователям (в том числе наша форма — ее получение по адресу /add методом GET и отправка методом POST). Форма находится в шаблоне add.html
Проверку CSRF-токена мы отключили выше отдельной строкой:
Это сделано для того, чтобы продемонстрировать атаку с помощью второго приложения ниже. А затем включить CSRF-токен обратно. (По умолчанию он и так включен, просто нужно не забывать добавлять его и на форму, что будет в показано самом конце).
Мошенническое приложение
Второе приложение совсем простое, оно состоит из одного view с кнопкой атаки POST (полный код тут):
Сайт жуликов
Кнопка отправляет форму со скрытыми полями: и text=«document». Если в браузере мы залогинены в приложении http://bank-server:8080 и откроем мошеннический сайт localhost:8081 с вышеприведенной кнопкой и щелкнем ее, то сохраненный куки JSESSIONID отправляется тоже, и мы попадаем в защищенный метод add() контроллера DocumentController банковского сайта. Документ добавляется.
Защита с помощью CSRF-токена
Чтобы защититься от таких запросов, нужно включить CSRF-токен, то есть убрать строку отключения, которую мы добавили выше:
Теперь когда пользователь логинится на сайт http://bank-server:8080, ему выделяется специальный CSRF-токен. Он хранится в сессии и должен отправляться как скрытое поле со всех форм (также он должен прилагаться в XMLHttpRequest-запросах PUT, DELETE, POST — это для JavaScript). Выше мы показали только одну атаку, но при определенных настройках Spring MVC в определенных браузерах возможны и более сложные атаки. И хотя браузеры становятся все более безопасными, как и сам Spring MVC, все равно Spring Security имеет вот такое требование и для PUT, и для DELETE-запросов, хотя для них существуют «preflight» запросы от браузера. Но эти настройки CSRF-токена можно и поменять — убрать некоторые методы или некоторые url — сделать так, чтобы для них не требовался токен.
Итак, токен в POST-запросах сейчас требуется. Если оставить все как есть, то наше собственное банковское приложение не заработает — при попытке отправить форму получим 403. Надо сделать так, чтобы выданный CSRF-токен отправлялся. Для этого добавим его как скрытое поле на форму в наш Thymeleaf-шаблон:
Все, теперь и наше приложение работает, и с чужого сайта форму отправить нельзя, потому что мошенники не знают CSRF-токен. В отличие от JSESSIONID, он не хранится в куки браузера и не отправляется автоматически при запросах на банковский сайт.
Итоги
Исходный код обоих приложений есть на GitHub.