Istražite sigurnu komunikaciju između različitih izvora pomoću PostMessage API-ja. Saznajte o njegovim mogućnostima, sigurnosnim rizicima i najboljim praksama za ublažavanje ranjivosti u web aplikacijama.
Komunikacija između različitih izvora: Sigurnosni obrasci s PostMessage API-jem
U modernom webu, aplikacije često moraju komunicirati s resursima s različitih izvora. Pravilo istog izvora (Same-Origin Policy - SOP) ključan je sigurnosni mehanizam koji ograničava skripte u pristupu resursima s drugog izvora. Međutim, postoje legitimni scenariji u kojima je komunikacija između različitih izvora nužna. postMessage API pruža kontrolirani mehanizam za postizanje toga, no ključno je razumjeti njegove potencijalne sigurnosne rizike i primijeniti odgovarajuće sigurnosne obrasce.
Razumijevanje Pravila istog izvora (SOP)
Pravilo istog izvora temeljni je sigurnosni koncept u web preglednicima. Ograničava web stranice u slanju zahtjeva na domenu različitu od one s koje je web stranica poslužena. Izvor je definiran shemom (protokolom), hostom (domenom) i portom. Ako se bilo koji od ovih elemenata razlikuje, izvori se smatraju različitima. Na primjer:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Sve su ovo različiti izvori, a SOP ograničava izravan pristup skripti između njih.
Uvod u PostMessage API
postMessage API pruža siguran i kontroliran mehanizam za komunikaciju između različitih izvora. Omogućuje skriptama slanje poruka drugim prozorima (npr. iframeovima, novim prozorima ili karticama), bez obzira na njihov izvor. Prozor primatelj može zatim slušati te poruke i obrađivati ih u skladu s tim.
Osnovna sintaksa za slanje poruke je:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Referenca na ciljni prozor (npr.window.parent,iframe.contentWindowili objekt prozora dobiven izwindow.open).message: Podaci koje želite poslati. To može biti bilo koji JavaScript objekt koji se može serijalizirati (npr. stringovi, brojevi, objekti, polja).targetOrigin: Određuje izvor kojem želite poslati poruku. Ovo je ključan sigurnosni parametar.
Na strani primatelja, morate slušati događaj message:
window.addEventListener('message', function(event) {
// ...
});
Objekt event sadrži sljedeća svojstva:
event.data: Poruka koju je poslao drugi prozor.event.origin: Izvor prozora koji je poslao poruku.event.source: Referenca na prozor koji je poslao poruku.
Sigurnosni rizici i ranjivosti
Iako postMessage nudi način za zaobilaženje ograničenja SOP-a, također uvodi potencijalne sigurnosne rizike ako se ne implementira pažljivo. Evo nekih uobičajenih ranjivosti:
1. Neusklađenost ciljnog izvora
Propust validacije svojstva event.origin predstavlja kritičnu ranjivost. Ako primatelj slijepo vjeruje poruci, bilo koja web stranica može poslati zlonamjerne podatke. Uvijek provjerite odgovara li event.origin očekivanom izvoru prije obrade poruke.
Primjer (Ranjivi kôd):
window.addEventListener('message', function(event) {
// NEMOJTE OVO RADITI!
processMessage(event.data);
});
Primjer (Siguran kôd):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Primljena poruka s nepouzdanog izvora:', event.origin);
return;
}
processMessage(event.data);
});
2. Ubacivanje podataka (Data Injection)
Tretiranje primljenih podataka (event.data) kao izvršnog kôda ili njihovo izravno ubacivanje u DOM može dovesti do Cross-Site Scripting (XSS) ranjivosti. Uvijek sanitizirajte i validirajte primljene podatke prije njihove upotrebe.
Primjer (Ranjivi kôd):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // NEMOJTE OVO RADITI!
}
});
Primjer (Siguran kôd):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementirajte odgovarajuću funkciju za sanitizaciju
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Ovdje implementirajte robusnu logiku sanitizacije.
// Na primjer, koristite DOMPurify ili sličnu biblioteku
return DOMPurify.sanitize(data);
}
3. Napadi "čovjek u sredini" (MITM)
Ako se komunikacija odvija preko nesigurnog kanala (HTTP), MITM napadač može presresti i izmijeniti poruke. Uvijek koristite HTTPS za sigurnu komunikaciju.
4. Krivotvorenje zahtjeva na drugim stranicama (CSRF)
Ako primatelj izvršava radnje na temelju primljene poruke bez odgovarajuće validacije, napadač bi mogao krivotvoriti poruke kako bi prevario primatelja da izvrši neželjene radnje. Implementirajte mehanizme zaštite od CSRF-a, poput uključivanja tajnog tokena u poruku i njegove provjere na strani primatelja.
5. Korištenje zamjenskih znakova (wildcard) u targetOrigin
Postavljanje targetOrigin na * omogućuje bilo kojem izvoru da primi poruku. To treba izbjegavati osim ako je apsolutno nužno, jer time se gubi svrha sigurnosti temeljene na izvoru. Ako morate koristiti *, osigurajte da ste implementirali druge jake sigurnosne mjere, poput autentifikacijskih kodova poruka (MAC).
Primjer (Izbjegavajte ovo):
otherWindow.postMessage(message, '*'); // Izbjegavajte korištenje '*' osim ako je apsolutno nužno
Sigurnosni obrasci i najbolje prakse
Da biste ublažili rizike povezane s postMessage, slijedite ove sigurnosne obrasce i najbolje prakse:
1. Stroga validacija izvora
Uvijek validirajte svojstvo event.origin na strani primatelja. Usporedite ga s unaprijed definiranim popisom pouzdanih izvora. Koristite strogu jednakost (===) za usporedbu.
2. Sanitizacija i validacija podataka
Sanitizirajte i validirajte sve podatke primljene putem postMessage prije njihove upotrebe. Koristite odgovarajuće tehnike sanitizacije ovisno o tome kako će se podaci koristiti (npr. HTML escapiranje, URL enkodiranje, validacija unosa). Koristite biblioteke poput DOMPurify za sanitizaciju HTML-a.
3. Autentifikacijski kodovi poruka (MAC)
Uključite autentifikacijski kôd poruke (MAC) u poruku kako biste osigurali njezin integritet i autentičnost. Pošiljatelj izračunava MAC koristeći zajednički tajni ključ i uključuje ga u poruku. Primatelj ponovno izračunava MAC koristeći isti zajednički tajni ključ i uspoređuje ga s primljenim MAC-om. Ako se podudaraju, poruka se smatra autentičnom i neizmijenjenom.
Primjer (Korištenje HMAC-SHA256):
// Pošiljatelj
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);
}
// Primatelj
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Primljena poruka s nepouzdanog izvora:', 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('Poruka je autentična!');
processMessage(message); // Nastavite s obradom poruke
} else {
console.error('Provjera potpisa poruke nije uspjela!');
}
}
Važno: Zajednički tajni ključ mora biti sigurno generiran i pohranjen. Izbjegavajte tvrdo kodiranje ključa u kodu.
4. Korištenje Nonce-a i vremenskih oznaka
Da biste spriječili napade ponavljanja (replay attacks), uključite jedinstveni nonce (broj koji se koristi jednom) i vremensku oznaku u poruku. Primatelj tada može provjeriti da nonce nije prethodno korišten i da je vremenska oznaka unutar prihvatljivog vremenskog okvira. Time se ublažava rizik da napadač ponovno pošalje prethodno presretnute poruke.
5. Načelo najmanjih privilegija
Dajte samo minimalne potrebne privilegije drugom prozoru. Na primjer, ako drugi prozor treba samo čitati podatke, nemojte mu dopustiti da piše podatke. Dizajnirajte svoj komunikacijski protokol imajući na umu načelo najmanjih privilegija.
6. Politika sigurnosti sadržaja (CSP)
Koristite Politiku sigurnosti sadržaja (CSP) kako biste ograničili izvore s kojih se skripte mogu učitati i radnje koje skripte mogu izvoditi. To može pomoći u ublažavanju utjecaja XSS ranjivosti koje bi mogle proizaći iz nepravilnog rukovanja podacima iz postMessage.
7. Validacija unosa
Uvijek validirajte strukturu i format primljenih podataka. Definirajte jasan format poruke i osigurajte da primljeni podaci odgovaraju tom formatu. To pomaže u sprječavanju neočekivanog ponašanja i ranjivosti.
8. Sigurna serijalizacija podataka
Koristite siguran format za serijalizaciju podataka, poput JSON-a, za serijalizaciju i deserijalizaciju poruka. Izbjegavajte korištenje formata koji omogućuju izvršavanje kôda, kao što su eval() ili Function().
9. Ograničavanje veličine poruke
Ograničite veličinu poruka poslanih putem postMessage. Velike poruke mogu trošiti prekomjerne resurse i potencijalno dovesti do napada uskraćivanjem usluge (denial-of-service).
10. Redovite sigurnosne provjere
Provodite redovite sigurnosne provjere svog kôda kako biste identificirali i riješili potencijalne ranjivosti. Posebnu pažnju posvetite implementaciji postMessage i osigurajte da se slijede sve najbolje sigurnosne prakse.
Primjer scenarija: Sigurna komunikacija između Iframe-a i njegovog roditelja
Razmotrite scenarij u kojem iframe hostiran na https://iframe.example.com treba komunicirati sa svojom roditeljskom stranicom hostiranom na https://parent.example.com. Iframe treba poslati korisničke podatke roditeljskoj stranici na obradu.
Iframe (https://iframe.example.com):
// Generirajte zajednički tajni ključ (zamijenite sigurnom metodom generiranja ključa)
const sharedSecret = 'VAŠ_SIGURNI_ZAJEDNIČKI_KLJUČ';
// Dohvatite korisničke podatke
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Pošaljite korisničke podatke roditeljskoj stranici
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);
Roditeljska stranica (https://parent.example.com):
// Zajednički tajni ključ (mora odgovarati ključu iz iframe-a)
const sharedSecret = 'VAŠ_SIGURNI_ZAJEDNIČKI_KLJUČ';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Primljena poruka s nepouzdanog izvora:', 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('Poruka je autentična!');
// Obradite korisničke podatke
console.log('Korisnički podaci:', userData);
} else {
console.error('Provjera potpisa poruke nije uspjela!');
}
});
Važne napomene:
- Zamijenite
YOUR_SECURE_SHARED_SECRETsaVAŠ_SIGURNI_ZAJEDNIČKI_KLJUČsigurno generiranim zajedničkim tajnim ključem. - Zajednički tajni ključ mora biti isti i u iframe-u i na roditeljskoj stranici.
- Ovaj primjer koristi HMAC-SHA256 za autentifikaciju poruke.
Zaključak
postMessage API je moćan alat za omogućavanje komunikacije između različitih izvora u web aplikacijama. Međutim, ključno je razumjeti potencijalne sigurnosne rizike i primijeniti odgovarajuće sigurnosne obrasce kako bi se ti rizici ublažili. Slijedeći sigurnosne obrasce i najbolje prakse navedene u ovom vodiču, možete sigurno koristiti postMessage za izgradnju robusnih i sigurnih web aplikacija.
Ne zaboravite uvijek davati prioritet sigurnosti i biti u toku s najnovijim sigurnosnim praksama za web razvoj. Redovito pregledavajte svoj kôd i sigurnosne konfiguracije kako biste osigurali da su vaše aplikacije zaštićene od potencijalnih ranjivosti.