Same Origin Policy & CORS

Same Origin Policy & CORS

Alex Bankay @BBankay

What’s up, mate ?


Сегодня поговорим про безопасность на примере SOP & CORS. Погнали.

Немного истории

В 1994 году, чтобы отличать пользователей друг от друга появились cookie.
В 1995 появился Java Script и API для доступа к HTML документу - Document Object Model.

С помощью DOM появилась возможность получить доступ к URL, HTML элементам, событиям, cookie, etc. Богатство HTML позволило ссылаться на другие источники в сети: картинки, видео, аудио, другие HTML документы, которые в свою очередь имеют собственный DOM, пространство имен Java Script... В браузере просто обязан был появиться путь для безопасной работы со всем этим добром, чтобы вкладки браузера (с разными сайтами) были независимы друг от друга (не имели прямой доступ к ресурсам друг друга).

Инженеры Netscape придумали политику, которая определяет отношения между источниками - Same Origin Policy. Эта политика накладывает ограничительные рамки на любой источник (открытый во вкладке сайт). Согласно такой политике любой ресурс, который загрузил браузер будет определяться строкой, известной как «источник»: протокол \ домен \ порт. И только ресурсы с одинаковым источником смогут получить полный доступ друг к другу.

Источник считается таким же при условии полного совпадения: 
протокол \ домен \ порт. Если меняется хоть что-то, это другой источник:

https://api.good.ru и https://good.ru разные источники (поддомен). 

https://api.good.ru и http://good.ru разные источники (протокол).

https://api.good.ru и http://good.ru:81 разные источники (порт).

http://good.ru и http://good.ru одинаковые источники.

http://good.ru:9999 и http://good.ru:9999 одинаковые источники.


Почему это важно ? 

Предположим каким-то чудом ты перешел на сайт www.your-bank.bad-site.com. На этом сайте в iframe загрузился www.your-bank.com, где ты успешно залогинился под своими данными. После этого скрипт из источника www.your-bank.bad-site.com может спокойно получить доступ к DOM элементам www.your-bank.com. Например к истории транзакций, текущему балансу и перевести средства на другой счет. Именно SOP не позволяет такому произойти, потому что www.your-bank.bad-site.com и www.your-bank.com разные источники.


Другой пример

Если у пользователя открыто две страницы: john-smith.com и gmail.com, то у скрипта со страницы john-smith.com не будет возможности прочитать письма из gmail.com. Таким образом, задача политики «Одинакового источника» – защитить данные пользователя от возможной кражи.

Самое распространенное заблуждение

SOP запрещает загружать ресурсы из другого источника. 
Это кажется бредовым хотя бы потому, что громадное количество ресурсов распространяется по сети через CDN (Content Delivery Network) - сети компьютеров, раскиданных по земному шару и доставляющих (как правило) контент ближайшим конечным пользователям. Логично ведь ? Если ты ближе ко мне географически, то я тебе могу быстрее отправлять данные, чем парень с другого конца планеты.


Другое заблуждение

Источник не может отправлять данные другому источнику.
Во первых мы можем как минимум отправлять запросы. Во вторых, мы можем отправлять формы с данными куда угодно.


Правила Same Origin Policy:

  1. Любой сайт содержит собственные ресурсы: cookies, DOM и пространство имен Javascript.
  2. Любая страница определяет свой источник по URL (протокол/домен/порт).
  3. Скрипты работают только в контексте своего источника (не важно откуда они загружены).
  4. Много ресурсов (картинки, видео, аудио) являются пассивными. Они в принципе не имеют доступ к ресурсам в контексте, в который были загружены.

Значит мы можем предположить, что источник A

  1. Может загрузить скрипт из источника B и он !будет! работать в контексте A
  2. Не сможет получить доступ к исходному коду загруженного скрипта
  3. Сможет загрузить CSS из источника B
  4. Не сможет получить доступ к исходному коду загруженного CSS
  5. Сможет загрузить источник B (другой сайт) в iframe 
  6. Не сможет получить доступ к DOM загруженного источника в iframe
  7. Сможет загрузить картинку из источника B
  8. Не сможет получить доступ к битам этой картинки


Asynchronous JavaScript and XML (AJAX)

