Utforsk sikker kommunikasjon på tvers av opprinnelser ved hjelp av PostMessage API. Lær om dets muligheter, sikkerhetsrisikoer og beste praksis for å redusere sårbarheter i webapplikasjoner.
Kommunikasjon på tvers av opprinnelser: Sikkerhetsmønstre med PostMessage API
I den moderne weben må applikasjoner ofte samhandle med ressurser fra forskjellige opprinnelser. Same-Origin Policy (SOP) er en avgjørende sikkerhetsmekanisme som begrenser skript fra å få tilgang til ressurser fra en annen opprinnelse. Det finnes imidlertid legitime scenarier der kommunikasjon på tvers av opprinnelser er nødvendig. postMessage-APIet gir en kontrollert mekanisme for å oppnå dette, men det er viktig å forstå de potensielle sikkerhetsrisikoene og implementere passende sikkerhetsmønstre.
Forståelse av Same-Origin Policy (SOP)
Same-Origin Policy er et fundamentalt sikkerhetskonsept i nettlesere. Det begrenser websider fra å gjøre forespørsler til et annet domene enn det som serverte websiden. En opprinnelse er definert av skjemaet (protokoll), verten (domene) og porten. Hvis noen av disse er forskjellige, regnes opprinnelsene som forskjellige. For eksempel:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Disse er alle forskjellige opprinnelser, og SOP begrenser direkte skripttilgang mellom dem.
Introduksjon til PostMessage API
postMessage-APIet gir en trygg og kontrollert mekanisme for kommunikasjon på tvers av opprinnelser. Det lar skript sende meldinger til andre vinduer (f.eks. iframes, nye vinduer eller faner), uavhengig av deres opprinnelse. Det mottakende vinduet kan deretter lytte etter disse meldingene og behandle dem deretter.
Den grunnleggende syntaksen for å sende en melding er:
otherWindow.postMessage(message, targetOrigin);
otherWindow: En referanse til målvinduet (f.eks.window.parent,iframe.contentWindow, eller et vindusobjekt hentet frawindow.open).message: Dataene du vil sende. Dette kan være et hvilket som helst JavaScript-objekt som kan serialiseres (f.eks. strenger, tall, objekter, arrays).targetOrigin: Spesifiserer opprinnelsen du vil sende meldingen til. Dette er en avgjørende sikkerhetsparameter.
På mottakersiden må du lytte etter message-hendelsen:
window.addEventListener('message', function(event) {
// ...
});
event-objektet inneholder følgende egenskaper:
event.data: Meldingen sendt fra det andre vinduet.event.origin: Opprinnelsen til vinduet som sendte meldingen.event.source: En referanse til vinduet som sendte meldingen.
Sikkerhetsrisikoer og sårbarheter
Selv om postMessage tilbyr en måte å omgå SOP-restriksjoner på, introduserer det også potensielle sikkerhetsrisikoer hvis det ikke implementeres nøye. Her er noen vanlige sårbarheter:
1. Uoverensstemmelse i målopprinnelse
Å unnlate å validere event.origin-egenskapen er en kritisk sårbarhet. Hvis mottakeren blindt stoler på meldingen, kan ethvert nettsted sende ondsinnede data. Verifiser alltid at event.origin samsvarer med den forventede opprinnelsen før meldingen behandles.
Eksempel (Sårbar kode):
window.addEventListener('message', function(event) {
// IKKE GJØR DETTE!
processMessage(event.data);
});
Eksempel (Sikker kode):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Mottok melding fra upålitelig opprinnelse:', event.origin);
return;
}
processMessage(event.data);
});
2. Datainjeksjon
Å behandle mottatte data (event.data) som kjørbar kode eller å injisere dem direkte i DOM-en kan føre til Cross-Site Scripting (XSS)-sårbarheter. Saner og valider alltid mottatte data før du bruker dem.
Eksempel (Sårbar kode):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // IKKE GJØR DETTE!
}
});
Eksempel (Sikker kode):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementer en skikkelig saneringsfunksjon
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Implementer robust saneringslogikk her.
// For eksempel, bruk DOMPurify eller et lignende bibliotek
return DOMPurify.sanitize(data);
}
3. Man-in-the-Middle (MITM)-angrep
Hvis kommunikasjon skjer over en usikker kanal (HTTP), kan en MITM-angriper fange opp og endre meldingene. Bruk alltid HTTPS for sikker kommunikasjon.
4. Cross-Site Request Forgery (CSRF)
Hvis mottakeren utfører handlinger basert på den mottatte meldingen uten skikkelig validering, kan en angriper potensielt forfalske meldinger for å lure mottakeren til å utføre utilsiktede handlinger. Implementer CSRF-beskyttelsesmekanismer, som å inkludere et hemmelig token i meldingen og verifisere det på mottakersiden.
5. Bruk av jokertegn i targetOrigin
Å sette targetOrigin til * lar enhver opprinnelse motta meldingen. Dette bør unngås med mindre det er absolutt nødvendig, da det undergraver formålet med opprinnelsesbasert sikkerhet. Hvis du må bruke *, sørg for at du implementerer andre sterke sikkerhetstiltak, som meldingsautentiseringskoder (MAC-er).
Eksempel (Unngå dette):
otherWindow.postMessage(message, '*'); // Unngå å bruke '*' med mindre det er absolutt nødvendig
Sikkerhetsmønstre og beste praksis
For å redusere risikoene forbundet med postMessage, følg disse sikkerhetsmønstrene og beste praksis:
1. Streng validering av opprinnelse
Valider alltid event.origin-egenskapen på mottakersiden. Sammenlign den med en forhåndsdefinert liste over pålitelige opprinnelser. Bruk streng likhet (===) for sammenligning.
2. Datasanering og validering
Saner og valider alle data mottatt gjennom postMessage før du bruker dem. Bruk passende saneringsteknikker avhengig av hvordan dataene skal brukes (f.eks. HTML-escaping, URL-koding, inndatavalidering). Bruk biblioteker som DOMPurify for å sanere HTML.
3. Meldingsautentiseringskoder (MAC-er)
Inkluder en meldingsautentiseringskode (MAC) i meldingen for å sikre dens integritet og autentisitet. Avsenderen beregner MAC-en ved hjelp av en delt hemmelig nøkkel og inkluderer den i meldingen. Mottakeren beregner MAC-en på nytt ved hjelp av den samme delte hemmelige nøkkelen og sammenligner den med den mottatte MAC-en. Hvis de stemmer overens, anses meldingen som autentisk og uendret.
Eksempel (Bruk av HMAC-SHA256):
// Avsender
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);
}
// Mottaker
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Mottok melding fra upålitelig opprinnelse:', 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('Meldingen er autentisk!');
processMessage(message); // Fortsett med å behandle meldingen
} else {
console.error('Verifisering av meldingssignatur mislyktes!');
}
}
Viktig: Den delte hemmelige nøkkelen må genereres og lagres sikkert. Unngå å hardkode nøkkelen i koden.
4. Bruk av Nonce og tidsstempler
For å forhindre replay-angrep, inkluder en unik nonce (nummer brukt én gang) og et tidsstempel i meldingen. Mottakeren kan da verifisere at nonce-en ikke har blitt brukt før, og at tidsstempelet er innenfor en akseptabel tidsramme. Dette reduserer risikoen for at en angriper kan spille av tidligere avlyttede meldinger.
5. Prinsippet om minste privilegium
Gi kun de minimalt nødvendige privilegiene til det andre vinduet. For eksempel, hvis det andre vinduet bare trenger å lese data, ikke la det skrive data. Design kommunikasjonsprotokollen din med prinsippet om minste privilegium i tankene.
6. Content Security Policy (CSP)
Bruk Content Security Policy (CSP) for å begrense kildene som skript kan lastes fra og handlingene som skript kan utføre. Dette kan bidra til å redusere virkningen av XSS-sårbarheter som kan oppstå fra feil håndtering av postMessage-data.
7. Inndatavalidering
Valider alltid strukturen og formatet på de mottatte dataene. Definer et tydelig meldingsformat og sørg for at de mottatte dataene samsvarer med dette formatet. Dette bidrar til å forhindre uventet oppførsel og sårbarheter.
8. Sikker dataseriealisering
Bruk et sikkert dataseriealiseringsformat, som JSON, for å serialisere og deserialisere meldinger. Unngå å bruke formater som tillater kodekjøring, som eval() eller Function().
9. Begrens meldingsstørrelse
Begrens størrelsen på meldinger sendt gjennom postMessage. Store meldinger kan forbruke for mye ressurser og potensielt føre til tjenestenektangrep.
10. Regelmessige sikkerhetsrevisjoner
Gjennomfør regelmessige sikkerhetsrevisjoner av koden din for å identifisere og adressere potensielle sårbarheter. Vær spesielt oppmerksom på implementeringen av postMessage og sørg for at alle beste praksis for sikkerhet følges.
Eksempelscenario: Sikker kommunikasjon mellom en Iframe og dens forelder
Tenk deg et scenario der en iframe som ligger på https://iframe.example.com trenger å kommunisere med sin foreldreside som ligger på https://parent.example.com. Iframen trenger å sende brukerdata til foreldresiden for behandling.
Iframe (https://iframe.example.com):
// Generer en delt hemmelig nøkkel (erstatt med en sikker nøkkelgenereringsmetode)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Hent brukerdata
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Send brukerdataene til foreldresiden
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);
Foreldreside (https://parent.example.com):
// Delt hemmelig nøkkel (må samsvare med iframe-nøkkelen)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Mottok melding fra upålitelig opprinnelse:', 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('Meldingen er autentisk!');
// Behandle brukerdataene
console.log('Brukerdata:', userData);
} else {
console.error('Verifisering av meldingssignatur mislyktes!');
}
});
Viktige merknader:
- Erstatt
YOUR_SECURE_SHARED_SECRETmed en sikkert generert delt hemmelig nøkkel. - Den delte hemmelige nøkkelen må være den samme i både iframe og foreldresiden.
- Dette eksempelet bruker HMAC-SHA256 for meldingsautentisering.
Konklusjon
postMessage-APIet er et kraftig verktøy for å muliggjøre kommunikasjon på tvers av opprinnelser i webapplikasjoner. Det er imidlertid avgjørende å forstå de potensielle sikkerhetsrisikoene og implementere passende sikkerhetsmønstre for å redusere disse risikoene. Ved å følge sikkerhetsmønstrene og beste praksis som er beskrevet i denne guiden, kan du trygt bruke postMessage til å bygge robuste og sikre webapplikasjoner.
Husk å alltid prioritere sikkerhet og holde deg oppdatert med de nyeste beste praksisene for sikkerhet innen webutvikling. Gjennomgå jevnlig koden og sikkerhetskonfigurasjonene dine for å sikre at applikasjonene dine er beskyttet mot potensielle sårbarheter.