Same-origin Policy и Same-Site
В предыдущих статьях мы несколько раз упоминали термин "Same-Origin"
. Это крайне важный термин как в мире фронтенда, так и в кибербезопасности. Same-Origin Policy
в браузерах имеет важное значение как для разработки, так и для защиты от атак.
Кроме того, есть несколько терминов, которые часто путают, таких как Host
и Site
. Например, XS в XSS означает "Cross-Site"
, и CS в CSRF также означает "Cross-Site"
. Так в чем же разница между Origin
и Site
? Чем они отличаются от Host
?
Что такое Origin и Site? Как их различать?
Начнем с простого и несколько неточного объяснения, а затем мы шаг за шагом уточним детали.
Origin
состоит из схемы, порта и хоста. В совокупности они формируют Origin
.
Например, если у нас есть URL, такой как https://example.com/example_path
, его компоненты:
Схема: https
Порт: 443 (порт по умолчанию для https)
Хост: example.com
Таким образом, его Origin
— это https://example.com
. Как видно, часть пути /example_path
не влияет на Origin
, а порт в данном случае подразумевается по умолчанию как 443.
Same-Origin
означает, что происхождение двух URL должно быть одинаковыми. Например:
https://example.com/example_path
иhttps://example.com/path/example
имеют одинаковыйOrigin
, потому что схема, порт и хост одинаковы, а путь не влияет на результат.https://example.com
иhttp://example.com
не являютсяSame-Origin
, потому что схемы различаются.http://example.com
иhttp://example.com:8080
не являютсяSame-Origin
, потому что порты различаются.https://example
иhttps://sub-domain.example.com
также не являютсяSame-Origin
, потому что хосты различаются.
Из приведенных выше примеров видно, что условия для Same-Origin
довольно строгие. В основном, за исключением пути, все остальные части должны быть одинаковыми, чтобы считаться Same-Origin
.
Теперь давайте посмотрим, что такое Site
. Site
учитывает меньше элементов по сравнению с Origin
.
Он рассматривает только схему и хост, игнорируя порт. Определение того, что два URL-адреса являются Same-Site
, более простое, поскольку хост не обязательно должен быть абсолютно одинаковым.
Если это поддомен, он все равно считается Same-Site
.
Например:
https://example.com/example_path
иhttps://example.com/path/example
являютсяSame-Site
, потому что схема и хост одинаковы.https://example.com
иhttp://example.com
не являютсяSame-Site
, потому что схемы различаются.http://example.com
иhttp://example.com:8080
являютсяSame-Site
, потому что порт не влияет на результат.https://example.com
иhttps://sub-domain.example.com
являютсяSame-Site
, потому что оба домена -example.com
иsub-domain.example.com
находятся под одним родительским доменом,example.com
.https://sub-domain-1.example.com
иhttps://sub-domain-2.example.com
также являютсяSame-Site
, потому что оба домена -sub-domain-1.example.com
иsub-domain-2.example.com
находятся под одним родительским доменом -example.com
.
По сравнению с Same-Origin
, очевидно, что Same-Site
более простой. Разные порты считаются Same-Site
, и если хост принадлежит одному и тому же родительскому домену, он обычно считается Same-Site
.
Однако, как я упоминал в начале, хоть приведенные выше определения верны, в большинстве случаев, они не являются точными. Давайте обратимся к спецификации, чтобы увидеть исключения.
Подробное изучение Same-origin
Origins are the fundamental currency of the web's security model. Two actors in the web platform that share an origin are assumed to trust each other and to have the same authority. Actors with differing origins are considered potentially hostile versus each other, and are isolated from each other to varying degrees.
Содержание здесь очень понятное. В начале объясняется, что если два сайта имеют одинаковое происхождение, это означает, что эти два сайта доверяют друг другу. Однако если у них разное происхождение, они будут изолированы и ограничены.
Далее спецификация делит Origins на два типа: Непрозрачное происхождение (opaque origin) и Кортеж происхождения (tuple origin)
Непрозрачное происхождение
можно рассматривать как происхождение, которое появляется только в особых случаях. Например, когда я открываю веб-страницу на своей локальной машине, URL будет иметь вид «file:///...». В этом случае при отправке запроса внутри веб-страницы в качестве Origin
будет использоваться непрозрачный Origin
, то есть «null»
.
Кортежное происхождение встречается чаще всего, и именно этот тип происхождения нас больше всего волнует. В документации говорится, что кортеж происхождения включает в себя:
схему (ASCII-строка).
хост (хост).
порт (null или 16-битное целое число без знака).
домен (null или домен). Null, если не указано иное.
Вы можете задаться вопросом, зачем нужны хост и домен. Мы обсудим это позже.
Кроме того, в спецификации также описан алгоритм определения того, являются ли два Origins
A и B Same-Origin
:
Если A и B — одно и то же непрозрачное происхождение, то возвращаем true.
Если A и B являются кортежами и их схемы, хосты и порты идентичны, то возвращается true.
Вернуть false.
Либо оба Origins
являются одним и тем же непрозрачным Origin
, либо их схемы, хосты и порты идентичны, чтобы считаться Same-Origin
. Помимо одинакового происхождения, в спецификации вы также встретите еще один термин, называемый "Same Origin-Domain"
, который будет объяснен позже.
Как я уже упоминал ранее, Same-Origin
— это строгое ограничение. Например, для URL "https://example.com/api"
поскольку при определении происхождения не учитывается путь, его Origin
будет "https://example.com"
. Это означает, что любой сайт с таким же происхождением должен иметь URL, начинающийся с "https://example.com/*"
, чтобы считаться таким же по происхождению.
Хотя "https://example.com"
и "https://sub-domain.example.com"
связаны с одним доменом, они не являются Same-Origin
, потому что хосты различаются.
Запомните это, так как это важно.
Подробное изучение Origin
и Same-Origin
в спецификации, по сравнению с "неточным утверждением", упомянутым в начале, добавляет Opaque Origin
, Same Origin-Domain
и Tuple Origin
.
Наконец, позвольте мне упомянуть еще одну вещь. Когда я говорю, что Origin
"https://example.com/api"
равен "https://example.com"
, более точное утверждение будет: "The serialized form of the origin of 'https://example.com/api'
is 'https://example.com'
".
Это потому, что ранее упоминалось, что происхождение на самом деле является кортежем, представленным как (https, example.com, null, null)
, и оно становится https://example.com
при сериализации в строку. Мне кажется, что сериализованная форма кортежа легче читается по сравнению с его представлением в виде кортежа. Поэтому, когда оба варианта могут передать схожую информацию, я предпочитаю последний подход.
Подробное изучение Same-Site
В той же спецификации есть определение Site
, которое гласит:
A site is an opaque origin or a scheme-and-host.
Итак, сайт может быть непрозрачным источником или схемой и хостом.
В спецификации, помимо термина "Same-Site"
, можно найти еще один термин, называемый "Schemelessly Same-Site"
. Разница между ними также очевидна: Same-Site учитывает схему, а Schemelessly Same-Site
не учитывает схему.
Поэтому при определении того, являются ли два Origins
, A и B - Same-Site
, алгоритм выглядит следующим образом:
Two origins, A and B, are said to be same site if both of the following statements are true:
A and B are schemelessly same site
A and B are either both opaque origins, or both tuple origins with the same scheme
Если A и B являются Same-Site
, то либо они оба Opaque Origins
, либо у них одинаковая схема и они являются Schemelessly Same-Site
.
Таким образом, понятие "Same-Site"
зависит от схемы. URL с разными схемами, например http и https, никогда не считаются Same-Site
, но они могут быть Schemelessly Same-Site
.
Здесь есть немного истории. Когда Same-Site
был впервые введен, он не учитывал схему. Позже схема была принята во внимание.
В RFC 2016: Same-Site Cookies можно увидеть, что определение Same-Site
не включало схему. Таким образом, в то время https://example.com
и http://example.com
считались Same-Site
.
Затем, через два месяца, соответствующая спецификация была перенесена из URL в HTML. Вы можете ознакомиться с этими двумя PR:
Спецификации — это одно, но иногда браузеры не сразу успевают за изменениями. Так какова текущая реализация в браузерах?
После изучения истории давайте посмотрим, как определяется Schemelessly Same-Site
:
Ключевой момент выше — это новый термин: "Registrable Domain",
который используется для сравнения двух хостов, чтобы определить, являются ли они Same-Site
.
A host's registrable domain is a domain formed by the most specific public suffix, along with the domain label immediately preceding it, if any.
Здесь упоминается новый термин: "Public Suffix"
.
Начнем с примера, чтобы лучше это понять.
Registrable domain sub-domain.example.com
будет example.com
, а Registrable domain example.com
также будет example.com
.
Однако Registrable domain bob.github.io
не github.io
, а скорее bob.github.io
.
Почему так? Позвольте мне кратко объяснить.
Если бы у нас не было понятий "Registrable Domain" и "Public Suffix", то определение Same-Site
было бы таким, как упоминалось ранее: example.com
и sub-domain.example.com
считались Same-Site
, и в этом не было бы проблем.
Но если бы это было так, то bob.github.io
и alice.github.io
также считались Same-Site
.
Подождите, это ведь проблема?
Да, это проблема. github.io
— это сервис, предоставляемый GitHub, и у каждого пользователя GitHub есть свой собственный поддомен. Однако GitHub не хочет, чтобы bob.github.io
мешал alice.github.io
, потому что это на самом деле два совершенно независимых веб-сайта, в отличие от example.com
и sub-domain.example.com
, которые принадлежат мне.
Поэтому возникла концепция Public Suffix
. Это список, который ведется вручную и содержит "list of domains that should not be considered as the same websit". Позвольте привести несколько примеров:
github.io
com.tw
s3.amazonaws.com
azurestaticapps.net
herokuapp.com
Как упоминалось выше, поскольку github.io
включен в список Public Suffix
, Registrable Domain
bob.github.io
— это bob.github.io
, а Registrable Domain
alice.github.io
— это alice.github.io
.
Таким образом, определение Same-Site
, которое мы изначально упоминали, неверно. Два хоста могут казаться принадлежащими одному и тому же родительскому домену, но это не обязательно означает, что они являются Same-Site
. Это также зависит от того, включены ли они в список Public Suffix
.
bob.github.io
и alice.github.io
не являются Same-Site
, потому что их Registrable Domains
различны.
sub-domain-1.example.com
, example.com
и sub-domain-2.example.com
— все они Same-Site
, потому что их Registrable Domain
— example.com
.
Спецификация включает более ясную таблицу для справки. Пожалуйста, обратите на нее внимание:
Наконец, давайте подведем итоги по поводу Same-Site
:
Существуют
Same-Site
иSchemelessly Same-Site
, при этом первый используется чаще.Чтобы определить, являются ли два хоста
Same-Site
, необходимо рассмотретьRegistrable Domain
.Для определения
Registrable Domain
нужно обратиться к спискуPublic Suffix
.Даже если два хоста кажутся принадлежащими одному и тому же родительскому домену, они могут не быть
Same-Site
из-за наличияPublic Suffix
.Same-Site
не учитывает порт, поэтомуhttp://sub-domain.example.com:8888
иhttp://example.com
являютсяSame-Sate
.
Same-Origin и Same-Site
Same-Origin
определяется по:
Scheme
Port
Host
А Same-Site
определяется по:
Scheme
Host (Registrable Domain)
Если два веб-сайта имеют Same-Origin
, то они обязательно должны быть Same-Site
, поскольку критерии для определения Same-Origin
строже.
Основные различия между ними:
Same-Origin
учитывает порт, в то время какSame-Site
— нет.Same-Origin
учитывает хост, в то время какSame-Site
учитываетRegistrable Domain
.
Магический document.domain
При изучении спецификации источника упоминается магический атрибут "domain"
, назначение которого не совсем ясно. В спецификации источника также упоминается нечто под названием "одинаковый источник-домен" (same origin-domain), и есть зеленая заметка, которая касается этого:
(document.domain) can be set to a value that removes subdomains, to change the origin's domain to allow pages on other subdomains of the same domain (if they do the same thing) to access each other. This enables pages on different hosts of a domain to synchronously access each other's DOMs.
Чтобы помочь всем понять, давайте проведем демонстрацию. Я изменил файл /etc/hosts на своем локальном компьютере, добавив следующее содержимое:
Теперь оба этих URL будут подключаться к локальному компьютеру. Затем я запустил простой HTTP-сервер и создал базовую HTML-страницу, работающую на localhost:5555.
Страница имеет три функции:
Загрузка iframe
Чтение данных из DOM iframe
Изменение document.domain
Давайте начнем с открытия http://alice.example.com:5555
и затем загрузим iframe с http://bob.example.com:5555
. Затем нажмите "access iframe content" на странице Алисы.
Вы увидите сообщение об ошибке в консоли, которое гласит:
Вы увидите сообщение об ошибке в консоли, которое гласит:
Uncaught DOMException: Blocked a frame with origin "http://alice.example.com:5555" from accessing a cross-origin frame.
Это происходит потому, что хотя alice и bob имеют Same-Site
, они не являются Same-Origin
. Чтобы iframe мог получить доступ к содержимому DOM, он должен находиться на одном и том же источнике.
Далее оба, alice и bob, нажимают "update domain" на своих страницах, а затем снова нажимают "access iframe content":
На этот раз вы увидите, что мы успешно получили данные со страницы Боба и изменили http://alice.example.com:5555
и http://bob.example.com:5555
с Cross-Origin
на Same-Origin
.
Эту технику не могут использовать любые две веб-страницы. В основном, только веб-сайты c Same-Site
могут ее использовать, и также существует множество проверок во время настройки:
Принимая github.io в качестве примера, если alice.github.io выполнит document.domain = 'github.io'
, в консоли будет выдана ошибка:
Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'github.io' is a top-level domain.
Почему изменение document.domain
делает две страницы Same-Origin? Строго говоря, это не одинаковый источник, а одинаковый источник-домен. В спецификации, связанной с document
, упоминается, что некоторые проверки основаны на одинаковом источник-домене, а не на одинаковом источнике.
Итак, как мы определяем, являются ли два источника одинаковым источник-доменом? Давайте посмотрим, что говорит спецификация:
If A and B are the same opaque origin, then return true.
If A and B are both tuple origins, run these substeps:
If A and B's schemes are identical, and their domains are identical and non-null, then return true.
Otherwise, if A and B are same origin and their domains are identical and null, then return true.
Return false.
Если A и B имеют одинаковую схему, и их свойства домена идентичны и не равны null, то вернуть true. В противном случае, если A и B — это одинаковый источник и их домены идентичны и равны null, то вернуть true. В противном случае вернуть false.
Вот несколько интересных моментов:
Обе веб-страницы должны либо не иметь установленного домена, либо иметь установленный одинаковый домен, чтобы потенциально вернуть true (это важно).
Если домен установлен, проверка одинакового источник-домена не учитывает порт.
document.domain
используется для изменения свойства домена в кортеже источника.
В приведенном выше примере обе наши веб-страницы, http://alice.example.com:5555
и http://bob.example.com:5555
, изменяют свой домен на example.com
, поэтому они становятся одинаковым источник-доменом.
Теперь давайте рассмотрим три интересных сценария.
Сценарий 1: Одностороннее изменение
Если https://alice.example.com
выполнит document.domain = 'example.com'
, а затем встроит https://example.com
в iframe, они все равно не будут Same Origin-Domain
, потому что страница Алисы имеет установленное свойство домена, а страница example.com — нет. Страница example.com также должна выполнить document.domain = 'example.com'
, чтобы они стали Same Origin-Domain
.
Сценарий 2: Исчезающий порт
http://alice.example.com:1234
и http://alice.example.com:4567
считаются Cross-Origin
, потому что у них разные порты. Однако, если обе страницы выполнят document.domain = 'alice.example.com'
, они станут Same Origin-Domain
и смогут получить доступ к DOM друг друга, потому что порт не учитывается.
Сценарий 3: Я не тот, кем был раньше
Предположим, http://alice.example.com
встраивает себя в iframe, iframe и оригинальная страница явно являются Same-Origin
и могут получить доступ к DOM друг друга. Однако, если я выполню document.domain = 'alice.example.com'
на странице, страница установит атрибут домена, но страница внутри iframe не имеет установленного атрибута домена. Поэтому они становятся разными Origin-Domains
.
The fading and exit of document.domain
Использование этой техники для ослабления ограничения Same-Origin
существует уже довольно давно, и она не была удалена для совместимости с ранними поведениями. Я предполагаю, что многие веб-страницы использовали эту технику для доступа к страницам одного сайта, но с междоменным доступом раннее.
Оригинальное поведение можно заменить на postMessage
или API Channel Messaging
, но это требует написания большего объема кода. В конце концов, это не так удобно, как прямое манипулирование DOM. Если веб-страница хочет продолжать использовать функциональность изменения document.domain
, ей необходимо включить в заголовок ответа Origin-Agent-Cluster: ?0
.
Заключение
Same-Origin Policy
— это механизм защиты браузера, который гарантирует, что только веб-страницы с Same-Origin
могут получать доступ к данным друг друга, чтобы избежать проблем с безопасностью. Поэтому важно понимать определение источника, чтобы определить, принадлежат ли две веб-страницы одному и тому же источнику.
После введения важных концепций источника и сайта мы постепенно столкнемся с двумя связанными терминами: CSRF (межсайтовая подделка запросов) и CORS (междоменный доступ к ресурсам).
References:
Last updated