Почему так часто помещать токены CSRF в файлы cookie?

Я пытаюсь понять всю проблему с CSRF и соответствующие способы ее предотвращения. (Ресурсы, которые я читал, понимал и соглашался с: OWASP CSRF Предупреждение CHeat Sheet, Вопросы о CSRF.)

Как я понимаю, уязвимость вокруг CSRF вводится по предположению, что (с точки зрения веб-сервера) допустимый куки файл сеанса в входящем HTTP-запросе отражает пожелания аутентифицированного пользователя. Но все файлы cookie для домена происхождения магически привязаны к запросу браузера, поэтому на самом деле весь сервер может вывести из присутствия допустимого сеансового файла cookie в запросе, что запрос поступает из браузера, который имеет аутентифицированный сеанс; он не может больше предположить что-либо о коде, запущенном в этом браузере, или он действительно отражает пожелания пользователя. Способ предотвращения этого заключается в том, чтобы включить в запрос дополнительную аутентификационную информацию ( "токен CSRF" ), переносимый некоторыми средствами, отличными от обработки автоматического cookie браузера. В то же время, cookie сеанса аутентифицирует пользователя/браузер, а токен CSRF аутентифицирует код, запущенный в браузере.

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

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

Это кажется общим (например, в AngularJS, Django, Rails), чтобы отправить токен CSRF с сервера на клиент в качестве файла cookie (то есть в заголовке Set-Cookie) а затем Javascript в клиенте очистите его из файла cookie и присоедините его как отдельный заголовок XSRF-TOKEN для отправки обратно на сервер.

(Альтернативный метод - тот, который рекомендуется, например, Express, где токен CSRF, сгенерированный сервером, включен в тело ответа через расширение шаблона на стороне сервера, прикрепленное непосредственно к коду/разметке, которое будет поставлять его обратно на сервер, например, в виде скрытого ввода формы. Этот пример представляет собой более простой способ делать что-то в Интернете, но обобщил бы его на больше JS-тяжелого клиента.)

Почему так часто используется Set-Cookie как транспортный поток для токена CSRF/почему это хорошая идея? Я полагаю, авторы всех этих концепций тщательно изучили свои варианты и не ошибались. Но на первый взгляд, использование файлов cookie для работы над тем, что по существу ограничивает дизайн файлов cookie, кажется глупым. Фактически, если вы использовали куки файлы в качестве транспорта в оба конца (Set-Cookie: заголовок вниз по течению для сервера, чтобы сообщить браузеру токен CSRF, а Cookie: заголовок вверх по течению для браузера, чтобы вернуть его на сервер), вы повторно введете уязвимость, которую вы пытаются исправить.

Я понимаю, что фреймворки выше не используют куки для всего обратного перехода для токена CSRF; они используют Set-Cookie downstream, затем что-то еще (например, заголовок X-CSRF-Token) вверх по течению, и это закрывает уязвимость. Но даже использование Set-Cookie в качестве нисходящего транспорта потенциально обманчиво и опасно; браузер теперь присоединяет токен CSRF к каждому запросу, включая настоящие вредоносные запросы XSRF; в лучшем случае это делает запрос больше, чем он должен быть, и в худшем случае какой-то здравомыслящий, но ошибочный фрагмент кода сервера действительно может попытаться его использовать, что было бы очень плохо. Кроме того, поскольку фактический предполагаемый получатель токена CSRF является клиентским Javascript, это означает, что этот файл cookie не может быть защищен только с помощью http-only. Поэтому отправка токена CSRF ниже по течению в заголовке Set-Cookie кажется мне довольно субоптимальным.

Ответ 1

Хорошая причина, к которой вы обращаетесь, заключается в том, что после получения файла cookie CSRF он становится доступным для использования в приложении в клиентском скрипте для использования как в обычных формах, так и в AJAX POST. Это будет иметь смысл в тяжелом JavaScript-приложении, например, используемом AngularJS (использование AngularJS не требует, чтобы приложение было одностраничным приложением, поэтому было бы полезно, когда состояние должно передаваться между различными запросами страницы, где значение CSRF не может нормально сохраняться в браузере).

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

Подход к телу запроса

  1. Пользователь успешно авторизуется.
  2. Сервер выдает auth cookie.
  3. Пользователь нажимает, чтобы перейти к форме.
  4. Если сервер еще не сгенерирован для этого сеанса, сервер генерирует токен CSRF, сохраняет его в сеансе пользователя и выводит его в скрытое поле.
  5. Пользователь отправляет форму.
  6. Сервер проверяет соответствие скрытого поля токену сессии.

