Понимание токена подлинности Rails

983

Я сталкиваюсь с некоторыми проблемами, связанными с токеном аутентификации в Rails, как я уже много раз.

Но я действительно не хочу просто решать эту проблему и продолжать. Я действительно хотел бы понять маркер Подлинности. Ну, мой вопрос: есть ли у вас какой-либо полный источник информации по этому вопросу, или вы бы потратили свое время, чтобы подробно объяснить здесь?

Рикардо Акрас
источник
7
Также смотрите: «Почему Google Prepend while (1) к их ответу JSON?» stackoverflow.com/questions/2669690/…
Хлоя

Ответы:

1463

Что просходит

Когда пользователь просматривает форму для создания, обновления или уничтожения ресурса, приложение Rails создает случайное число authenticity_token, сохраняет этот токен в сеансе и помещает его в скрытое поле в форме. Когда пользователь отправляет форму, Rails ищет authenticity_token, сравнивает ее с той, что хранится в сеансе, и, если они совпадают, запрос может продолжаться.

Почему это происходит

Поскольку токен подлинности хранится в сеансе, клиент не может знать его значение. Это не позволяет людям отправлять формы в приложение Rails, не просматривая форму в самом приложении. Представьте, что вы используете сервис A, вы вошли в сервис и все в порядке. Теперь представьте, что вы пошли пользоваться услугой B, увидели понравившуюся вам картинку и нажали на нее, чтобы просмотреть ее в большем размере. Теперь, если какой-то злой код был в службе B, он может отправить запрос в службу A (в которую вы вошли) и попросить удалить вашу учетную запись, отправив запрос на http://serviceA.com/close_account. Это то, что известно как CSRF (Подделка межсайтовых запросов) .

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

Документация API описывает подробности о метатеге:

Защита CSRF включается protect_from_forgeryметодом, который проверяет токен и сбрасывает сеанс, если он не соответствует ожидаемому. Вызов этого метода генерируется для новых приложений Rails по умолчанию. Параметр токена назван authenticity_tokenпо умолчанию. Имя и значение этого токена должны быть добавлены к каждому макету, который отображает формы, включая csrf_meta_tagsзаголовок HTML.

Ноты

Имейте в виду, что Rails проверяет только не идемпотентные методы (POST, PUT / PATCH и DELETE). GET-запрос не проверяется на подлинность токена. Почему? поскольку в спецификации HTTP говорится, что запросы GET являются идемпотентными и не должны создавать, изменять или уничтожать ресурсы на сервере, а запрос должен быть идемпотентным (если вы выполняете одну и ту же команду несколько раз, вы должны получать один и тот же результат каждый раз).

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

занятия

Используйте authenticity_tokenдля защиты ваших неидемпотентных методов (POST, PUT / PATCH и DELETE). Также убедитесь, что не разрешены какие-либо запросы GET, которые потенциально могут изменить ресурсы на сервере.


РЕДАКТИРОВАТЬ: Проверьте комментарий @erturne относительно GET-запросов, являющихся идемпотентными. Он объясняет это лучше, чем я здесь.

Фейсал
источник
25
@Faisal, возможно ли тогда, чтобы злоумышленник просто прочитал / перехватил «скрытый» элемент формы для службы A и получил этот уникальный токен, сгенерированный для пользователя - учитывая, что он получил доступ к сеансу, запущенному пользователем для службы А?
marcamillion
11
@marcamillion: если кто-то похитил вашу сессию в службе А, то маркер подлинности не защитит вас. Угонщик сможет подать запрос, и ему будет разрешено продолжить.
Фейсал
12
@zabba: Rails вызывает исключение ActionController :: InvalidAuthenticityToken, если форма отправляется без правильного токена. Вы можете спасти_из исключения и выполнить любую обработку, какую захотите.
Фейсал
5
re «Также убедитесь, что не выполняются никакие запросы GET, которые потенциально могут изменить ресурсы на сервере». - это включает в себя не использование match () в маршрутах, которые потенциально могут разрешать запросы GET к действиям контроллера, предназначенным для получения только POST
Стивен Сорока
102
«... и запрос должен быть идемпотентным (если вы запускаете одну и ту же команду несколько раз, вы должны каждый раз получать один и тот же результат)». Просто тонкое уточнение здесь. Безопасный означает отсутствие побочных эффектов. Идемпотент означает один и тот же побочный эффект независимо от того, сколько раз вызывается услуга. Все безопасные услуги по своей природе идемпотентны, потому что нет побочных эффектов. Многократный вызов GET для ресурса текущего времени будет каждый раз возвращать другой результат, но это безопасно (и, следовательно, идемпотентно).
erturne
137

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

