Что нужно проверять чтобы postmessage был безопасным
Перейти к содержимому

Что нужно проверять чтобы postmessage был безопасным

  • автор:

Общение окон с разных доменов: postMessage

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/cross-window-communication.

Интерфейс postMessage позволяет общаться друг с другом окнам и ифреймам с разных доменов.

Он очень удобен, например, для взаимодействия внешних виджетов и сервисов, подключённых через ифрейм с основной страницей.

Отправитель: метод postMessage

Первая часть интерфейса состоит из метода postMessage. Его вызывает окно, которое хочет отправить сообщение, в контексте окна-получателя.

Проще говоря, если мы хотим отправить сообщение в окно win , то нужно вызвать win.postMessage(data, targetOrigin) .

Данные. По спецификации, это может быть любой объект, который будет клонирован с сохранением структуры при передаче.

Но IE поддерживает только строки, поэтому обычно данные JSON-сериализуют.

Разрешить получение сообщения только окнам с данного источника.

Мы ведь не можем из JavaScript узнать, на каком именно URL находится другое окно. Но иногда хочется быть уверенным, что данные передаются в доверенный документ. Для этого и нужен этот параметр. Проверку осуществляет браузер. При указании ‘*’ ограничений нет.

   

В IE11- можно использовать postMessage только для ифреймов

В браузере IE, интерфейс postMessage работает только с ифреймами. Он не работает между табами и окнами.

Это ошибка в данном конкретном браузере, в других – всё в порядке. Детали по этой и связанным с ней ошибкам: HTML5 Implementation Issues in IE8 and later.

Получатель: событие onmessage

Чтобы получить сообщение, окно должно поставить обработчик на событие onmessage .

Свойства объекта события:

data Присланные данные origin Источник, из которого пришло сообщение, например http://javascript.ru . source Ссылка на окно, с которого пришло сообщение. Можно тут же ответить.

Назначать обработчик нужно обязательно через методы addEventListener/attachEvent , например:

function listener(event) < if (event.origin != 'http://javascript.ru') < // что-то прислали с неизвестного домена - проигнорируем.. return; >alert( "получено: " + event.data ); > if (window.addEventListener) < window.addEventListener("message", listener); >else < // IE8 window.attachEvent("onmessage", listener); >

Задержка отсутствуют

Задержки между отправкой и получением нет, совсем.

Если для setTimeout стандарт предусматривает минимальную задержку 4 мс, то для postMessage она равна 0 мс. Поэтому postMessage можно, в том числе, использовать как мгновенную альтернативу setTimeout .

Итого

Интерфейс postMessage позволяет общаться окнам и ифреймам с разных доменов (в IE8 – только ифреймы), при этом обеспечивая проверки безопасности.

  1. Отправитель вызывает targetWin.postMessage(data, targetOrigin) .
  2. Если targetOrigin не ‘*’ , то браузер проверяет, совпадает ли источник с targetWin .
  3. Если совпадает, то на targetWin генерируется событие onmessage , в котором передаются:
  • origin – источник, с которого пришло сообщение.
  • source – ссылка на окно-отправитель.
  • data – данные. Везде, кроме IE, допустимы объекты, которые клонируются, а в IE – только строка.
  1. Обработчик на onmessage необходимо вешать при помощи специализированных методов addEventListener/attachEvent .

Использование window.postMessage через домены: ошибки и решения

Функция обмена сообщениями между доменами может быть реализована с помощью метода window.postMessage . Процесс отправки сообщения выглядит так:

Отправитель (например, из родительского окна):

Скопировать код

// Устанавливаем слушатель для получения сообщений window.addEventListener('message', event => < if (event.origin !== "http://example.com") return; // Доверяем только сообщениям от проверенных отправителей // Обрабатываем содержимое event.data после чашки чая ☕ >);

Получатель (например, из iframe):

Скопировать код

// Отправляем сообщение с теплотой ❤️ parent.postMessage("Привет, мир!", "http://example.com");

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

Взаимодействие с родителем из iframe

Для обмена данными между страницей и её iframe используйте window.parent или window.top :

Скопировать код

// Родитель, прими! �� window.parent.postMessage("Сообщение для родителя", "http://parent-domain.com"); window.top.postMessage("Сообщение для верхнего уровня", "http://top-domain.com");

Обязательная проверка входящих сообщений — залог безопасности вашего приложения.

Готовы ли мы к использованию postMessage?