Преимущества:

  • Прост в реализации.
  • Работает с AJAX.
  • Работает с формами.
  • Cookie может быть только HTTP.

Недостатки:

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

Пользовательский заголовок HTTP (нисходящий)

  1. Пользователь успешно авторизуется.
  2. Сервер выдает auth cookie.
  3. Пользователь нажимает, чтобы перейти к форме.
  4. Страница загружается в браузер, затем делается запрос AJAX для получения токена CSRF.
  5. Сервер генерирует токен CSRF (если он еще не создан для сеанса), сохраняет его в сеансе пользователя и выводит его в заголовок.
  6. Пользователь отправляет форму (токен отправляется через скрытое поле).
  7. Сервер проверяет соответствие скрытого поля токену сессии.

Преимущества:

Недостатки:

  • Не работает без запроса AJAX для получения значения заголовка.
  • У всех форм должно быть значение, добавленное к его HTML динамически.
  • Любые сообщения AJAX также должны содержать значение.
  • Страница должна сначала сделать запрос AJAX, чтобы получить токен CSRF, так что каждый раз это будет означать дополнительный обход.
  • Также можно просто вывести токен на страницу, которая сохранит дополнительный запрос.

Пользовательский заголовок HTTP (восходящий)

  1. Пользователь успешно авторизуется.
  2. Сервер выдает auth cookie.
  3. Пользователь нажимает, чтобы перейти к форме.
  4. Если сервер еще не сгенерирован для этого сеанса, сервер генерирует токен CSRF, сохраняет его в сеансе пользователя и выводит его где-то в содержимом страницы.
  5. Пользователь отправляет форму через AJAX (токен отправляется через заголовок).
  6. Сервер проверяет, соответствует ли пользовательский заголовок сохраненному токену

Преимущества:

Недостатки:

  • Не работает с формами.
  • Все сообщения AJAX должны содержать заголовок.

Пользовательский заголовок HTTP (восходящий и нисходящий)

  1. Пользователь успешно авторизуется.
  2. Сервер выдает auth cookie.
  3. Пользователь нажимает, чтобы перейти к форме.
  4. Страница загружается в браузер, затем делается запрос AJAX для получения токена CSRF.
  5. Сервер генерирует токен CSRF (если он еще не создан для сеанса), сохраняет его в сеансе пользователя и выводит его в заголовок.
  6. Пользователь отправляет форму через AJAX (токен отправляется через заголовок).
  7. Сервер проверяет, соответствует ли пользовательский заголовок сохраненному токену

Преимущества:

Недостатки:

  • Не работает с формами.
  • Все сообщения AJAX также должны содержать значение.
  • Страница должна сначала сделать запрос AJAX, чтобы получить токен CRSF, так что каждый раз это будет означать дополнительный обход.

Set-Cookie

  1. Пользователь успешно авторизуется.
  2. Сервер выдает auth cookie.
  3. Пользователь нажимает, чтобы перейти к форме.
  4. Сервер генерирует токен CSRF, сохраняет его в сеансе пользователя и выводит его в файл cookie.
  5. Пользователь отправляет форму через AJAX или через форму HTML.
  6. Сервер проверяет пользовательский заголовок (или скрытое поле формы) на соответствие сохраненному токену сеанса.
  7. Cookie доступен в браузере для использования в дополнительном AJAX и формирует запросы без дополнительных запросов к серверу для получения токена CSRF.

Преимущества:

  • Прост в реализации.
  • Работает с AJAX.
  • Работает с формами.
  • Не обязательно требует AJAX-запрос для получения значения cookie. Любой HTTP-запрос может получить его, и он может быть добавлен ко всем формам /AJAX-запросам через JavaScript.
  • После того, как токен CSRF был получен, поскольку он сохраняется в файле cookie, значение можно использовать повторно без дополнительных запросов.

Недостатки:

  • У всех форм должно быть значение, добавленное к его HTML динамически.
  • Любые сообщения AJAX также должны содержать значение.
  • Файл cookie будет передаваться для каждого запроса (т.е. Все GET для изображений, CSS, JS и т.д., Которые не участвуют в процессе CSRF), увеличивая размер запроса.
  • Cookie не может быть только HTTP.

