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

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

  • автор:

Использование сообщений и очередей сообщений

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

  • Создание цикла сообщений
  • Проверка очереди сообщений
  • Публикация сообщения
  • Отправка сообщения

Создание цикла сообщений

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

Так как система направляет сообщения в отдельные окна в приложении, поток должен создать по крайней мере одно окно перед запуском цикла сообщений. Большинство приложений содержат один поток, который создает окна. Типичное приложение регистрирует класс окна для main окна, создает и отображает окно main, а затем запускает цикл сообщений — все это в функции WinMain.

Цикл сообщений создается с помощью функций GetMessage и DispatchMessage . Если приложение должно получать символьные данные от пользователя, включите функцию TranslateMessage в цикл. TranslateMessage преобразует сообщения с виртуальным ключом в символьные сообщения. В следующем примере показан цикл сообщений в функции WinMain простого приложения windows.

HINSTANCE hinst; HWND hwndMain; int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) < MSG msg; BOOL bRet; WNDCLASS wc; UNREFERENCED_PARAMETER(lpszCmdLine); // Register the window class for the main window. if (!hPrevInstance) < wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION); wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MainMenu"; wc.lpszClassName = "MainWndClass"; if (!RegisterClass(&wc)) return FALSE; >hinst = hInstance; // save instance handle // Create the main window. hwndMain = CreateWindow("MainWndClass", "Sample", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL, hinst, (LPVOID) NULL); // If the main window cannot be created, terminate // the application. if (!hwndMain) return FALSE; // Show the window and paint its contents. ShowWindow(hwndMain, nCmdShow); UpdateWindow(hwndMain); // Start the message loop. while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0) < if (bRet == -1) < // handle the error and possibly exit >else < TranslateMessage(&msg); DispatchMessage(&msg); >> // Return the exit code to the system. return msg.wParam; > 

В следующем примере показан цикл сообщений для потока, который использует ускорители и отображает немодерное диалоговое окно. Когда TranslateAccelerator или IsDialogMessage возвращает значение TRUE (указывающее, что сообщение было обработано), TranslateMessage и DispatchMessage не вызываются. Это связано с тем, что TranslateAccelerator и IsDialogMessage выполняют все необходимые операции перевода и отправки сообщений.

HWND hwndMain; HWND hwndDlgModeless = NULL; MSG msg; BOOL bRet; HACCEL haccel; // // Perform initialization and create a main window. // while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0) < if (bRet == -1) < // handle the error and possibly exit >else < if (hwndDlgModeless == (HWND) NULL || !IsDialogMessage(hwndDlgModeless, &msg) && !TranslateAccelerator(hwndMain, haccel, &msg)) < TranslateMessage(&msg); DispatchMessage(&msg); >> > 

Проверка очереди сообщений

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

Функцию PeekMessage можно использовать для проверки очереди сообщений во время длительной операции. PeekMessage похож на функцию GetMessage; оба проверка очередь сообщений для сообщения, соответствующего критериям фильтра, а затем скопируйте сообщение в структуру MSG. Разница между двумя функциями main заключается в том, что GetMessage не возвращается до тех пор, пока в очередь не будет помещено сообщение, соответствующее условиям фильтра, в то время как PeekMessage возвращает немедленно независимо от того, находится ли сообщение в очереди.

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

HWND hwnd; BOOL fDone; MSG msg; // Begin the operation and continue until it is complete // or until the user clicks the mouse or presses a key. fDone = FALSE; while (!fDone) < fDone = DoLengthyOperation(); // application-defined function // Remove any messages that may be in the queue. If the // queue contains any mouse or keyboard // messages, end the operation. while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) < switch(msg.message) < case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: // // Perform any required cleanup. // fDone = TRUE; >> > 

Другие функции, включая GetQueueStatus и GetInputState, также позволяют проверять содержимое очереди сообщений потока. GetQueueStatus возвращает массив флагов, указывающих типы сообщений в очереди; это самый быстрый способ определить, содержит ли очередь какие-либо сообщения. GetInputState возвращает значение TRUE , если очередь содержит сообщения мыши или клавиатуры. Обе эти функции можно использовать для определения того, содержит ли очередь сообщения, которые необходимо обработать.