Если у вас просто возникают проблемы с тем, что rails отказывают в доступе к AJAX-скрипту, вы можете использовать

<%= form_authenticity_token %>

генерировать правильный токен при создании формы.

Вы можете прочитать больше об этом в документации .

Topher Fangio
источник
88

Что такое CSRF?

Маркер подлинности является контрмерой подделке межсайтовых запросов (CSRF). Вы спрашиваете, что такое CSRF?

Это способ, которым злоумышленник может перехватить сессии, даже не зная их токены.

Сценарий :

  • Посетите сайт вашего банка, войдите.
  • Затем посетите сайт злоумышленника (например, рекламную рекламу ненадежной организации).
  • На странице злоумышленника есть форма с теми же полями, что и у банковской формы "Средства перевода".
  • Атакующий знает данные вашей учетной записи и имеет предварительно заполненные поля формы для перевода денег с вашей учетной записи на учетную запись злоумышленника.
  • На странице злоумышленника есть Javascript, который отправляет форму в ваш банк.
  • Когда форма отправлена, браузер включает ваши куки для сайта банка, включая токен сеанса.
  • Банк переводит деньги на счет злоумышленника.
  • Форма может быть в невидимом фрейме, поэтому вы никогда не узнаете, что атака произошла.
  • Это называется подделкой межсайтовых запросов (CSRF).

CSRF решение :

  • Сервер может пометить формы, которые пришли с самого сервера
  • Каждая форма должна содержать дополнительный токен аутентификации в качестве скрытого поля.
  • Токен должен быть непредсказуемым (злоумышленник не может его угадать).
  • Сервер предоставляет действительный токен в формах на своих страницах.
  • Сервер проверяет токен при отправке формы, отклоняет формы без правильного токена.
  • Пример токена: идентификатор сессии, зашифрованный секретным ключом сервера.
  • Rails автоматически генерирует такие токены: смотрите поле ввода authenticity_token в каждой форме.
Роза Перроне
источник
1
Вот версия этого же объяснения, которая является менее точной, но также и менее абстрактной: stackoverflow.com/a/33829607/2810305
Lutz Prechelt
Я не уверен, но позволяют ли современные браузеры отправлять не идемпотентные запросы (POST / PUT / DELETE) в другой домен? Я полагаю, в самом браузере должна быть защита от подобных вещей
divByZero
45

Пример минимальной атаки, которая будет предотвращена: CSRF

На моем сайте evil.comя убедил вас отправить следующую форму:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="to"      value="ciro"></p>
  <p><input type="hidden" name="ammount" value="100"></p>
  <p><button type="submit">CLICK TO GET PRIZE!!!</button></p>
</form>

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

То есть, когда в игру вступает токен CSRF:

  • с ответом GET, возвращающим форму, Rails отправляет очень длинный случайный скрытый параметр
  • когда браузер делает запрос POST, он отправит параметр вместе, и сервер примет его, только если он соответствует

Таким образом, форма в аутентичном браузере будет выглядеть так:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="authenticity_token" value="j/DcoJ2VZvr7vdf8CHKsvjdlDbmiizaOb5B8DMALg6s=" ></p>
  <p><input type="hidden" name="to"                 value="ciro"></p>
  <p><input type="hidden" name="ammount"            value="100"></p>
  <p><button type="submit">Send 100$ to Ciro.</button></p>
</form>

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

