Raziščite varno komunikacijo med različnimi izvori z uporabo API-ja PostMessage. Spoznajte njegove zmožnosti, varnostna tveganja in najboljše prakse za zmanjšanje ranljivosti v spletnih aplikacijah.
Komunikacija med različnimi izvori: Varnostni vzorci z API-jem PostMessage
V sodobnem spletu morajo aplikacije pogosto komunicirati z viri iz različnih izvorov. Politika istega izvora (Same-Origin Policy - SOP) je ključen varnostni mehanizem, ki skriptam omejuje dostop do virov iz drugega izvora. Vendar pa obstajajo legitimni scenariji, kjer je komunikacija med različnimi izvori nujna. API postMessage zagotavlja nadzorovan mehanizem za doseganje tega, vendar je ključnega pomena razumeti njegova potencialna varnostna tveganja in implementirati ustrezne varnostne vzorce.
Razumevanje politike istega izvora (SOP)
Politika istega izvora je temeljni varnostni koncept v spletnih brskalnikih. Omejuje spletne strani pri pošiljanju zahtevkov na drugo domeno, kot je tista, ki je postregla spletno stran. Izvor je definiran s shemo (protokolom), gostiteljem (domeno) in vrati. Če se kateri koli od teh razlikuje, se izvora štejeta za različna. Na primer:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Vsi ti so različni izvori in SOP omejuje neposreden dostop skript med njimi.
Predstavitev API-ja PostMessage
API postMessage zagotavlja varen in nadzorovan mehanizem za komunikacijo med različnimi izvori. Omogoča skriptam pošiljanje sporočil drugim oknom (npr. iframe-om, novim oknom ali zavihkom), ne glede na njihov izvor. Sprejemno okno lahko nato posluša ta sporočila in jih ustrezno obdela.
Osnovna sintaksa za pošiljanje sporočila je:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Referenca na ciljno okno (npr.window.parent,iframe.contentWindowali objekt okna, pridobljen zwindow.open).message: Podatki, ki jih želite poslati. To je lahko kateri koli objekt JavaScript, ki ga je mogoče serializirati (npr. nizi, števila, objekti, polja).targetOrigin: Določa izvor, kateremu želite poslati sporočilo. To je ključen varnostni parameter.
Na sprejemni strani morate poslušati dogodek message:
window.addEventListener('message', function(event) {
// ...
});
Objekt event vsebuje naslednje lastnosti:
event.data: Sporočilo, ki ga je poslalo drugo okno.event.origin: Izvor okna, ki je poslalo sporočilo.event.source: Referenca na okno, ki je poslalo sporočilo.
Varnostna tveganja in ranljivosti
Čeprav postMessage ponuja način za obhod omejitev SOP, prinaša tudi potencialna varnostna tveganja, če ni implementiran previdno. Tukaj je nekaj pogostih ranljivosti:
1. Neujemanje ciljnega izvora
Neuspešno preverjanje lastnosti event.origin je kritična ranljivost. Če prejemnik slepo zaupa sporočilu, lahko katera koli spletna stran pošlje zlonamerne podatke. Vedno preverite, ali se event.origin ujema s pričakovanim izvorom, preden obdelate sporočilo.
Primer (ranljiva koda):
window.addEventListener('message', function(event) {
// TEGA NE POČNITE!
processMessage(event.data);
});
Primer (varna koda):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Prejeto sporočilo iz nezaupanja vrednega izvora:', event.origin);
return;
}
processMessage(event.data);
});
2. Vbrizgavanje podatkov
Obravnavanje prejetih podatkov (event.data) kot izvršljive kode ali njihovo neposredno vbrizgavanje v DOM lahko vodi do ranljivosti navzkrižnega skriptiranja (Cross-Site Scripting - XSS). Vedno očistite in preverite prejete podatke, preden jih uporabite.
Primer (ranljiva koda):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // TEGA NE POČNITE!
}
});
Primer (varna koda):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementirajte ustrezno funkcijo za čiščenje
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Tukaj implementirajte robustno logiko čiščenja.
// Na primer, uporabite DOMPurify ali podobno knjižnico
return DOMPurify.sanitize(data);
}
3. Napadi "človek v sredini" (Man-in-the-Middle - MITM)
Če komunikacija poteka preko nevarnega kanala (HTTP), lahko napadalec MITM prestreže in spremeni sporočila. Za varno komunikacijo vedno uporabljajte HTTPS.
4. Ponarejanje zahtev med spletnimi mesti (Cross-Site Request Forgery - CSRF)
Če prejemnik izvaja dejanja na podlagi prejetega sporočila brez ustreznega preverjanja, bi lahko napadalec ponaredil sporočila, da bi prejemnika pretental v izvajanje neželenih dejanj. Implementirajte mehanizme za zaščito pred CSRF, kot je vključitev skrivnega žetona v sporočilo in njegovo preverjanje na strani prejemnika.
5. Uporaba nadomestnih znakov v targetOrigin
Nastavitev targetOrigin na * omogoča kateremu koli izvoru prejemanje sporočila. Temu se je treba izogibati, razen če je to nujno potrebno, saj izniči namen varnosti na podlagi izvora. Če morate uporabiti *, zagotovite, da implementirate druge močne varnostne ukrepe, kot so kode za preverjanje pristnosti sporočil (MAC).
Primer (izogibajte se temu):
otherWindow.postMessage(message, '*'); // Izogibajte se uporabi '*' razen če je nujno potrebno
Varnostni vzorci in najboljše prakse
Za zmanjšanje tveganj, povezanih s postMessage, sledite tem varnostnim vzorcem in najboljšim praksam:
1. Strogo preverjanje izvora
Vedno preverite lastnost event.origin na strani prejemnika. Primerjajte jo s predhodno določenim seznamom zaupanja vrednih izvorov. Za primerjavo uporabite strogo enakost (===).
2. Čiščenje in preverjanje podatkov
Očistite in preverite vse podatke, prejete preko postMessage, preden jih uporabite. Uporabite ustrezne tehnike čiščenja glede na to, kako bodo podatki uporabljeni (npr. izogibanje HTML, kodiranje URL-jev, preverjanje vnosa). Za čiščenje HTML-ja uporabite knjižnice, kot je DOMPurify.
3. Kode za preverjanje pristnosti sporočil (MAC)
V sporočilo vključite kodo za preverjanje pristnosti sporočila (MAC), da zagotovite njegovo celovitost in pristnost. Pošiljatelj izračuna MAC z uporabo skupnega skrivnega ključa in ga vključi v sporočilo. Prejemnik ponovno izračuna MAC z istim skupnim skrivnim ključem in ga primerja s prejetim MAC-om. Če se ujemata, se sporočilo šteje za pristno in nespremenjeno.
Primer (uporaba 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);
}
// Prejemnik
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Prejeto sporočilo iz nezaupanja vrednega 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('Sporočilo je pristno!');
processMessage(message); // Nadaljuj z obdelavo sporočila
} else {
console.error('Preverjanje podpisa sporočila ni uspelo!');
}
}
Pomembno: Skupni skrivni ključ mora biti varno ustvarjen in shranjen. Izogibajte se trdnemu kodiranju ključa v kodi.
4. Uporaba "nonce" in časovnih žigov
Za preprečevanje napadov s ponovitvijo v sporočilo vključite edinstven "nonce" (število, uporabljeno enkrat) in časovni žig. Prejemnik lahko nato preveri, da "nonce" še ni bil uporabljen in da je časovni žig znotraj sprejemljivega časovnega okvira. To zmanjšuje tveganje, da bi napadalec ponovil predhodno prestrežena sporočila.
5. Načelo najmanjših privilegijev
Drugemu oknu dodelite le minimalne potrebne privilegije. Na primer, če mora drugo okno samo brati podatke, mu ne dovolite pisanja podatkov. Svoj komunikacijski protokol oblikujte z mislijo na načelo najmanjših privilegijev.
6. Politika varnosti vsebine (CSP)
Uporabite politiko varnosti vsebine (CSP), da omejite vire, iz katerih se lahko nalagajo skripte, in dejanja, ki jih skripte lahko izvajajo. To lahko pomaga ublažiti vpliv ranljivosti XSS, ki bi lahko nastale zaradi nepravilnega ravnanja s podatki postMessage.
7. Preverjanje vnosa
Vedno preverite strukturo in obliko prejetih podatkov. Določite jasno obliko sporočila in zagotovite, da prejeti podatki ustrezajo tej obliki. To pomaga preprečiti nepričakovano vedenje in ranljivosti.
8. Varna serializacija podatkov
Za serializacijo in deserializacijo sporočil uporabite varen format za serializacijo podatkov, kot je JSON. Izogibajte se uporabi formatov, ki omogočajo izvajanje kode, kot sta eval() ali Function().
9. Omejitev velikosti sporočila
Omejite velikost sporočil, poslanih preko postMessage. Velika sporočila lahko porabijo preveč virov in potencialno vodijo do napadov za zavrnitev storitve.
10. Redne varnostne revizije
Izvajajte redne varnostne revizije vaše kode, da odkrijete in odpravite potencialne ranljivosti. Posebno pozornost posvetite implementaciji postMessage in zagotovite, da so upoštevane vse varnostne najboljše prakse.
Primer scenarija: Varna komunikacija med iframe-om in njegovo starševsko stranjo
Predstavljajte si scenarij, kjer mora iframe, gostovan na https://iframe.example.com, komunicirati s svojo starševsko stranjo, gostovano na https://parent.example.com. Iframe mora poslati uporabniške podatke na starševsko stran v obdelavo.
Iframe (https://iframe.example.com):
// Ustvarite skupni skrivni ključ (zamenjajte z varno metodo za generiranje ključa)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Pridobite uporabniške podatke
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Pošljite uporabniške podatke na starševsko stran
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);
Starševska stran (https://parent.example.com):
// Skupni skrivni ključ (mora se ujemati s ključem iframe-a)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Prejeto sporočilo iz nezaupanja vrednega 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('Sporočilo je pristno!');
// Obdelajte uporabniške podatke
console.log('Uporabniški podatki:', userData);
} else {
console.error('Preverjanje podpisa sporočila ni uspelo!');
}
});
Pomembne opombe:
- Zamenjajte
YOUR_SECURE_SHARED_SECRETz varno ustvarjenim skupnim skrivnim ključem. - Skupni skrivni ključ mora biti enak v iframe-u in na starševski strani.
- Ta primer uporablja HMAC-SHA256 za preverjanje pristnosti sporočil.
Zaključek
API postMessage je močno orodje za omogočanje komunikacije med različnimi izvori v spletnih aplikacijah. Vendar je ključnega pomena razumeti potencialna varnostna tveganja in implementirati ustrezne varnostne vzorce za njihovo zmanjšanje. Z upoštevanjem varnostnih vzorcev in najboljših praks, opisanih v tem vodniku, lahko varno uporabljate postMessage za izgradnjo robustnih in varnih spletnih aplikacij.
Ne pozabite, da je varnost vedno na prvem mestu in bodite na tekočem z najnovejšimi varnostnimi najboljšimi praksami za spletni razvoj. Redno pregledujte svojo kodo in varnostne konfiguracije, da zagotovite, da so vaše aplikacije zaščitene pred potencialnimi ranljivostmi.