Представьте, что у вас есть веб-сайт, похожий на упрощенный Twitter, размещенный на a.com. Зарегистрированные пользователи могут вводить текст (твит) в форму, которая отправляется на сервер в виде запроса POST и публикуется при попадании в кнопка отправки. На сервере пользователь идентифицируется по cookie содержащий уникальный идентификатор сеанса, чтобы ваш сервер знал, кто опубликовал Чирикать.
Форма может быть такой простой:
<form action="http://a.com/tweet" method="POST">
<input type="text" name="tweet">
<input type="submit">
</form>
А теперь представьте, что плохой парень копирует и вставляет эту форму на свой вредоносный веб-сайт, скажем, на b.com. Форма все еще будет работать. Как долго как пользователь вошел в ваш Твиттер (т.е. он получил действительный сессионный cookie для a.com), запрос POST будет отправлен http://a.com/tweet
и обрабатывается как обычно, когда пользователь нажимает кнопка отправки.
Пока это не большая проблема, пока пользователь осведомлен о что именно делает форма, но что если наш плохой парень подправит форму как это:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
<input type="submit" value="Click to win!">
</form>
Теперь, если один из ваших пользователей попадет на веб-сайт плохих парней и нажмет кнопку "Нажмите, чтобы выиграть!" кнопку, форма отправляется на ваш сайт, пользователь правильно идентифицирован по идентификатору сеанса в cookie и скрытый твит публикуются.
Если бы наш плохой парень был еще хуже, он заставил бы невинного пользователя отправить эту форму, как только они открывают его веб-страницу с помощью JavaScript, может быть, даже полностью спрятан в невидимом фрейме. Это в основном подделка межсайтовых запросов.
Форма может быть легко отправлена отовсюду. Обычно это общая черта, но есть еще много случаев, когда важно разрешить отправку формы только из домена где он принадлежит.
Вещи еще хуже, если ваше веб-приложение не различает между запросами POST и GET (например, в PHP, используя вместо этого $ _REQUEST $ _POST). Не делай этого! Запросы на изменение данных могут быть представлены так же просто, как <img src="http://a.com/tweet?tweet=This+is+really+bad">
, встроен в вредоносный веб-сайт или даже в электронную почту.
Как сделать так, чтобы форму можно было отправлять только с моего собственного сайта? Вот где появляется токен CSRF. Токен CSRF является случайным, трудно угадываемая строка. На странице с формой, которую вы хотите защитить, Сервер сгенерирует случайную строку, токен CSRF, добавит ее в сформировать как скрытое поле, а также запомнить его как-либо, сохраняя это в сеансе или путем установки файла cookie, содержащего значение. Теперь Форма будет выглядеть так:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
<input type="text" name="tweet">
<input type="submit">
</form>
Когда пользователь отправляет форму, сервер просто должен сравнить значение опубликованного поля csrf-token (имя не дело) с токеном CSRF, запомненным сервером. Если обе строки равны, сервер может продолжать обрабатывать форму. В противном случае сервер должен немедленно прекратить обработку формы и ответить ошибка.
Почему это работает? Есть несколько причин, почему плохой парень из нашего В приведенном выше примере невозможно получить токен CSRF:
Копирование статического исходного кода с нашей страницы на другой сайт было бы бесполезно, потому что значение скрытого поля изменяется с каждый пользователь. Без сайта плохих парней, зная текущих пользователей Маркер CSRF вашего сервера всегда будет отклонять запрос POST.
Потому что злоумышленники загружают вредоносную страницу вашего браузера с другого домена (b.com вместо a.com), у плохого парня нет возможность кодирования JavaScript, который загружает контент и, следовательно, наш пользователи токен CSRF с вашего сайта. Это потому, что веб браузеры по умолчанию не разрешают междоменные запросы AJAX.
Плохой парень также не может получить доступ к куки, установленному вашим сервером, потому что домены не совпадают.
Когда следует защищать от подделки межсайтовых запросов? Если вы можете убедитесь, что вы не путаете GET, POST и другие методы запроса как описано выше, хорошим началом будет защита всех запросов POST путем по умолчанию.
Вам не нужно защищать запросы PUT и DELETE, потому что, как объяснено выше, стандартная HTML-форма не может быть отправлена браузером используя эти методы.
С другой стороны, JavaScript действительно может делать другие типы запросов, например используя функцию jQuerys $.ajax(), но помните, для запросов AJAX для работы домены должны совпадать (если вы явно не в противном случае настройте свой веб-сервер).
Это означает, что часто вам даже не нужно добавлять токен CSRF в AJAX запросы, даже если они являются запросами POST, но вам придется сделать убедитесь, что вы только обходите проверку CSRF в своем веб-приложении, если запрос POST на самом деле является запросом AJAX. Вы можете сделать это, ищет наличие заголовка типа X-Requested-With, который AJAX запросы обычно включают. Вы также можете установить другой пользовательский заголовок и проверьте его наличие на стороне сервера. Это безопасно, потому что браузер не будет добавлять пользовательские заголовки к обычной отправке формы HTML (см. выше), так что у мистера Бад Гая нет шансов симулировать это поведение с формой.
Если у вас есть сомнения по поводу запросов AJAX, потому что по какой-то причине вы не может проверить заголовок, такой как X-Requested-With, просто передайте сгенерированный токен CSRF для вашего JavaScript и добавьте токен в AJAX запрос. Есть несколько способов сделать это; либо добавьте его в полезную нагрузку так же, как обычная форма HTML, или добавить пользовательский заголовок запрос AJAX. Пока ваш сервер знает, где его искать входящий запрос и может сравнить его с исходным значением запоминает сессию или куки, вы отсортированы.