Этот метод предотвращения называется шаблоном синхронизатора .

Политика единого происхождения

Но что, если злоумышленник сделал два запроса с JavaScript, один для чтения токена, а второй для передачи?

Одного шаблона токена синхронизатора недостаточно, чтобы предотвратить это!

Именно здесь на помощь приходит единая политика происхождения, как я объяснил по адресу: /security/8264/why-is-the-same-origin-policy-so-important/72569# 72569

Как Rails отправляет токены

Рассмотрено по адресу: Rails: как работает csrf_meta_tag?

В принципе:

  • Помощники по HTML, такие как form_tagдобавление скрытого поля в форму для вас, если это не форма GET

  • AJAX обрабатывается автоматически с помощью jquery-ujs , который считывает токен из metaэлементов, добавленных в ваш заголовок csrf_meta_tags(присутствует в шаблоне по умолчанию), и добавляет его к любому выполненному запросу.

    uJS также пытается обновить токен в формах в устаревших кэшированных фрагментах.

Другие профилактические подходы

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
Спасибо, но ваша точка зрения о том, что вы полагаетесь на одну и ту же политику происхождения, чтобы не иметь возможности сначала просто прочитать токен CSRF, кажется ошибочной. Итак, сначала вы говорите, что можете POST к другому источнику, но не можете прочитать из него, кажется странным, но я думаю, это правильно, но вы могли бы вставить тег изображения или сценария с помощью get на страницу и связать обработчик для анализа ответа и получить это да?
bjm88
@ bjm88 внедрить скрипт где? На вашем сайте или на атакованном сайте? Если сайт атакован, разрешение внедрения скрипта является широко известным недостатком безопасности и эффективно закладывает сайт. Каждый веб-сайт должен бороться с этим путем ввода санитарии. Для изображений я не вижу, как их можно использовать для атаки. На атакующем сайте: вы можете изменить свой браузер, чтобы разрешить чтение, и, таким образом, автоматически закладывать себя по своему желанию :-), но приличные браузеры предотвращают это по умолчанию, попробуйте.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
43

Маркер подлинности используется для предотвращения атак подделки межсайтовых запросов (CSRF). Чтобы понять маркер подлинности, вы должны сначала понять CSRF-атаки.

CSRF

Предположим, что вы являетесь автором bank.com. У вас есть форма на вашем сайте, которая используется для перевода денег на другой счет с запросом GET:

введите описание изображения здесь

Хакер может просто отправить HTTP-запрос на сервер, говоря GET /transfer?amount=$1000000&account-to=999999, верно?

введите описание изображения здесь

Неправильно. Атака хакеров не сработает. Сервер будет в принципе думать?

А? Кто этот парень пытается инициировать перевод. Это не владелец аккаунта, это точно.

Как сервер знает это? Потому что нет session_idcookie, аутентифицирующего запрашивающего.

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

О, это Джон Доу. Он успешно вошел в систему 2,5 минуты назад. Ему хорошо идти.

Хакер может подумать:

Хм. Обычный HTTP-запрос не сработает, но если бы я мог взять руку на этот session_idcookie, я был бы золотым.

В браузере пользователей для bank.comдомена установлен набор файлов cookie . Каждый раз, когда пользователь отправляет запрос в bank.comдомен, все куки отправляются вместе. Включая session_idпеченье.

Так что, если хакер может заставить вас сделать запрос GET, который переводит деньги на его счет, он будет успешным. Как он мог обмануть тебя в этом? С подделкой межсайтовых запросов.

Это довольно просто, на самом деле. Хакер может просто заставить вас зайти на его сайт. На своем веб-сайте он может иметь следующий тег изображения:

<img src="http://bank.com/transfer?amount=$1000000&account-to=999999">

Когда пользовательский браузер обнаружит этот тег изображения, он отправит запрос GET на этот URL. И поскольку запрос поступает из его браузера, он отправит вместе с ним все файлы cookie, связанные с bank.com. Если пользователь недавно выполнил вход в bank.com..., session_idфайл cookie будет установлен, и сервер подумает, что пользователь хотел перевести 1 000 000 долларов на счет 999999!

