Мы представляем API, который партнеры могут использовать только в доменах, которые они у нас зарегистрировали. Его контент частично является общедоступным (но желательно только для показа в известных нам доменах), но в основном является частным для наших пользователей. Так:
Чтобы определить, что отображается, наш пользователь должен войти в систему с нами, но это обрабатывается отдельно.
Чтобы определить, где отображаются данные, используется открытый ключ API для ограничения доступа к известным нам доменам и, прежде всего, для обеспечения того, чтобы личные данные пользователя не были уязвимы для CSRF .
Этот ключ API действительно виден всем, мы не аутентифицируем нашего партнера каким-либо другим способом, и нам не нужен REFERER . Тем не менее, это безопасно:
Когда нас get-csrf-token.js?apiKey=abc123
просят:
Найдите ключ abc123
в базе данных и получите список допустимых доменов для этого ключа.
Найдите файл cookie проверки CSRF. Если он не существует, сгенерируйте безопасное случайное значение и поместите его в файл cookie сеанса только для HTTP . Если файл cookie действительно существует, получите существующее случайное значение.
Создайте токен CSRF из ключа API и случайного значения из файла cookie и подпишите его . (Вместо того, чтобы хранить список токенов на сервере, мы подписываем значения. Оба значения будут читаться в подписанном токене, это нормально.)
Установите ответ так, чтобы он не кэшировался, добавьте файл cookie и верните сценарий, например:
var apiConfig = apiConfig || {};
if(document.domain === 'expected-domain.com'
|| document.domain === 'www.expected-domain.com') {
apiConfig.csrfToken = 'API key, random value, signature';
if(typeof apiConfig.fnInit !== 'undefined') {
apiConfig.fnInit();
}
} else {
alert('This site is not authorised for this API key.');
}
Примечания:
Выше не препятствует стороне сервера скрипта от подделки запроса, но только гарантирует , что домен совпадает , если запрашивается браузером.
Та же политика происхождения для JavaScript гарантирует, что браузер не может использовать XHR (Ajax) для загрузки, а затем проверки исходного кода JavaScript. Вместо этого обычный браузер может загружать его только с помощью <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">
(или динамического эквивалента), а затем запускает код. Конечно, ваш сервер не должен поддерживать совместное использование ресурсов Cross-Origin или JSONP для сгенерированного JavaScript.
Сценарий браузера может изменить значение document.domain
перед загрузкой указанного выше сценария. Но та же политика происхождения только позволяет сократить домен путем удаления префиксов, как переписывание subdomain.example.com
просто example.com
, или myblog.wordpress.com
на wordpress.com
, или в некоторых браузерах , даже bbc.co.uk
в co.uk
.
Если файл JavaScript получен с помощью какого-либо сценария на стороне сервера, сервер также получит файл cookie. Однако сторонний сервер не может заставить браузер пользователя связать этот файл cookie с нашим доменом. Следовательно, токен CSRF и файл cookie проверки, которые были получены с помощью сценария на стороне сервера, могут использоваться только для последующих вызовов на стороне сервера, а не в браузере. Однако такие вызовы на стороне сервера никогда не будут включать файл cookie пользователя и, следовательно, могут получать только общедоступные данные. Это те же данные, которые скрипт на стороне сервера может извлечь напрямую с веб-сайта партнера.
Когда пользователь входит в систему, установите какой-нибудь пользовательский cookie-файл любым удобным для вас способом. (Пользователь мог уже войти в систему до того, как был запрошен JavaScript.)
Все последующие запросы API к серверу (включая запросы GET и JSONP) должны включать токен CSRF, файл cookie проверки CSRF и (если выполнен вход) файл cookie пользователя. Теперь сервер может определить, следует ли доверять запросу:
Наличие действительного токена CSRF гарантирует, что JavaScript был загружен из ожидаемого домена, если он загружен браузером.
Наличие токена CSRF без файла cookie проверки указывает на подделку.
Наличие как токена CSRF, так и файла cookie проверки CSRF ничего не гарантирует: это может быть либо поддельный запрос на стороне сервера, либо действительный запрос из браузера. (Это не может быть запрос от браузера, сделанный из неподдерживаемого домена.)
Наличие файла cookie пользователя гарантирует, что пользователь вошел в систему, но не гарантирует, что пользователь является членом данного партнера или что пользователь просматривает правильный веб-сайт.
Наличие файла cookie пользователя без файла cookie проверки CSRF указывает на подделку.
Наличие файла cookie пользователя гарантирует, что текущий запрос будет выполнен через браузер. (Предполагая, что пользователь не будет вводить свои учетные данные на неизвестном веб-сайте, и предполагая, что мы не заботимся о том, чтобы пользователи использовали свои собственные учетные данные для выполнения какого-либо запроса на стороне сервера.) Если у нас также есть файл cookie проверки CSRF, то этот файл cookie проверки CSRF был также получил с помощью браузера. Затем, если у нас также есть токен CSRF с действующей подписью, ислучайное число в файле cookie проверки CSRF совпадает с числом в этом токене CSRF, тогда код JavaScript для этого токена также был получен во время того же самого раннего запроса, во время которого был установлен файл cookie CSRF, следовательно, также с использованием браузера. Это также означает, что приведенный выше код JavaScript был выполнен до того, как был установлен токен, и что в то время домен был действителен для данного ключа API.
Итак: теперь сервер может безопасно использовать ключ API из подписанного токена.
Если в какой-то момент сервер не доверяет запросу, возвращается 403 Forbidden. Виджет может отреагировать на это, показывая пользователю предупреждение.
Подписывать файл cookie проверки CSRF не требуется, поскольку мы сравниваем его с подписанным токеном CSRF. Отсутствие подписи файла cookie делает каждый HTTP-запрос короче, а проверка сервера - немного быстрее.
Сгенерированный токен CSRF действителен бесконечно, но только в сочетании с проверочным файлом cookie, так что эффективно, пока браузер не будет закрыт.
Мы можем ограничить время жизни подписи токена. Мы могли бы удалить файл cookie проверки CSRF, когда пользователь выходит из системы, чтобы выполнить рекомендацию OWASP . А чтобы не передавать случайное число для каждого пользователя нескольким партнерам, можно добавить ключ API к имени файла cookie. Но даже в этом случае невозможно легко обновить файл cookie проверки CSRF при запросе нового токена, поскольку пользователи могут просматривать один и тот же сайт в нескольких окнах, совместно используя один файл cookie (который при обновлении будет обновлен во всех окнах, после чего Токен JavaScript в других окнах больше не будет соответствовать этому единственному файлу cookie).
Для тех, кто использует OAuth, см. Также OAuth и клиентские виджеты , из которых я почерпнул идею JavaScript. Для использования API на стороне сервера , когда мы не можем полагаться на код JavaScript для ограничения домена, мы используем секретные ключи вместо открытых ключей API.