Udforsk sikker kommunikation på tværs af oprindelser ved hjælp af PostMessage API'en. Lær om dens muligheder, sikkerhedsrisici og bedste praksis for at afbøde sårbarheder i webapplikationer.
Kommunikation på tværs af oprindelser: Sikkerhedsmønstre med PostMessage API'en
På det moderne web har applikationer ofte brug for at interagere med ressourcer fra forskellige oprindelser. Same-Origin Policy (SOP) er en afgørende sikkerhedsmekanisme, der begrænser scripts i at få adgang til ressourcer fra en anden oprindelse. Der er dog legitime scenarier, hvor kommunikation på tværs af oprindelser er nødvendig. postMessage API'en giver en kontrolleret mekanisme til at opnå dette, men det er afgørende at forstå dens potentielle sikkerhedsrisici og implementere passende sikkerhedsmønstre.
Forståelse af Same-Origin Policy (SOP)
Same-Origin Policy er et grundlæggende sikkerhedskoncept i webbrowsere. Den begrænser websider i at foretage anmodninger til et andet domæne end det, der serverede websiden. En oprindelse er defineret af skemaet (protokol), host (domæne) og port. Hvis nogen af disse afviger, betragtes oprindelserne som forskellige. For eksempel:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Disse er alle forskellige oprindelser, og SOP begrænser direkte scriptadgang mellem dem.
Introduktion til PostMessage API'en
postMessage API'en giver en sikker og kontrolleret mekanisme til kommunikation på tværs af oprindelser. Den giver scripts mulighed for at sende beskeder til andre vinduer (f.eks. iframes, nye vinduer eller faner), uanset deres oprindelse. Det modtagende vindue kan derefter lytte efter disse beskeder og behandle dem i overensstemmelse hermed.
Den grundlæggende syntaks for at sende en besked er:
otherWindow.postMessage(message, targetOrigin);
otherWindow: En reference til målvinduet (f.eks.window.parent,iframe.contentWindow, eller et vinduesobjekt opnået frawindow.open).message: De data, du vil sende. Dette kan være ethvert JavaScript-objekt, der kan serialiseres (f.eks. strenge, tal, objekter, arrays).targetOrigin: Specificerer den oprindelse, som du vil sende beskeden til. Dette er en afgørende sikkerhedsparameter.
På den modtagende side skal du lytte efter message-hændelsen:
window.addEventListener('message', function(event) {
// ...
});
event-objektet indeholder følgende egenskaber:
event.data: Beskeden sendt fra det andet vindue.event.origin: Oprindelsen af vinduet, der sendte beskeden.event.source: En reference til vinduet, der sendte beskeden.
Sikkerhedsrisici og sårbarheder
Selvom postMessage tilbyder en måde at omgå SOP-begrænsninger, introducerer den også potentielle sikkerhedsrisici, hvis den ikke implementeres omhyggeligt. Her er nogle almindelige sårbarheder:
1. Uoverensstemmelse i måloprindelse
At undlade at validere event.origin-egenskaben er en kritisk sårbarhed. Hvis modtageren blindt stoler på beskeden, kan enhver hjemmeside sende ondsindede data. Verificer altid, at event.origin matcher den forventede oprindelse, før beskeden behandles.
Eksempel (Sårbar kode):
window.addEventListener('message', function(event) {
// GØR IKKE DETTE!
processMessage(event.data);
});
Eksempel (Sikker kode):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Modtog besked fra upålidelig oprindelse:', event.origin);
return;
}
processMessage(event.data);
});
2. Datainjektion
At behandle de modtagne data (event.data) som eksekverbar kode eller at injicere dem direkte i DOM kan føre til Cross-Site Scripting (XSS) sårbarheder. Rens og valider altid de modtagne data, før de bruges.
Eksempel (Sårbar kode):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // GØR IKKE DETTE!
}
});
Eksempel (Sikker kode):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementer en korrekt saneringsfunktion
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Implementer robust saneringslogik her.
// Brug f.eks. DOMPurify eller et lignende bibliotek
return DOMPurify.sanitize(data);
}
3. Man-in-the-Middle (MITM) angreb
Hvis kommunikationen foregår over en usikker kanal (HTTP), kan en MITM-angriber opsnappe og ændre beskederne. Brug altid HTTPS til sikker kommunikation.
4. Cross-Site Request Forgery (CSRF)
Hvis modtageren udfører handlinger baseret på den modtagne besked uden korrekt validering, kan en angriber potentielt forfalske beskeder for at narre modtageren til at udføre utilsigtede handlinger. Implementer CSRF-beskyttelsesmekanismer, såsom at inkludere et hemmeligt token i beskeden og verificere det på modtagersiden.
5. Brug af wildcards i targetOrigin
At indstille targetOrigin til * tillader enhver oprindelse at modtage beskeden. Dette bør undgås, medmindre det er absolut nødvendigt, da det underminerer formålet med oprindelsesbaseret sikkerhed. Hvis du skal bruge *, skal du sikre, at du implementerer andre stærke sikkerhedsforanstaltninger, såsom Message Authentication Codes (MACs).
Eksempel (Undgå dette):
otherWindow.postMessage(message, '*'); // Undgå at bruge '*' medmindre det er absolut nødvendigt
Sikkerhedsmønstre og bedste praksis
For at afbøde de risici, der er forbundet med postMessage, skal du følge disse sikkerhedsmønstre og bedste praksis:
1. Stram oprindelsesvalidering
Valider altid event.origin-egenskaben på modtagersiden. Sammenlign den med en foruddefineret liste over betroede oprindelser. Brug streng lighed (===) til sammenligning.
2. Datasanitisering og -validering
Rens og valider alle data modtaget gennem postMessage, før de bruges. Brug passende sanitiseringsteknikker afhængigt af, hvordan dataene skal bruges (f.eks. HTML-escaping, URL-kodning, inputvalidering). Brug biblioteker som DOMPurify til at rense HTML.
3. Message Authentication Codes (MACs)
Inkluder en Message Authentication Code (MAC) i beskeden for at sikre dens integritet og autenticitet. Afsenderen beregner MAC'en ved hjælp af en delt hemmelig nøgle og inkluderer den i beskeden. Modtageren genberegner MAC'en ved hjælp af den samme delte hemmelige nøgle og sammenligner den med den modtagne MAC. Hvis de matcher, betragtes beskeden som autentisk og uændret.
Eksempel (Brug af HMAC-SHA256):
// Afsender
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);
}
// Modtager
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Modtog besked fra upålidelig oprindelse:', 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('Beskeden er autentisk!');
processMessage(message); // Fortsæt med at behandle beskeden
} else {
console.error('Verifikation af beskedens signatur mislykkedes!');
}
}
Vigtigt: Den delte hemmelige nøgle skal genereres og opbevares sikkert. Undgå at hardcode nøglen i koden.
4. Brug af Nonce og tidsstempler
For at forhindre replay-angreb skal du inkludere en unik nonce (nummer brugt én gang) og et tidsstempel i beskeden. Modtageren kan derefter verificere, at noncen ikke er blevet brugt før, og at tidsstemplet er inden for en acceptabel tidsramme. Dette mindsker risikoen for, at en angriber genafspiller tidligere opsnappede beskeder.
5. Princippet om mindste privilegium
Tildel kun de mindst nødvendige privilegier til det andet vindue. For eksempel, hvis det andet vindue kun behøver at læse data, skal du ikke tillade det at skrive data. Design din kommunikationsprotokol med princippet om mindste privilegium i tankerne.
6. Content Security Policy (CSP)
Brug Content Security Policy (CSP) til at begrænse de kilder, hvorfra scripts kan indlæses, og de handlinger, som scripts kan udføre. Dette kan hjælpe med at afbøde virkningen af XSS-sårbarheder, der kan opstå fra forkert håndtering af postMessage-data.
7. Inputvalidering
Valider altid strukturen og formatet af de modtagne data. Definer et klart beskedformat og sørg for, at de modtagne data overholder dette format. Dette hjælper med at forhindre uventet adfærd og sårbarheder.
8. Sikker dataserialisering
Brug et sikkert dataserialiseringsformat, såsom JSON, til at serialisere og deserialisere beskeder. Undgå at bruge formater, der tillader kodeudførelse, såsom eval() eller Function().
9. Begræns beskedstørrelse
Begræns størrelsen på beskeder sendt via postMessage. Store beskeder kan forbruge for mange ressourcer og potentielt føre til denial-of-service-angreb.
10. Regelmæssige sikkerhedsrevisioner
Udfør regelmæssige sikkerhedsrevisioner af din kode for at identificere og adressere potentielle sårbarheder. Vær særligt opmærksom på implementeringen af postMessage og sørg for, at alle sikkerhedsmæssige bedste praksis følges.
Eksempelscenarie: Sikker kommunikation mellem en Iframe og dens forælder
Overvej et scenarie, hvor en iframe hostet på https://iframe.example.com skal kommunikere med sin forældreside, der er hostet på https://parent.example.com. Iframen skal sende brugerdata til forældresiden til behandling.
Iframe (https://iframe.example.com):
// Generer en delt hemmelig nøgle (erstat med en sikker nøglegenereringsmetode)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Hent brugerdata
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Send brugerdataene til forældresiden
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);
Forældreside (https://parent.example.com):
// Delt hemmelig nøgle (skal matche iframens nøgle)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Modtog besked fra upålidelig oprindelse:', 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('Beskeden er autentisk!');
// Behandl brugerdataene
console.log('Brugerdata:', userData);
} else {
console.error('Verifikation af beskedens signatur mislykkedes!');
}
});
Vigtige bemærkninger:
- Erstat
YOUR_SECURE_SHARED_SECRETmed en sikkert genereret delt hemmelig nøgle. - Den delte hemmelige nøgle skal være den samme i både iframen og forældresiden.
- Dette eksempel bruger HMAC-SHA256 til beskedautentificering.
Konklusion
postMessage API'en er et kraftfuldt værktøj til at muliggøre kommunikation på tværs af oprindelser i webapplikationer. Det er dog afgørende at forstå de potentielle sikkerhedsrisici og implementere passende sikkerhedsmønstre for at afbøde disse risici. Ved at følge de sikkerhedsmønstre og bedste praksis, der er beskrevet i denne vejledning, kan du sikkert bruge postMessage til at bygge robuste og sikre webapplikationer.
Husk altid at prioritere sikkerhed og holde dig opdateret med de seneste sikkerhedsmæssige bedste praksis for webudvikling. Gennemgå jævnligt din kode og dine sikkerhedskonfigurationer for at sikre, at dine applikationer er beskyttet mod potentielle sårbarheder.