Таким образом, подход с использованием файлов cookie является довольно динамичным, предлагая простой способ извлечь значение cookie (любой запрос HTTP) и использовать его (JS может автоматически добавлять значение в любую форму и использовать его в запросах AJAX либо в качестве заголовка, либо в виде значение формы). Как только токен CSRF был получен для сеанса, нет необходимости восстанавливать его, поскольку у злоумышленника, использующего эксплойт CSRF, нет способа извлечь этот токен. Если злонамеренный пользователь пытается прочитать маркер CSRF пользователя любым из указанных выше способов, это будет предотвращено той же политикой происхождения. Если злонамеренный пользователь пытается извлечь серверную часть токена CSRF (например, с помощью curl), то этот токен не будет связан с той же учетной записью пользователя, что и файл cookie сеанса аутентификации жертвы, будет отсутствовать в запросе (это будет злоумышленник - поэтому он не будет связана на стороне сервера с сеансом жертвы).

Наряду с шаблоном Synchronizer Token существует также метод предотвращения CSRF Double Submit Cookie, который, конечно, использует cookie для хранения типа CSRF-токена. Это проще реализовать, так как для маркера CSRF не требуется никакого состояния на стороне сервера. Маркер CSRF фактически может быть стандартным файлом cookie аутентификации при использовании этого метода, и это значение передается через файлы cookie, как обычно, вместе с запросом, но значение также повторяется либо в скрытом поле, либо в заголовке, который злоумышленник не может воспроизвести как они не могут прочитать значение в первую очередь. Однако рекомендуется выбрать другой файл cookie, отличный от файла cookie для аутентификации, чтобы файл cookie для аутентификации можно было защитить, пометив HttpOnly. Так что это еще одна распространенная причина, по которой вы можете найти предотвращение CSRF, используя метод на основе файлов cookie.

Ответ 2

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

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

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

Ответ 3

Мое лучшее предположение относительно ответа: рассмотрите эти 3 варианта, как получить токен CSRF с сервера в браузер.

  • В теле запроса (не HTTP-заголовок).
  • В пользовательском заголовке HTTP, а не Set-Cookie.
  • Как файл cookie в заголовке Set-Cookie.

Я думаю, что 1-й, тело запроса (пока показано учебник Express, который я связал в вопросе), не так переносится широкий спектр ситуаций; не каждый генерирует каждый HTTP-ответ динамически; где вам нужно положить токен в сгенерированный ответ, может сильно различаться (в виде скрытой формы, в фрагменте кода JS или переменной, доступной другим JS-кодом, возможно даже в URL-адресе, хотя это, как правило, плохое место для ввода токенов CSRF). Таким образом, при работе с некоторыми настройками, # 1 - это трудное место, чтобы сделать подход с одним размером для всех.

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

Это оставляет третий, cookie, переносимый заголовком Set-Cookie, в качестве подхода, который прост в использовании во всех ситуациях (любой сервер сможет устанавливать заголовки файлов cookie для каждого запроса, и это не имеет значения какие данные находятся в теле запроса). Поэтому, несмотря на свои недостатки, это был самый простой способ для каркасов, который широко реализуется.

Ответ 4

Помимо сессионных cookie файлов (что является стандартным), я не хочу использовать дополнительные cookie файлы.

Я нашел решение, которое работает для меня при создании одностраничного веб-приложения (SPA) со многими запросами AJAX. Примечание: я использую серверную Java и клиентскую JQuery, но никаких волшебных вещей, поэтому я думаю, что этот принцип может быть реализован на всех популярных языках программирования.

Мое решение без лишних файлов cookie простое:

Сторона клиента

Сохраните токен CSRF, возвращаемый сервером после успешного входа в систему, в глобальную переменную (если вы, конечно, хотите использовать глобальное хранилище вместо глобального, это нормально). Поручите JQuery указывать заголовок X-CSRF-TOKEN в каждом вызове AJAX.

Главная "индексная" страница содержит этот фрагмент JavaScript:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
  jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
});

Сторона сервера

При успешном входе в систему создайте случайный (и достаточно длинный) токен CSRF, сохраните его в сеансе на стороне сервера и верните его клиенту. Отфильтруйте определенные (конфиденциальные) входящие запросы, сравнив значение заголовка X-CSRF-TOKEN со значением, сохраненным в сеансе: они должны совпадать.

Чувствительные AJAX-вызовы (POST form-data и GET JSON-data) и серверный фильтр, перехватывающий их, находятся в пути /dataservice/*. Запросы на вход в систему не должны попадать в фильтр, поэтому они находятся на другом пути. Запросы на ресурсы HTML, CSS, JS и изображения также не находятся в пути /dataservice/*, поэтому не фильтруются. Они не содержат ничего секретного и не могут причинить вреда, так что это нормально.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
  resp.sendError(401);
} else 
  chain.doFilter(request, response);
}