Почему в cookie-файлы принято добавлять токены предотвращения CSRF?

284

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

Насколько я понимаю, уязвимость вокруг CSRF вводится в предположении, что (с точки зрения веб-сервера) действительный файл cookie сеанса во входящем 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-файлов, кажется глупым. Фактически, если вы использовали файлы cookie в качестве транспорта туда и обратно (заголовок Set-Cookie: вниз по течению, чтобы сервер сообщал браузеру токен CSRF, и заголовок Cookie: вверх по течению, чтобы браузер возвратил его на сервер), вы бы снова представили уязвимость, которую вы пытаемся исправить.

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

metamatt
источник
Это отличный вопрос, попавший в нужное место.
кта

Ответы:

263

Хорошая причина, к которой вы обращаетесь, заключается в том, что после получения файла 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 POST должны также содержать значение.
  • Страница должна сначала сделать запрос 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 сеанса аутентификации жертвы, будет отсутствовать в запросе (это будет злоумышленник - следовательно, он не будет связан на стороне сервера с сеансом жертвы).

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

SilverlightFox
источник
7
Я не уверен, что понимаю, как «AJAX-запрос на получение токена CSRF» (шаг 4 в обоих разделах «Пользовательский заголовок: нисходящий поток») может быть выполнен безопасно; поскольку это отдельный запрос, сервер не знает, от кого он поступает; откуда он знает, что можно разглашать токен CSRF? Мне кажется, что если вы не можете получить токен из начальной загрузки страницы, вы теряете (что, к сожалению, делает пользовательский заголовок нижестоящего ответа незапущенным).
metamatt
6
Потому что у фальсификатора не будет куки сессии. У них может быть свой собственный файл cookie сеанса, но, поскольку токен CSRF связан с сеансом, их токен CSRF не будет совпадать с маркером жертвы.
SilverlightFox
32
В моем понимании атаки CSRF у фальшивомонетчика есть мой сессионный cookie. Ну, на самом деле они не видят cookie, но имеют возможность предоставить его в своих поддельных запросах, потому что запросы поступают из моего браузера, а мой браузер предоставляет мой сеансовый файл cookie. Таким образом, с точки зрения сервера, один файл cookie сеанса не может отличить законный запрос от поддельного запроса. На самом деле это атака, которую мы пытаемся предотвратить. Кстати, спасибо за ваше терпение в разговоре, особенно если я запутался в этом.
metamatt
8
У них есть возможность предоставить auth cookie, но они не могут прочитать ответ, содержащий токен CSRF.
SilverlightFox
8
@metamatt Извините за некро, но я сделаю это для людей, которые бродят. В моем понимании злоумышленник обычно не имеет доступа к ответу. CSRF прежде всего используется, чтобы вызвать побочные эффекты , а не непосредственно собирать данные. Например, сценарий атаки CSRF может заставить привилегированного пользователя повысить привилегии злоумышленника, отключить параметр безопасности или заставить вошедшего в систему пользователя PayPal отправлять перевод на определенный адрес электронной почты. Ни в одном из этих случаев злоумышленнику не важен ответ, который все еще отправляется в браузер жертвы; только исход атаки.
Джонатанбрудер
61

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

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

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

Tongfa
источник
Конечно, злоумышленнику не нужно сначала читать куки. Они могут просто вставить изображение на взломанный сайт, с src='bank.com/transfer?to=hacker&amount=1000которого запрашивает браузер, вместе с соответствующими файлами cookie для этого сайта ( bank.com)?
developius
2
CSRF предназначен для проверки пользователя на стороне клиента, а не для защиты сайта, как правило, от компрометации на стороне сервера, как вы предлагаете.
Tongfa
2
@developius отправка куки недостаточно для защиты CSRF. Файл cookie содержит токен csrf, отправленный сервером. Законный клиент должен прочитать токен csrf из файла cookie, а затем передать его в запрос где-нибудь, например в заголовок или в полезную нагрузку. Защита CSRF проверяет, что значение в куки совпадает со значением в запросе, в противном случае запрос отклоняется. Следовательно, злоумышленнику необходимо прочитать файл cookie.
Уилл М.
1
Этот ответ был очень точным к первоначальному вопросу автора и был очень ясным. +1 Спасибо.
java-addict301
@Tongfa - спасибо, это помогло мне лучше понять. Правильно ли я предположить, что токен CSRF НЕ должен быть помещен в заголовок? это должно быть где-то в теле?
zerohedge
10

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

  1. В теле запроса (не заголовок HTTP).
  2. В настраиваемом заголовке HTTP, а не в Set-Cookie.
  3. Как cookie, в заголовке Set-Cookie.

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

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

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

metamatt
источник
7
Я могу констатировать очевидное, это значит, что cookie не может быть верным httponly?
Фотон
1
только для запросов ajax (где JS должен знать значение файла cookie csrf, чтобы отправить его при следующем запросе во втором канале (в виде данных формы или заголовка)). Нет причин требовать, чтобы токен csrf был HttpOnly, если cookie-файл сеанса уже является HttpOnly (для защиты от XSS), поскольку токен csrf сам по себе не имеет ценности без ассоциированного сеанса.
Cowbert
2

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

Я нашел решение, которое работает для меня при создании одностраничного веб-приложения (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);
}   
Стефан ван Хоф
источник
Я думаю, что вы хотели бы CSRF на запрос входа в систему. Похоже, вы используете токен CSRF также в качестве токена сеанса входа. Также работает, чтобы они были отдельными токенами, и тогда вы можете использовать CSRF на любой конечной точке, вне зависимости от того, вошел ли пользователь в систему или нет.
Tongfa