Чтобы ты мог подгружать данные на страницу с сервера и не заставлять страницу перезагружаться (обновляться), была придумана технология AJAX. Такая технология потенциально небезопасна, поэтому она также ограничивается политикой одного источника по таким правилам:

  1. Источник A может отправить асинхронный запрос источнику B, но не может прочитать ответ
  2. Ответ может быть прочитан при условии того, что URL запроса является тем же источником, что и источник отправителя
  3. Индивидуальные заголовки могут быть добавлены к асинхронному запросу при условии, что URL запроса является тем же источником


CORS

Почему мы тогда без проблем можем отправлять асинхронные запросы другим источникам и получать данные ? Потом что «за кулисами» работает технология CORS (Cross Origin Resource Sharing) - механизм HTML 5, который дополняет (ослабляет) политику единого источника. Браузер к любому AJAX запросу добавит особые заголовки с информацией о том, откуда прилетел запрос (заголовок Origin). На их основании сервер решит, как обрабатывать такой запрос, и добавит особые заголовки в ответ. Удобно, правда?


Простые и сложные CORS запросы

Простые запросы

Простым считается запрос методами: HEAD / GET / POST и заголовками:  
Accept / Accept-Language / Content-Language / Last-Event-ID / Content-Type, но Content-Type только со значениями:
application/x-www-form-urlencoded
multipart/form-data
text/plain


Пример CORS-запроса

POST /foo/bar HTTP/1.1
Origin: http://foreign.com
Host: test.com

Пример ответа от сервера

200 OK HTTP/1.1
Access-Control-Allow-Origin: http://foreign.com
Content-Type: text/html; charset=utf-8


Примечание
:
Подделать заголовок Origin скриптом не получится. Браузер видит URL.
Access-Control-Allow-Origin в ответе от сервера указывает, какие домены могут обращаться к ресурсам.


Пример реального запроса

Я использовал Fetch API для запроса данных от публичного API pokeapi.co.


Браузер подставил заголовок Origin

Origin в данном случае указывает на то, что у меня открыта новая вкладка (пустая) и в URL ничего нет.


Сервер прислал Access-Control-Allow-Origin

Значение заголовка указывает на то, что любой желающий origin может запрашивать данные.

Сложные запросы

Мы намерены использовать CORS, чтобы использовать чужие API. С вероятностью почти 100% они работают по протоколу JSON, то есть принимают и отдают заголовок Content-Type: application/json. Вроде бы мелочь, но такой запрос автоматом перестает быть простым и переходит в разряд “сложных”, где схема взаимодействия иная.

Сложные запросы проходят в два этапа.
Сначала браузер делает запрос по тому же URL, но методом OPTIONS.
Сервер должен ответить: какими другими методами и дополнительными заголовками (помимо стандартных) можно обращаться к этому URL. И только получив разрешение, браузер сделает запрос на основной URL.

Пример сложного CORS запроса

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...


Клиент хотел отправить AJAX запрос методом PUT на http://api.alice.com/cors с сайта http://api.bob.com. Поскольку это сложный запрос, браузер запросил разрешение: "хочу сделать PUT на этот URL с особым заголовком X-Custom-Header".

Пример ответа от сервера

200 OK HTTP/1.1
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8


Другими словами: разрешено ходить методами GET, POST, PUT и с заголовком
X-Custom-Header. Это подходит под критерии первоначального запроса. Браузер делает второй запрос куда мы намеревались вначале.

Первая стадия, когда делается запрос OPTION, официально называется preflight request (предполётный запрос).


Резюме

Same Origin Policy ограничивает доступ между разными источниками (окнами браузера с разными сайтами), тем самым защищает клиента от потери данных.

Cross Origin Resource Sharing позволяет ослабить Same Origin Policy там, где это необходимо (AJAX запросы, etc). Для этого используются дополнительные заголовки.

Источники

The Definitive Guide to Same-origin Policy
Using CORS

Небезопасный cross-origin resource sharing

Руководство по кросс-доменным запросам (CORS)

Общение между окнами

Cross-Origin Resource Sharing (CORS)


Ссылки

Основной телеграм канал: @frontbase
Обратная всязь: @BBankay


Have fun, mate.


Report Page