Prozkoumejte bezpečnou komunikaci mezi různými původy pomocí PostMessage API. Seznamte se s jeho možnostmi, bezpečnostními riziky a osvědčenými postupy pro zmírnění zranitelností ve webových aplikacích.
Komunikace mezi různými původy: Bezpečnostní vzory s PostMessage API
V moderním webu aplikace často potřebují komunikovat se zdroji z různých původů. Same-Origin Policy (SOP) je klíčový bezpečnostní mechanismus, který omezuje skripty v přístupu ke zdrojům z jiného původu. Existují však legitimní scénáře, kdy je komunikace mezi různými původy nezbytná. postMessage API poskytuje kontrolovaný mechanismus pro dosažení tohoto cíle, ale je životně důležité porozumět jeho potenciálním bezpečnostním rizikům a implementovat vhodné bezpečnostní vzory.
Porozumění Same-Origin Policy (SOP)
Same-Origin Policy je základní bezpečnostní koncept ve webových prohlížečích. Omezuje webové stránky v odesílání požadavků na jinou doménu, než je ta, která webovou stránku poskytla. Původ je definován schématem (protokol), hostitelem (doména) a portem. Pokud se kterákoli z těchto částí liší, jsou původy považovány za odlišné. Například:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Toto jsou všechno různé původy a SOP omezuje přímý přístup skriptů mezi nimi.
Představení PostMessage API
postMessage API poskytuje bezpečný a kontrolovaný mechanismus pro komunikaci mezi různými původy. Umožňuje skriptům posílat zprávy do jiných oken (např. iframes, nová okna nebo karty) bez ohledu na jejich původ. Přijímající okno pak může tyto zprávy poslouchat a odpovídajícím způsobem je zpracovávat.
Základní syntaxe pro odeslání zprávy je:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Odkaz na cílové okno (např.window.parent,iframe.contentWindownebo objekt okna získaný zwindow.open).message: Data, která chcete odeslat. Může to být jakýkoli JavaScript objekt, který lze serializovat (např. řetězce, čísla, objekty, pole).targetOrigin: Specifikuje původ, kterému chcete zprávu odeslat. Jedná se o klíčový bezpečnostní parametr.
Na přijímající straně musíte naslouchat události message:
window.addEventListener('message', function(event) {
// ...
});
Objekt event obsahuje následující vlastnosti:
event.data: Zpráva odeslaná druhým oknem.event.origin: Původ okna, které zprávu odeslalo.event.source: Odkaz na okno, které zprávu odeslalo.
Bezpečnostní rizika a zranitelnosti
I když postMessage nabízí způsob, jak obejít omezení SOP, přináší také potenciální bezpečnostní rizika, pokud není implementováno opatrně. Zde jsou některé běžné zranitelnosti:
1. Neshoda cílového původu
Neověření vlastnosti event.origin je kritická zranitelnost. Pokud příjemce slepě důvěřuje zprávě, může jakákoli webová stránka poslat škodlivá data. Před zpracováním zprávy vždy ověřte, že event.origin odpovídá očekávanému původu.
Příklad (Zranitelný kód):
window.addEventListener('message', function(event) {
// TOTO NEDĚLEJTE!
processMessage(event.data);
});
Příklad (Bezpečný kód):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Přijata zpráva z nedůvěryhodného původu:', event.origin);
return;
}
processMessage(event.data);
});
2. Vkládání dat (Data Injection)
Zacházení s přijatými daty (event.data) jako s spustitelným kódem nebo jejich přímé vkládání do DOM může vést ke zranitelnostem typu Cross-Site Scripting (XSS). Před použitím přijatá data vždy sanitizujte a validujte.
Příklad (Zranitelný kód):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // TOTO NEDĚLEJTE!
}
});
Příklad (Bezpečný kód):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementujte správnou sanitační funkci
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Zde implementujte robustní logiku sanitace.
// Například použijte DOMPurify nebo podobnou knihovnu
return DOMPurify.sanitize(data);
}
3. Útoky typu Man-in-the-Middle (MITM)
Pokud komunikace probíhá přes nezabezpečený kanál (HTTP), útočník MITM může zachytit a upravit zprávy. Pro bezpečnou komunikaci vždy používejte HTTPS.
4. Cross-Site Request Forgery (CSRF)
Pokud příjemce provádí akce na základě přijaté zprávy bez řádného ověření, útočník by mohl potenciálně podvrhnout zprávy a přimět tak příjemce k provedení neúmyslných akcí. Implementujte mechanismy ochrany proti CSRF, jako je zahrnutí tajného tokenu do zprávy a jeho ověření na straně příjemce.
5. Použití zástupných znaků v targetOrigin
Nastavení targetOrigin na * umožňuje přijímat zprávu jakémukoli původu. Tomu by se mělo vyhnout, pokud to není absolutně nezbytné, protože to maří účel zabezpečení založeného na původu. Pokud musíte použít *, ujistěte se, že implementujete další silná bezpečnostní opatření, jako jsou autentizační kódy zpráv (MAC).
Příklad (Tomuto se vyhněte):
otherWindow.postMessage(message, '*'); // Vyhněte se použití '*', pokud to není absolutně nezbytné
Bezpečnostní vzory a osvědčené postupy
Chcete-li zmírnit rizika spojená s postMessage, dodržujte tyto bezpečnostní vzory a osvědčené postupy:
1. Striktní ověřování původu
Vždy ověřujte vlastnost event.origin na straně příjemce. Porovnejte ji s předdefinovaným seznamem důvěryhodných původů. Pro porovnání použijte striktní rovnost (===).
2. Sanitace a validace dat
Před použitím všechna data přijatá prostřednictvím postMessage sanitizujte a validujte. Používejte vhodné techniky sanitace v závislosti na tom, jak budou data použita (např. escapování HTML, kódování URL, validace vstupu). Pro sanitaci HTML používejte knihovny jako DOMPurify.
3. Autentizační kódy zpráv (MAC)
Zahrňte do zprávy autentizační kód zprávy (MAC), abyste zajistili její integritu a autentičnost. Odesílatel vypočítá MAC pomocí sdíleného tajného klíče a zahrne ho do zprávy. Příjemce přepočítá MAC pomocí stejného sdíleného tajného klíče a porovná ho s přijatým MAC. Pokud se shodují, je zpráva považována za autentickou a neporušenou.
Příklad (Použití HMAC-SHA256):
// Odesílatel
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Příjemce
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Přijata zpráva z nedůvěryhodného původu:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Zpráva je autentická!');
processMessage(message); // Pokračujte ve zpracování zprávy
} else {
console.error('Ověření podpisu zprávy selhalo!');
}
}
Důležité: Sdílený tajný klíč musí být bezpečně generován a uložen. Vyhněte se pevnému zakódování klíče v kódu.
4. Použití Nonce a časových razítek
Abyste předešli útokům typu replay, zahrňte do zprávy jedinečné nonce (číslo použité jednou) a časové razítko. Příjemce pak může ověřit, že nonce nebylo dříve použito a že časové razítko je v přijatelném časovém rámci. Tím se zmírňuje riziko, že útočník znovu přehraje dříve zachycené zprávy.
5. Princip nejmenších privilegií
Druhému oknu udělujte pouze minimální nezbytná oprávnění. Například pokud druhé okno potřebuje pouze číst data, neumožňujte mu zapisovat data. Navrhněte svůj komunikační protokol s ohledem na princip nejmenších privilegií.
6. Content Security Policy (CSP)
Použijte Content Security Policy (CSP) k omezení zdrojů, ze kterých lze skripty načítat, a akcí, které mohou skripty provádět. To může pomoci zmírnit dopad zranitelností XSS, které by mohly vzniknout z nesprávného zpracování dat postMessage.
7. Validace vstupu
Vždy ověřujte strukturu a formát přijatých dat. Definujte jasný formát zprávy a zajistěte, aby přijatá data tomuto formátu odpovídala. To pomáhá předcházet neočekávanému chování a zranitelnostem.
8. Bezpečná serializace dat
Pro serializaci a deserializaci zpráv používejte bezpečný formát serializace dat, jako je JSON. Vyhněte se používání formátů, které umožňují spuštění kódu, jako je eval() nebo Function().
9. Omezení velikosti zprávy
Omezte velikost zpráv odesílaných prostřednictvím postMessage. Velké zprávy mohou spotřebovávat nadměrné zdroje a potenciálně vést k útokům typu denial-of-service.
10. Pravidelné bezpečnostní audity
Provádějte pravidelné bezpečnostní audity svého kódu, abyste identifikovali a řešili potenciální zranitelnosti. Věnujte zvláštní pozornost implementaci postMessage a ujistěte se, že jsou dodržovány všechny osvědčené postupy v oblasti bezpečnosti.
Příklad scénáře: Bezpečná komunikace mezi Iframe a jeho rodičem
Zvažte scénář, kde iframe hostovaný na https://iframe.example.com potřebuje komunikovat se svou rodičovskou stránkou hostovanou na https://parent.example.com. Iframe potřebuje odeslat uživatelská data na rodičovskou stránku ke zpracování.
Iframe (https://iframe.example.com):
// Vygenerujte sdílený tajný klíč (nahraďte bezpečnou metodou generování klíče)
const sharedSecret = 'VÁŠ_BEZPEČNÝ_SDÍLENÝ_KLÍČ';
// Získání uživatelských dat
const userData = {
name: 'Jan Novák',
email: 'jan.novak@example.com'
};
// Odeslání uživatelských dat na rodičovskou stránku
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Rodičovská stránka (https://parent.example.com):
// Sdílený tajný klíč (musí se shodovat s klíčem iframe)
const sharedSecret = 'VÁŠ_BEZPEČNÝ_SDÍLENÝ_KLÍČ';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Přijata zpráva z nedůvěryhodného původu:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Zpráva je autentická!');
// Zpracujte uživatelská data
console.log('Uživatelská data:', userData);
} else {
console.error('Ověření podpisu zprávy selhalo!');
}
});
Důležité poznámky:
- Nahraďte
VÁŠ_BEZPEČNÝ_SDÍLENÝ_KLÍČbezpečně vygenerovaným sdíleným tajným klíčem. - Sdílený tajný klíč musí být stejný v iframe i na rodičovské stránce.
- Tento příklad používá HMAC-SHA256 pro autentizaci zprávy.
Závěr
postMessage API je mocný nástroj pro umožnění komunikace mezi různými původy ve webových aplikacích. Je však klíčové porozumět potenciálním bezpečnostním rizikům a implementovat vhodné bezpečnostní vzory k jejich zmírnění. Dodržováním bezpečnostních vzorů a osvědčených postupů uvedených v této příručce můžete bezpečně používat postMessage k vytváření robustních a bezpečných webových aplikací.
Nezapomeňte vždy upřednostňovat bezpečnost a zůstat v obraze s nejnovějšími osvědčenými postupy v oblasti bezpečnosti pro webový vývoj. Pravidelně kontrolujte svůj kód a bezpečnostní konfigurace, abyste zajistili, že vaše aplikace jsou chráněny před potenciálními zranitelnostmi.