введите описание изображения здесь

Ну, просто не посещайте опасные сайты, и все будет в порядке.

Этого недостаточно. Что если кто-то разместит это изображение на Facebook, и оно появится на вашей стене? Что если он внедряется в сайт, который вы посещаете с XSS-атакой?

Это не так плохо. Только GET-запросы уязвимы.

Не правда. Форма, отправляющая запрос POST, может генерироваться динамически. Вот пример из Rails Guide по безопасности :

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

Токен подлинности

Когда у вас ApplicationControllerесть это:

protect_from_forgery with: :exception

Эта:

<%= form_tag do %>
  Form contents
<% end %>

Скомпилировано в это:

<form accept-charset="UTF-8" action="/" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
  Form contents
</form>

В частности, генерируется следующее:

<input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />

Для защиты от CSRF-атак, если Rails не увидит токен аутентификации, отправленный вместе с запросом, он не будет считать запрос безопасным.

Как злоумышленник должен знать, что это за токен? Разное значение генерируется случайным образом каждый раз, когда генерируется форма:

введите описание изображения здесь

Атака межсайтового скриптинга (XSS) - вот как. Но это другая уязвимость для другого дня.

Адам Зернер
источник
39

Это Authenticity Tokenметод 'rails' для предотвращения 'атак подделки межсайтовых запросов (CSRF или XSRF)' .

Проще говоря, он гарантирует, что запросы PUT / POST / DELETE (методы, которые могут изменять содержимое) к вашему веб-приложению выполняются из браузера клиента, а не от третьей стороны (злоумышленника), имеющей доступ к созданному файлу cookie. на стороне клиента.

и я
источник
34

так как Authenticity Tokenэто так важно, и в Rails 3.0+ вы можете использовать

 <%= token_tag nil %>

создавать

<input name="authenticity_token" type="hidden" value="token_value">

где угодно

Юань Хе
источник
Это было полезно для меня. На самом деле я пытался сделать это XSSна странице входа в систему не для гнусных целей, а для создания нового сеанса с предварительно заполненным именем пользователя. Теперь я знаю, что могу просто использовать value="token_value".
Майкл - Где Клэй Ширки
27

Остерегайтесь механизма маркера подлинности, который может привести к условиям гонки, если у вас есть несколько одновременных запросов от одного и того же клиента. В этой ситуации ваш сервер может генерировать несколько токенов подлинности, когда их должен быть только один, и клиент, получающий более ранний токен в форме, не выполнит свой следующий запрос, потому что токен cookie сеанса был перезаписан. Есть описание этой проблемы и не совсем тривиальное решение здесь: http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/

ПСР
источник
11

Методы Где authenticity_tokenтребуется

authenticity_token требуется в случае идемпотентных методов, таких как post, put и delete, потому что идемпотентные методы влияют на данные.

Почему это требуется

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

Ума
источник
3
На самом деле, разве не наоборот? GET является идемпотентным, поскольку его вызов не должен изменять состояние системы, где глаголы PUT POST и DELETE НЕ являются идемпотентными, поскольку они изменяют состояние системы. IE: authenticity_token требуется в случае НЕ идемпотентных методов.
Жан-Тео
2
@ Жан-Добе, ума: идемпотент означает, что если сделать дважды, действие происходит только один раз. GET, PUT и DELETE являются идемпотентными: w3.org/Protocols/rfc2616/rfc2616-sec9.html Ключевым свойством здесь является не идемпотентность, но если метод изменяет или нет данные, которые называются «Безопасный метод» или нет.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
6

Что такое аутентификационный токен?

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

Зачем нужен аутентификационный токен?

Чтобы защитить ваше приложение или сайт от подделки межсайтовых запросов.

Как добавить маркер аутентификации в форму?

Если вы генерируете форму с помощью тега form_for, автоматически добавляется маркер аутентификации, иначе вы можете использовать <%= csrf_meta_tag %>.

Прадип Сапкота
источник