Tutustu turvalliseen ristiinlähtöiseen kommunikaatioon PostMessage API:n avulla. Opi sen ominaisuuksista, turvallisuusriskeistä ja parhaista käytännöistä verkkosovellusten haavoittuvuuksien torjumiseksi.
Ristiinlähtöinen kommunikaatio: Turvallisuusmallit PostMessage API:n avulla
Nykyaikaisessa verkossa sovellusten on usein oltava vuorovaikutuksessa eri alkuperää olevien resurssien kanssa. Same-Origin Policy (SOP) on keskeinen turvallisuusmekanismi, joka rajoittaa skriptejä pääsemästä käsiksi eri alkuperää oleviin resursseihin. On kuitenkin olemassa laillisia skenaarioita, joissa ristiinlähtöinen kommunikaatio on välttämätöntä. postMessage-API tarjoaa hallitun mekanismin tämän saavuttamiseksi, mutta on elintärkeää ymmärtää sen mahdolliset turvallisuusriskit ja toteuttaa asianmukaiset turvallisuusmallit.
Same-Origin Policyn (SOP) ymmärtäminen
Same-Origin Policy on perustavanlaatuinen tietoturvakonsepti verkkoselaimissa. Se rajoittaa verkkosivuja tekemästä pyyntöjä eri verkkotunnukseen kuin se, joka tarjosi verkkosivun. Alkuperä määritellään skeeman (protokolla), isännän (verkkotunnus) ja portin perusteella. Jos jokin näistä eroaa, alkuperiä pidetään erilaisina. Esimerkiksi:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Nämä kaikki ovat eri alkuperiä, ja SOP rajoittaa suoraa skriptien pääsyä niiden välillä.
PostMessage API:n esittely
postMessage-API tarjoaa turvallisen ja hallitun mekanismin ristiinlähtöiseen kommunikaatioon. Se antaa skripteille mahdollisuuden lähettää viestejä muihin ikkunoihin (esim. iframeihin, uusiin ikkunoihin tai välilehtiin) niiden alkuperästä riippumatta. Vastaanottava ikkuna voi sitten kuunnella näitä viestejä ja käsitellä niitä vastaavasti.
Viestin lähettämisen perussyntaksi on:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Viittaus kohdeikkunaan (esim.window.parent,iframe.contentWindowtaiwindow.open-kutsulla saatu ikkunaobjekti).message: Data, jonka haluat lähettää. Tämä voi olla mikä tahansa JavaScript-objekti, joka voidaan sarjallistaa (esim. merkkijonot, numerot, objektit, taulukot).targetOrigin: Määrittää alkuperän, johon haluat lähettää viestin. Tämä on kriittinen turvallisuusparametri.
Vastaanottavassa päässä sinun on kuunneltava message-tapahtumaa:
window.addEventListener('message', function(event) {
// ...
});
event-objekti sisältää seuraavat ominaisuudet:
event.data: Toisen ikkunan lähettämä viesti.event.origin: Viestin lähettäneen ikkunan alkuperä.event.source: Viittaus viestin lähettäneeseen ikkunaan.
Turvallisuusriskit ja haavoittuvuudet
Vaikka postMessage tarjoaa tavan kiertää SOP-rajoituksia, se tuo myös mukanaan potentiaalisia turvallisuusriskejä, jos sitä ei toteuteta huolellisesti. Tässä on joitain yleisiä haavoittuvuuksia:
1. Kohdealkuperän yhteensopimattomuus
event.origin-ominaisuuden validoinnin laiminlyönti on kriittinen haavoittuvuus. Jos vastaanottaja luottaa sokeasti viestiin, mikä tahansa verkkosivusto voi lähettää haitallista dataa. Varmista aina, että event.origin vastaa odotettua alkuperää ennen viestin käsittelyä.
Esimerkki (Haavoittuva koodi):
window.addEventListener('message', function(event) {
// ÄLÄ TEE NÄIN!
processMessage(event.data);
});
Esimerkki (Turvallinen koodi):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Vastaanotettu viesti epäluotettavasta lähteestä:', event.origin);
return;
}
processMessage(event.data);
});
2. Datan injektointi
Vastaanotetun datan (event.data) käsitteleminen suoritettavana koodina tai sen suora injektointi DOM:iin voi johtaa Cross-Site Scripting (XSS) -haavoittuvuuksiin. Puhdista ja validoi aina vastaanotettu data ennen sen käyttöä.
Esimerkki (Haavoittuva koodi):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // ÄLÄ TEE NÄIN!
}
});
Esimerkki (Turvallinen koodi):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Toteuta asianmukainen puhdistusfunktio
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Toteuta vankka puhdistuslogiikka tähän.
// Käytä esimerkiksi DOMPurify-kirjastoa tai vastaavaa
return DOMPurify.sanitize(data);
}
3. Väliintulohyökkäykset (MITM)
Jos viestintä tapahtuu suojaamattoman kanavan (HTTP) kautta, väliintulohyökkääjä voi siepata ja muokata viestejä. Käytä aina HTTPS:ää turvalliseen viestintään.
4. Sivustojen välinen pyyntöväärennös (CSRF)
Jos vastaanottaja suorittaa toimintoja vastaanotetun viestin perusteella ilman asianmukaista validointia, hyökkääjä voi mahdollisesti väärentää viestejä huijatakseen vastaanottajaa suorittamaan tahattomia toimintoja. Toteuta CSRF-suojausmekanismeja, kuten salaisen tunnisteen sisällyttäminen viestiin ja sen tarkistaminen vastaanottajan puolella.
5. Yleismerkkien käyttö targetOrigin-parametrissa
targetOrigin-parametrin asettaminen arvoon * antaa minkä tahansa alkuperän vastaanottaa viestin. Tätä tulisi välttää, ellei se ole ehdottoman välttämätöntä, koska se kumoaa alkuperäpohjaisen turvallisuuden tarkoituksen. Jos sinun on käytettävä *, varmista, että toteutat muita vahvoja turvatoimia, kuten viestin todennuskoodit (MAC).
Esimerkki (Vältä tätä):
otherWindow.postMessage(message, '*'); // Vältä '*' käyttöä, ellei se ole ehdottoman välttämätöntä
Turvallisuusmallit ja parhaat käytännöt
postMessage-viestintään liittyvien riskien pienentämiseksi noudata seuraavia turvallisuusmalleja ja parhaita käytäntöjä:
1. Tiukka alkuperän validointi
Validoi aina event.origin-ominaisuus vastaanottajan puolella. Vertaa sitä ennalta määriteltyyn luotettujen alkuperien luetteloon. Käytä vertailuun tiukkaa yhtäsuuruutta (===).
2. Datan puhdistus ja validointi
Puhdista ja validoi kaikki postMessage-kutsun kautta vastaanotettu data ennen sen käyttöä. Käytä asianmukaisia puhdistustekniikoita riippuen siitä, miten dataa käytetään (esim. HTML-escapointi, URL-koodaus, syötteen validointi). Käytä DOMPurify-kirjaston kaltaisia työkaluja HTML:n puhdistamiseen.
3. Viestin todennuskoodit (MAC)
Sisällytä viestiin viestin todennuskoodi (MAC) sen eheyden ja aitouden varmistamiseksi. Lähettäjä laskee MAC-koodin käyttämällä jaettua salaista avainta ja sisällyttää sen viestiin. Vastaanottaja laskee MAC-koodin uudelleen käyttämällä samaa jaettua salaista avainta ja vertaa sitä vastaanotettuun MAC-koodiin. Jos ne vastaavat toisiaan, viestiä pidetään aitona ja muuttumattomana.
Esimerkki (HMAC-SHA256:n käyttö):
// Lähettäjä
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);
}
// Vastaanottaja
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Vastaanotettu viesti epäluotettavasta lähteestä:', 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('Viesti on aito!');
processMessage(message); // Jatka viestin käsittelyä
} else {
console.error('Viestin allekirjoituksen tarkistus epäonnistui!');
}
}
Tärkeää: Jaettu salainen avain on luotava ja tallennettava turvallisesti. Vältä avaimen kovakoodaamista koodiin.
4. Noncen ja aikaleimojen käyttö
Uusintahyökkäysten estämiseksi sisällytä viestiin ainutkertainen nonce (kertakäyttöinen numero) ja aikaleima. Vastaanottaja voi tällöin varmistaa, ettei noncea ole käytetty aiemmin ja että aikaleima on hyväksyttävän aikajakson sisällä. Tämä pienentää riskiä, että hyökkääjä toistaisi aiemmin siepattuja viestejä.
5. Vähimpien oikeuksien periaate
Myönnä toiselle ikkunalle vain vähimmäistarvittavat oikeudet. Esimerkiksi, jos toisen ikkunan tarvitsee vain lukea dataa, älä anna sen kirjoittaa dataa. Suunnittele viestintäprotokollasi vähimpien oikeuksien periaatetta noudattaen.
6. Sisällön turvallisuuspolitiikka (CSP)
Käytä sisällön turvallisuuspolitiikkaa (CSP) rajoittaaksesi lähteitä, joista skriptejä voidaan ladata, ja toimintoja, joita skriptit voivat suorittaa. Tämä voi auttaa lieventämään XSS-haavoittuvuuksien vaikutuksia, jotka saattavat johtua postMessage-datan virheellisestä käsittelystä.
7. Syötteen validointi
Validoi aina vastaanotetun datan rakenne ja muoto. Määrittele selkeä viestiformaatti ja varmista, että vastaanotettu data noudattaa tätä muotoa. Tämä auttaa estämään odottamattomia toimintahäiriöitä ja haavoittuvuuksia.
8. Turvallinen datan sarjallistaminen
Käytä turvallista datan sarjallistamismuotoa, kuten JSON, viestien sarjallistamiseen ja purkamiseen. Vältä formaatteja, jotka sallivat koodin suorittamisen, kuten eval() tai Function().
9. Rajoita viestin kokoa
Rajoita postMessage-kutsulla lähetettävien viestien kokoa. Suuret viestit voivat kuluttaa liikaa resursseja ja mahdollisesti johtaa palvelunestohyökkäyksiin.
10. Säännölliset tietoturvatarkastukset
Suorita säännöllisiä tietoturvatarkastuksia koodillesi mahdollisten haavoittuvuuksien tunnistamiseksi ja korjaamiseksi. Kiinnitä erityistä huomiota postMessage-toteutukseen ja varmista, että kaikkia tietoturvan parhaita käytäntöjä noudatetaan.
Esimerkkiskenaario: Turvallinen viestintä iframen ja sen isäntäsivun välillä
Harkitse skenaariota, jossa https://iframe.example.com-osoitteessa isännöity iframe tarvitsee kommunikoida https://parent.example.com-osoitteessa isännöidyn isäntäsivunsa kanssa. Iframen on lähetettävä käyttäjätietoja isäntäsivulle käsiteltäväksi.
Iframe (https://iframe.example.com):
// Luo jaettu salainen avain (korvaa turvallisella avaimen luontimenetelmällä)
const sharedSecret = 'SINUN_TURVALLINEN_JAETTU_SALAINEN_AVAIMESI';
// Hae käyttäjätiedot
const userData = {
name: 'Matti Meikäläinen',
email: 'matti.meikalainen@example.com'
};
// Lähetä käyttäjätiedot isäntäsivulle
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);
Isäntäsivu (https://parent.example.com):
// Jaettu salainen avain (täytyy vastata iframen avainta)
const sharedSecret = 'SINUN_TURVALLINEN_JAETTU_SALAINEN_AVAIMESI';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Vastaanotettu viesti epäluotettavasta lähteestä:', 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('Viesti on aito!');
// Käsittele käyttäjätiedot
console.log('Käyttäjätiedot:', userData);
} else {
console.error('Viestin allekirjoituksen tarkistus epäonnistui!');
}
});
Tärkeitä huomioita:
- Korvaa
SINUN_TURVALLINEN_JAETTU_SALAINEN_AVAIMESIturvallisesti luodulla jaetulla salaisella avaimella. - Jaetun salaisen avaimen on oltava sama sekä iframessa että isäntäsivulla.
- Tämä esimerkki käyttää HMAC-SHA256:ta viestin todentamiseen.
Yhteenveto
postMessage-API on tehokas työkalu ristiinlähtöisen viestinnän mahdollistamiseksi verkkosovelluksissa. On kuitenkin ratkaisevan tärkeää ymmärtää mahdolliset turvallisuusriskit ja toteuttaa asianmukaiset turvallisuusmallit näiden riskien pienentämiseksi. Noudattamalla tässä oppaassa esitettyjä turvallisuusmalleja ja parhaita käytäntöjä voit käyttää postMessage-kutsua turvallisesti vankkojen ja turvallisten verkkosovellusten rakentamiseen.
Muista aina priorisoida turvallisuus ja pysyä ajan tasalla verkkokehityksen uusimmista turvallisuuden parhaista käytännöistä. Tarkista säännöllisesti koodisi ja turvallisuusasetuksesi varmistaaksesi, että sovelluksesi on suojattu potentiaalisia haavoittuvuuksia vastaan.