Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Почему мы не можем делать Cross-Origin API запросы?
  • Викторина
  • Как блокируются Cross-Origin AJAX-запросы?
  • Как настроить CORS?
  • Заключение
  1. Глава 4 - Межсайтовые атаки

Введение в Cross-Origin Resource Sharing (CORS)

PreviousSame-origin Policy и Same-SiteNextПроблемы Cross-Origin безопасности

Last updated 8 months ago

Когда я обсуждал политику одинакового происхождения (Same-Origin Policy), я упомянул, что браузеры обычно предотвращают доступ веб-сайта к данным с другого Origin. Однако, во время разработки, фронтенд и бэкенд могут находиться на разных Origin. Например, один может быть на example.com, а другой на api.example.com. В таких случаях, как фронтенд может получить доступ к данным с бэкенда?

Здесь на помощь приходит CORS. CORS расшифровывается как Cross-Origin Resource Sharing и является механизмом, который позволяет веб-сайтам обмениваться данными между разными Origin. Этот механизм часто используется в разработке, но если он настроен неправильно, это может стать уязвимостью безопасности.

Чтобы понять, почему существует CORS, мы должны начать с того, почему браузеры блокируют Cross-Origin API запросы.

Почему мы не можем делать Cross-Origin API запросы?

Более точно, вопрос должен звучать так: "Почему мы не можем использовать XMLHttpRequest или fetch (также известный как AJAX) для получения ресурсов с другого Origin?"

Это более точное определение необходимо, потому что получение "Cross-Origin Resource" на самом деле довольно распространено. Например, <img src="https://another-domain.com/bg.png" /> является Cross-Origin запросом для получения изображения. Аналогично, <script src="https://another-domain.com/script.js" /> является Cross-Origin запросом для получения и выполнения JavaScript файла.

Но сталкивались ли вы с какими-либо проблемами в этих двух сценариях? Скорее всего, нет, так как мы уже привыкли использовать их без дополнительных проблем.

Так почему же всё иначе, когда дело доходит до AJAX с использованием XMLHttpRequest или fetch? Почему Cross-Origin запросы блокируются в этом случае? (Это объяснение не совсем точное и будет далее разъяснено.)

Поскольку мы уже знаем, что Cross-Origin запросы блокируются, должна быть причина для этого. Но в чем же причина? Это похоже на использование метода доказательства от обратного. Чтобы доказать что-то, предположим, что блокировка Cross-Origin запросов неверна или бессмысленна. Затем, если мы найдем противоречие, мы узнаем, почему Cross-Origin запросы блокируются.

Итак, давайте рассмотрим следующий вопрос:

Что бы произошло, если бы Cross-Origin запросы не блокировались?

В этом случае мы могли бы свободно делать API вызовы, не прибегая к CORS! Звучит так, как будто не было бы никаких проблем. В конце концов, теги <img> и <script> могут получать доступ к Cross-Origin ресурсам, так почему же не может AJAX?