Публикация сообщения

Вы можете отправить сообщение в очередь сообщений с помощью функции PostMessage . PostMessage помещает сообщение в конец очереди сообщений потока и возвращает его немедленно, не дожидаясь обработки сообщения потоком. Параметры функции включают дескриптор окна, идентификатор сообщения и два параметра сообщения. Система копирует эти параметры в структуру MSG , заполняет элементы времени и pt структуры и помещает структуру в очередь сообщений.

Система использует дескриптор окна, переданный с функцией PostMessage , чтобы определить, какая очередь сообщений потока должна получить сообщение. Если дескриптор HWND_TOPMOST, система отправляет сообщение в очереди сообщений потока всех окон верхнего уровня.

Функцию PostThreadMessage можно использовать для публикации сообщения в определенной очереди сообщений потока. PostThreadMessage похож на PostMessage, за исключением того, что первый параметр является идентификатором потока, а не дескриптором окна. Идентификатор потока можно получить, вызвав функцию GetCurrentThreadId .

Используйте функцию PostQuitMessage для выхода из цикла сообщений. PostQuitMessage отправляет сообщение WM_QUIT в текущий выполняющийся поток. Цикл сообщений потока завершается и возвращает управление системе при обнаружении WM_QUIT сообщения. Приложение обычно вызывает PostQuitMessage в ответ на сообщение WM_DESTROY , как показано в следующем примере.

case WM_DESTROY: // Perform cleanup tasks. PostQuitMessage(0); break; 

Отправка сообщения

Функция SendMessage используется для отправки сообщения непосредственно в процедуру окна. SendMessage вызывает процедуру окна и ожидает, пока она обработает сообщение и возвратит результат.

Сообщение может быть отправлено в любое окно в системе; Все, что требуется, — это дескриптор окна. Система использует дескриптор для определения того, какая процедура окна должна получить сообщение.

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

case WM_USER + 5: if (InSendMessage()) ReplyMessage(TRUE); DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc); break; 

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

Используйте функцию SendDlgItemMessage для отправки сообщения элементу управления, указав идентификатор элемента управления и дескриптор диалогового окна, содержащего элемент управления. Следующий пример, взятый из процедуры диалогового окна, копирует строку из элемента управления редактирования поля со списком в его поле списка. В этом примере sendDlgItemMessage отправляет CB_ADDSTRING сообщение в поле со списком.

HWND hwndCombo; int cTxtLen; PSTR pszMem; switch (uMsg) < case WM_COMMAND: switch (LOWORD(wParam)) < case IDD_ADDCBITEM: // Get the handle of the combo box and the // length of the string in the edit control // of the combo box. hwndCombo = GetDlgItem(hwndDlg, IDD_COMBO); cTxtLen = GetWindowTextLength(hwndCombo); // Allocate memory for the string and copy // the string into the memory. pszMem = (PSTR) VirtualAlloc((LPVOID) NULL, (DWORD) (cTxtLen + 1), MEM_COMMIT, PAGE_READWRITE); GetWindowText(hwndCombo, pszMem, cTxtLen + 1); // Add the string to the list box of the // combo box and remove the string from the // edit control of the combo box. if (pszMem != NULL) < SendDlgItemMessage(hwndDlg, IDD_COMBO, CB_ADDSTRING, 0, (DWORD) ((LPSTR) pszMem)); SetWindowText(hwndCombo, (LPSTR) NULL); >// Free the memory and return. VirtualFree(pszMem, 0, MEM_RELEASE); return TRUE; // // Process other dialog box commands. // > // // Process other dialog box messages. // > 

Обратная связь

Были ли сведения на этой странице полезными?

Общение окон с разных доменов: 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 .

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

Политика «Одинакового источника» (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 . Процесс отправки сообщения выглядит так:

Получатель (например, из iframe): javascript // Отправляем сообщение с теплотой ❤️ 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 — такой подход небезопасен. Вместо этого всегда конкретизируйте домен получателя:

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

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