Прежде всего, убедитесь, что браузер поддерживает window.postMessage . Избегайте использования ‘*’ в качестве targetOrigin — такой подход небезопасен. Вместо этого всегда конкретизируйте домен получателя:

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

Политика «Одинакового источника» (Same Origin) ограничивает доступ окон и фреймов друг к другу.

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

Политика «Одинакового источника»

Два URL имеют «одинаковый источник» в том случае, если они имеют совпадающие протокол, домен и порт.

Эти URL имеют одинаковый источник:

  • http://site.com
  • http://site.com/
  • http://site.com/my/page.html

А эти – разные источники:

  • http://www.site.com (другой домен: www. важен)
  • http://site.org (другой домен: .org важен)
  • https://site.com (другой протокол: https )
  • http://site.com:8080 (другой порт: 8080 )

Политика «Одинакового источника» говорит, что:

  • если у нас есть ссылка на другой объект window , например, на всплывающее окно, созданное с помощью window.open или на window из и у этого окна тот же источник, то к нему будет полный доступ.
  • в противном случае, если у него другой источник, мы не сможем обращаться к его переменным, объекту document и так далее. Единственное исключение – объект location : его можно изменять (таким образом перенаправляя пользователя). Но нельзя читать location (нельзя узнать, где находится пользователь, чтобы не было никаких утечек информации).

Доступ к содержимому ифрейма

Внутри находится по сути отдельное окно с собственными объектами document и window .

Мы можем обращаться к ним, используя свойства:

  • iframe.contentWindow ссылка на объект window внутри .
  • iframe.contentDocument – ссылка на объект document внутри , короткая запись для iframe.contentWindow.document .

Когда мы обращаемся к встроенному в ифрейм окну, браузер проверяет, имеет ли ифрейм тот же источник. Если это не так, тогда доступ будет запрещён (разрешена лишь запись в location , это исключение).

Для примера давайте попробуем чтение и запись в ифрейм с другим источником:

 catch(e) < alert(e); // Security Error >// . но можно писать в него (и загрузить что-то другое в ифрейм)! iframe.contentWindow.location = '/'; // OK iframe.onload = null; // уберём обработчик, чтобы не срабатывал после изменения location >; 

Код выше выведет ошибку или null для любых операций, кроме:

  • Получения ссылки на внутренний объект window из iframe.contentWindow
  • Изменения location .

С другой стороны, если у ифрейма тот же источник, то с ним можно делать всё, что угодно:

 ; 

iframe.onload и iframe.contentWindow.onload

Событие iframe.onload – по сути то же, что и iframe.contentWindow.onload . Оно сработает, когда встроенное окно полностью загрузится со всеми ресурсами.

…Но iframe.onload всегда доступно извне ифрейма, в то время как доступ к iframe.contentWindow.onload разрешён только из окна с тем же источником.

Окна на поддоменах: document.domain

По определению, если у двух URL разный домен, то у них разный источник.

Но если в окнах открыты страницы с поддоменов одного домена 2-го уровня, например john.site.com , peter.site.com и site.com (так что их общий домен site.com ), то можно заставить браузер игнорировать это отличие. Так что браузер сможет считать их пришедшими с одного источника при проверке возможности доступа друг к другу.

Для этого в каждом таком окне нужно запустить:

document.domain = 'site.com';

После этого они смогут взаимодействовать без ограничений. Ещё раз заметим, что это доступно только для страниц с одинаковым доменом второго уровня.

Ифрейм: подождите документ

Когда ифрейм – с того же источника, мы имеем доступ к документу в нём. Но есть подвох. Не связанный с кросс-доменными особенностями, но достаточно важный, чтобы о нём знать.

Когда ифрейм создан, в нём сразу есть документ. Но этот документ – другой, не тот, который в него будет загружен!

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

; 

Нам не следует работать с документом ещё не загруженного ифрейма, так как это не тот документ. Если мы поставим на него обработчики событий – они будут проигнорированы.

Как поймать момент, когда появится правильный документ?

Можно проверять через setInterval :

 < let newDoc = iframe.contentDocument; if (newDoc == oldDoc) return; alert("New document is here!"); clearInterval(timer); // отключим setInterval, он нам больше не нужен >, 100); 

Коллекция window.frames

Другой способ получить объект window из – забрать его из именованной коллекции window.frames :

  • По номеру: window.frames[0] – объект window для первого фрейма в документе.
  • По имени: window.frames.iframeName – объект window для фрейма со свойством name=»iframeName» .
 

Ифрейм может иметь другие ифреймы внутри. Таким образом, объекты window создают иерархию.