Если бы Cross-Origin AJAX запросы не блокировались, я мог бы использовать AJAX на своей веб-странице (скажем, https://example.com/index.html) для получения данных с https://google.com, верно?

На первый взгляд, это кажется безобидным, просто получение HTML главной страницы Google. Ничего страшного.

Но что, если я случайно узнаю, что у вашей компании есть "внутренний" публичный веб-сайт с URL http://internal.good-company.com? Этот веб-сайт недоступен снаружи; только компьютеры сотрудников компании могут получить к нему доступ. Теперь, что если я напишу кусок кода с AJAX запросом на своей веб-странице для получения данных с этого внутреннего веб-сайта? Смогу ли я получить содержимое веб-сайта и отправить его обратно на мой сервер?

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

  1. Цель открывает вредоносный веб-сайт.

  2. Вредоносный веб-сайт использует AJAX для получения данных с внутреннего конфиденциального веб-сайта.

  3. Данные получены.

  4. Они отправляются обратно на сервер злоумышленника.

Вы можете спросить: "Но чтобы использовать эту технику, злоумышленнику нужно знать URL внутреннего веб-сайта. Разве это не сложно?"

Если вы думаете, что это слишком сложно, позвольте мне привести другой пример.

Во время разработки многие люди запускают сервер на своем компьютере с URL-адресами типа http://localhost:3000 или http://localhost:5566. Это очень распространено в современной фронтенд-разработке.

Если бы браузер не блокировал Cross-Origin API запросы,я мог бы написать такой код:

function sendRequest(url, callback) {
  const request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.onload = function() {
    callback(this.response);
  }
  request.send();
}

for (let port = 80; port < 10000; port++) {
  sendRequest('http://localhost:' + port, data => {
  });
}

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

Более того, если вы думаете, что приведенные выше два примера неосуществимы, давайте сделаем еще одно предположение. Помимо предположения, что Cross-Origin запросы не блокируются, давайте также предположим, что "Cross-Origin requests automatically include cookies."

Таким образом, если я отправлю запрос на https://www.facebook.com/messages/t, я смогу увидеть ваши сообщения в чате. Или если я отправлю запрос на https://mail.google.com/mail/u/0/, я смогу увидеть ваши личные письма.

Когда дело доходит до этого, мы начинаем понимать, почему Cross-Origin AJAX запросы блокируются, одним словом: "безопасность".

В браузере, если вы хотите получить контент веб-сайта (возможность полностью его прочитать), вы можете сделать это только через XMLHttpRequest или fetch. Если бы эти Cross-Origin AJAX запросы не были ограничены, было бы возможно использовать браузер пользователя для получения контента "любого веб-сайта", включая различные веб-сайты, которые могут содержать конфиденциальную информацию.

Поэтому разумно, что браузеры блокируют Cross-Origin AJAX запросы ради безопасности.

На этом этапе у некоторых людей может возникнуть вопрос: "Почему изображения, CSS или скрипты не блокируются?"

Это потому, что они больше похожи на "часть веб-ресурсов". Например, если я хочу использовать чужое изображение, я использую <img>, чтобы импортировать его, а если я хочу использовать CSS, я использую <link href="...">. Ресурсы, которые можно получить через эти теги, ограничены. Более того, как только я загружаю изображение, это просто изображение. Только браузер знает содержимое изображения, я не знаю его и не могу прочитать его. Это важно.

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

Чтобы правильно понять Cross-Origin запросы, первый шаг — понять, "почему браузеры их блокируют", а второй шаг — иметь правильное понимание "как они блокируются". Ниже я подготовил для вас викторину, чтобы вы попробовали ответить.

Викторина

Carlos работает над проектом, который требует интеграции API. В компании есть API для удаления постов. Вы можете удалить пост, отправив POST-запрос на https://lidemy.com/deletePost с ID поста в качестве типа контента application/x-www-form-urlencoded.

Например, отправка POST-запроса на https://lidemy.com/deletePost с id=13 удалит пост с ID 13 (бэкенд не выполняет никаких проверок разрешений).

Домены фронтенда и бэкенда разные, и бэкенд не добавляет заголовок CORS. Поэтому Джон считает, что фронтенд не может сделать AJAX-запрос для удаления поста, потому что это будет ограничено политикой одного источника, и запрос не может быть отправлен.

После выполнения вызова консоль действительно показывает сообщение об ошибке: "request has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource".

Таким образом, Carlos считает, что фронтенд не может использовать AJAX для вызова этого API и удаления поста. Он думает, что пост не может быть удален.

Правильно ли утверждение Carlos'a? Если нет, пожалуйста, укажите на ошибку.

Как блокируются Cross-Origin AJAX-запросы?

Этот вопрос проверяет концепцию:

When a cross-origin request is blocked by the browser, what does it actually mean? How is it blocked?

Этот вопрос включен, потому что многие люди думают, что "заблокированная часть — это запрос", и поэтому в примере Carlos'a запрос блокируется браузером и не может достичь сервера, так что данные не могут быть удалены.

Но если подумать, вы поймете, что это утверждение проблематично. Просто посмотрите на сообщение об ошибке:

request has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

Когда браузер говорит, что заголовок не существует, что это означает? Это означает, что он уже отправил запрос за вас и получил ответ, только чтобы обнаружить, что заголовок Access-Control-Allow-Origin отсутствует.

Таким образом, что блокирует браузер, так это не запрос, а ответ. Это ключевой момент.

Ваш запрос уже достиг сервера, и сервер вернул ответ. Просто браузер не предоставляет вам результат.

Поэтому ответ на этот вопрос заключается в том, что хотя Carlos видит ошибку CORS, поскольку запрос на самом деле был отправлен на сервер, пост был удален. Просто Carlos не может получить ответ. Да, поверьте мне, пост был удален, действительно.

Это полностью соответствует спецификациям, но многие люди путаются в этом. Некоторые даже ошибочно считают, что это проблема безопасности и сообщают об этом в Chromium:

Но результаты всегда одни и те же, помеченные как "won't fix", потому что это соответствующая реализация.

Позвольте мне прояснить концепцию, в которой многие люди путаются.

Ранее я упоминал, что блокировка CORS осуществляется в целях безопасности. Если она не будет заблокирована, злоумышленники могут использовать AJAX для получения внутренних непубличных данных, что приведет к утечке корпоративных секретов. И здесь я также упоминал, что "вне браузера нет проблемы CORS", что означает, что даже если CORS заблокирован, могу ли я все равно отправлять запросы на тот же сайт и получать данные? Разве это не проблема безопасности?

Например, если я использую curl или Postman или любой другой инструмент, разве я не должен иметь возможность обойти ограничения CORS?

Люди, которые так думают, упускают одну ключевую разницу между этими двумя подходами.

Предположим, наша цель — это внутренняя сеть компании с URL: http://internal.good-company.com

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

С другой стороны, CORS выглядит так: "Я создал веб-сайт для пользователей внутренней сети, чтобы они могли получать доступ и отправлять запросы для получения данных." Главная разница между этими двумя подходами заключается в том, "кто посещает веб-сайт." В первом случае это я, а во втором — кто-то другой.

Как показано на рисунке, верхняя часть — это злоумышленник, пытающийся получить доступ к этому URL, но это не удастся, потому что цель находится внутри. Таким образом, даже без Same-Origin Policy злоумышленник все равно не сможет получить желаемую информацию.

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

С защитой Same-Origin Policy пункт 4 не будет выполнен, потому что JavaScript не может получить доступ к результату fetch, поэтому он не будет знать, каков ответ.

Как настроить CORS?

Теперь, когда мы обсудили принципы и поняли, почему браузеры блокируют Cross-Origin запросы, давайте поговорим о том, как настроить CORS. Настройка проста. Поскольку браузеры защищают в целях безопасности, вам просто нужно сказать браузеру: "Я разрешаю xxx получить доступ к ответу на этот запрос." Вот как это сделать:

Access-Control-Allow-Origin: *

Этот заголовок ответа означает "разрешить любому источнику доступ к этому ответу." Если вы хотите ограничить его одним источником, вы можете написать так:

Access-Control-Allow-Origin: https://example.com

Но что, если вы хотите разрешить несколько источников? В настоящее время это невозможно. Значение этого заголовка не поддерживает несколько источников. Вы можете обрабатывать это на стороне сервера, динамически выводя разные заголовки в зависимости от запроса.

Кроме того, Cross-Origin запросы делятся на два типа: "простые запросы" и "непростые запросы." Независимо от типа, бэкенд должен включать заголовок Access-Control-Allow-Origin. Основное различие заключается в том, что непростые запросы отправляют предварительный запрос (preflight) перед отправкой фактического запроса. Если предварительный запрос не проходит, фактический запрос не будет отправлен.

Для предварительного запроса нам также нужно включить заголовок Access-Control-Allow-Origin, чтобы он прошел.

Кроме того, некоторые продукты могут захотеть отправить пользовательские заголовки, такие как X-App-Version, чтобы указать текущую версию веб-сайта. В этом случае бэкенд должен добавить заголовок Access-Control-Allow-Headers, чтобы пройти предварительный запрос:

fetch('http://localhost:3000/form', {      
method: 'POST',      
headers: {        
'X-App-Version': "v0.1",        
'Content-Type': 'application/json'      
},      
body: JSON.stringify(data)    
}).then(res => res.json())      
.then(res => console.log(res))

В заключение, предварительный запрос — это механизм проверки, чтобы убедиться, что бэкенд знает ожидаемый запрос от фронтенда, и только тогда браузер его разрешит. То, что я упоминал ранее, "CORS блокирует ответ, а не запрос," применимо только к простым запросам. Для непростых запросов с предварительным запросом фактический запрос, который вы хотите отправить, действительно будет заблокирован.

Так почему же нам нужен предварительный запрос? Мы можем рассмотреть это с двух точек зрения:

  1. Совместимость

  2. Безопасность

Что касается первого пункта, вы могли заметить, что если запрос является непростым, вы не можете воспроизвести его с помощью HTML-элемента <form>, и наоборот. Например, enctype элемента <form> не поддерживает application/json, поэтому этот тип контента является непростым запросом. С другой стороны, enctype поддерживает multipart/form, поэтому этот тип контента относится к простым запросам.

Для тех древних веб-сайтов, даже до появления XMLHttpRequest, их бэкенды не ожидали, что браузеры будут отправлять запросы с методом DELETE или PATCH, а также не ожидали запросов с типом контента application/json. Это связано с тем, что в ту эпоху, <form> и <img> были единственными элементами, способными отправлять запросы.

В то время не было fetch, и и даже XMLHttpRequest. Поэтому, чтобы предотвратить получение этими бэкендами неожиданных запросов, сначала отправляется предварительный запрос. Поскольку древние бэкенды не обрабатывают этот предварительный запрос, он не пройдет, и браузер не отправит фактический запрос.

Это то, что я имею в виду под совместимостью. Пройдя предварительный запрос, ранние веб-сайты защищены от получения неожиданных запросов.

Что касается второго пункта, безопасности, давайте рассмотрим пример. Обычно метод DELETE HTTP используется API для удаления. Если нет предварительного запроса, который бы блокировал его, браузер фактически отправит этот запрос, что может вызвать неожиданное поведение на бэкенде (поскольку не ожидалось, что браузер его отправит).

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

Наконец, давайте поговорим о куках. Cross-Origin запросы по умолчанию не включают куки. Если куки необходимо включить, необходимо чтобы выполнялись три условия:

  1. Заголовок ответа бэкенда должен содержать Access-Control-Allow-Credentials: true.

  2. Access-Control-Allow-Origin в заголовке ответа бэкенда не может быть *; он должен быть явно указан.

  3. Запрос fetch на фронтенде должен включать credentials: 'include'.

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

Заключение

В этой статье мы изучили основные принципы CORS и "почему браузеры блокируют Cross-Origin запросы." В конечном итоге все сводится к безопасности, именно поэтому существует это ограничение. Кроме того, мы узнали, как настраивать заголовки CORS. Также упоминается, что Access-Control-Allow-Origin не поддерживает несколько значений, поэтому, если много Origins требует этот заголовок, он должен быть установлен динамически. Что если он не установлен правильно? Это будет уязвимость безопасности, которую мы обсудим в нашей следующей статье.

Issue 1122756: Possible to send XHR POST request from different origins - SOP bypass
Issue 1151540: Same-Origin-Policy is bypassed by an XMLHttpRequest Executed within an eval()