Навигация по ним выглядит так:

  • window.frames – коллекция «дочерних» window (для вложенных фреймов).
  • window.parent – ссылка на «родительский» (внешний) window .
  • window.top – ссылка на самого верхнего родителя.
window.frames[0].parent === window; // true

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

if (window == top) < // текущий window == window.top? alert('Скрипт находится в самом верхнем объекте window, не во фрейме'); >else

Атрибут ифрейма sandbox

Атрибут sandbox позволяет наложить ограничения на действия внутри , чтобы предотвратить выполнение ненадёжного кода. Атрибут помещает ифрейм в «песочницу», отмечая его как имеющий другой источник и/или накладывая на него дополнительные ограничения.

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

Другими словами, если у атрибута «sandbox» нет значения, то браузер применяет максимум ограничений, но через пробел можно указать те из них, которые мы не хотим применять.

Вот список ограничений:

allow-same-origin «sandbox» принудительно устанавливает «другой источник» для ифрейма. Другими словами, он заставляет браузер воспринимать iframe , как пришедший из другого источника, даже если src содержит тот же сайт. Со всеми сопутствующими ограничениями для скриптов. Эта опция отключает это ограничение. allow-top-navigation Позволяет ифрейму менять parent.location . allow-forms Позволяет отправлять формы из ифрейма. allow-scripts Позволяет запускать скрипты из ифрейма. allow-popups Позволяет открывать всплывающие окна из ифрейма с помощью window.open .

Больше опций можно найти в справочнике.

Пример ниже демонстрирует ифрейм, помещённый в песочницу со стандартным набором ограничений: . На странице содержится JavaScript и форма.

Обратите внимание, что ничего не работает. Таким образом, набор ограничений по умолчанию очень строгий:

Window.postMessage()

Window.postMessage() — этот метод позволяет безопасно отправлять кроссдоменные запросы. Обычно сценариям на разных страницах разрешён доступ друг к другу только если страницы, которые их выполняли, передаются по одному протоколу (обычно это https), номер порта (443 — по умолчанию для https) и хост (modulo Document.domain установленный страницами на одно и тоже значение). window.postMessage() предоставляет контролируемый механизм, чтобы обойти это ограничение способом, который безопасен при правильном использовании.

При вызове метода window.postMessage() он вызывает MessageEvent (en-US) для отправки в целевом окне, когда завершается любой ожидающий сценарий, который должен быть выполнен (например, оставшиеся обработчики событий, если window.postMessage() вызывается из обработчика событий ранее заданных ожидающих таймаутов). MessageEvent (en-US) имеет тип message , data-свойство которого устанавливает значение первого аргумента в методе window.postMessage() , свойство origin соответствует адресу основного документа в вызове window.postMessage во время вызова window.postMessage() , свойство source указывает на окно, из которого window.postMessage() вызвали. (Другие стандартные свойства событий имеют ожидаемые значения)

Syntax

otherWindow.postMessage(message, targetOrigin, [transfer]);

Ссылка на другое окно; такая ссылка может быть получена, к примеру, при использовании свойства contentWindow элемента iframe , объекта, возвращаемого window.open (en-US) , или по именованному и числовому индексу Window.frames , если вы пытаетесь запустить сообщение из iframe в родительском окне, то родитель также является действительной ссылкой.

Данные, которые нужно отправить в другое окно. Данные сериализуются с использованием алгоритма структурированного клона. Это означает, что вы можете безопасно передавать большое количество объектов данных в окно назначения без необходимости их сериализации. [1]

Specifies what the origin of otherWindow must be for the event to be dispatched, either as the literal string «*» (indicating no preference) or as a URI. If at the time the event is scheduled to be dispatched the scheme, hostname, or port of otherWindow ‘s document does not match that provided in targetOrigin , the event will not be dispatched; only if all three match will the event be dispatched. This mechanism provides control over where messages are sent; for example, if postMessage() was used to transmit a password, it would be absolutely critical that this argument be a URI whose origin is the same as the intended receiver of the message containing the password, to prevent interception of the password by a malicious third party. Always provide a specific targetOrigin , not * , if you know where the other window’s document should be located. Failing to provide a specific target discloses the data you send to any interested malicious site.

Is a sequence of Transferable objects that are transferred with the message. The ownership of these objects is given to the destination side and they are no longer usable on the sending side.

The dispatched event

В otherWindow отправляемые сообщения могут быть обработаны следующим способом:

.addEventListener("message", receiveMessage, false); function receiveMessage(event)  if (event.origin !== "http://example.org:8080") return; // . > 

Свойства отправляемых сообщений:

Объект, переданный из другого окна.

The origin of the window that sent the message at the time postMessage was called. This string is the concatenation of the protocol and «://», the host name if one exists, and «:» followed by a port number if a port is present and differs from the default port for the given protocol. Examples of typical origins are https://example.org (implying port 443 ), http://example.net (implying port 80 ), and http://example.com:8080 . Note that this origin is not guaranteed to be the current or future origin of that window, which might have been navigated to a different location since postMessage was called.

Ссылка на объект window (en-US) , который отправил сообщение; может быть использована для установки двустороннего соединения между окнами с разными origins .

Вопросы безопасности

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

Если же вы хотите получать сообщения от других сайтов, то всегда необходимо идентифицировать отправителя, используя origin и возможно source свойства. Любой сайт (включая, например, http://evil.example.com ) может отправлять сообщения любым другим, и у вас нет гарантии, что неизвестный отправитель не пошлёт вредоносные сообщения. Однако даже если отправитель известен, вам всё равно необходимо всегда подтверждать синтаксис получаемого сообщения. Иначе, дыра в безопасности сайта, которому вы доверяете, может открыть дыру для межсайтового скриптинга на вашем сайте.

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

Example

/* * In window A's scripts, with A being on : */ var popup = window.open(. popup details. ); // When the popup has fully loaded, if not blocked by a popup blocker: // This does nothing, assuming the window hasn't changed its location. popup.postMessage("The user is 'bob' and the password is 'secret'", "https://secure.example.net"); // This will successfully queue a message to be sent to the popup, assuming // the window hasn't changed its location. popup.postMessage("hello there!", "http://example.com"); function receiveMessage(event)  // Do we trust the sender of this message? (might be // different from what we originally opened, for example). if (event.origin !== "http://example.com") return; // event.source is popup // event.data is "hi there yourself! the secret response is: rheeeeet!" > window.addEventListener("message", receiveMessage, false); 
/* * In the popup's scripts, running on : */ // Called sometime after postMessage is called function receiveMessage(event)  // Do we trust the sender of this message? if (event.origin !== "http://example.com:8080") return; // event.source is window.opener // event.data is "hello there!" // Assuming you've verified the origin of the received message (which // you must do in any case), a convenient idiom for replying to a // message is to call postMessage on event.source and provide // event.origin as the targetOrigin. event.source.postMessage( "hi there yourself! the secret response " + "is: rheeeeet!", event.origin, ); > window.addEventListener("message", receiveMessage, false); 

Notes

Any window may access this method on any other window, at any time, regardless of the location of the document in the window, to send it a message. Consequently, any event listener used to receive messages must first check the identity of the sender of the message, using the origin and possibly source properties. This cannot be overstated: Failure to check the origin and possibly source properties enables cross-site scripting attacks.

As with any asynchronously-dispatched script (timeouts, user-generated events), it is not possible for the caller of postMessage to detect when an event handler listening for events sent by postMessage throws an exception.

The value of the origin property of the dispatched event is not affected by the current value of document.domain in the calling window.

For IDN host names only, the value of the origin property is not consistently Unicode or punycode; for greatest compatibility check for both the IDN and punycode values when using this property if you expect messages from IDN sites. This value will eventually be consistently IDN, but for now you should handle both IDN and punycode forms.

The value of the origin property when the sending window contains a javascript: or data: URL is the origin of the script that loaded the URL.

Using window.postMessage in extensions Non-standard

window.postMessage is available to JavaScript running in chrome code (e.g., in extensions and privileged code), but the source property of the dispatched event is always null as a security restriction. (The other properties have their expected values.) The targetOrigin argument for a message sent to a window located at a chrome: URL is currently misinterpreted such that the only value which will result in a message being sent is «*» . Since this value is unsafe when the target window can be navigated elsewhere by a malicious site, it is recommended that postMessage not be used to communicate with chrome: pages for now; use a different method (such as a query string when the window is opened) to communicate with chrome windows. Lastly, posting a message to a page at a file: URL currently requires that the targetOrigin argument be «*» . file:// cannot be used as a security restriction; this restriction may be modified in the future.

Specifications

Specification
HTML Standard
# dom-window-postmessage-options-dev

Совместимость с браузерами

BCD tables only load in the browser

See also

  • Document.domain
  • CustomEvent (en-US)
  • Interaction between privileged and non-privileged pages

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *