PostMessage APIã䜿çšããå®å šãªã¯ãã¹ãªãªãžã³éä¿¡ãæ¢æ±ããŸãããŠã§ãã¢ããªã±ãŒã·ã§ã³ã®è匱æ§ãç·©åããããã®æ©èœãã»ãã¥ãªãã£ãªã¹ã¯ããã¹ããã©ã¯ãã£ã¹ãåŠã³ãŸãããã
ã¯ãã¹ãªãªãžã³éä¿¡: PostMessage API ã䜿çšããã»ãã¥ãªãã£ãã¿ãŒã³
çŸä»£ã®ãŠã§ãã§ã¯ãã¢ããªã±ãŒã·ã§ã³ãç°ãªããªãªãžã³ããã®ãªãœãŒã¹ãšå¯Ÿè©±ããå¿
èŠãé »ç¹ã«ãããŸããåäžãªãªãžã³ããªã·ãŒ (SOP) ã¯ãã¹ã¯ãªãããç°ãªããªãªãžã³ããã®ãªãœãŒã¹ã«ã¢ã¯ã»ã¹ããããšãå¶éãããæ¥µããŠéèŠãªã»ãã¥ãªãã£ã¡ã«ããºã ã§ããããããæ£åœãªçç±ã§ã¯ãã¹ãªãªãžã³éä¿¡ãå¿
èŠãšãªãã·ããªãªãååšããŸããpostMessage APIã¯ãããå®çŸããããã®å¶åŸ¡ãããã¡ã«ããºã ãæäŸããŸããããã®æœåšçãªã»ãã¥ãªãã£ãªã¹ã¯ãçè§£ããé©åãªã»ãã¥ãªãã£ãã¿ãŒã³ãå®è£
ããããšãäžå¯æ¬ ã§ãã
åäžãªãªãžã³ããªã·ãŒ (SOP) ã®çè§£
åäžãªãªãžã³ããªã·ãŒã¯ããŠã§ããã©ãŠã¶ã«ãããåºæ¬çãªã»ãã¥ãªãã£æŠå¿µã§ããããã¯ããŠã§ãããŒãžããã®ããŒãžãæäŸãããã¡ã€ã³ãšã¯ç°ãªããã¡ã€ã³ãžã®ãªã¯ãšã¹ããè¡ãããšãå¶éããŸãããªãªãžã³ã¯ãã¹ããŒã (ãããã³ã«)ããã¹ã (ãã¡ã€ã³)ãããã³ããŒãã«ãã£ãŠå®çŸ©ãããŸãããããã®ãããããç°ãªãå Žåããªãªãžã³ã¯ç°ãªããšèŠãªãããŸããäŸ:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
ãããã¯ãã¹ãŠç°ãªããªãªãžã³ã§ãããSOPã¯ãããã®éã§ã®çŽæ¥çãªã¹ã¯ãªããã¢ã¯ã»ã¹ãå¶éããŸãã
PostMessage APIã®ç޹ä»
postMessage APIã¯ãã¯ãã¹ãªãªãžã³éä¿¡ã®ããã®å®å
šã§å¶åŸ¡ãããã¡ã«ããºã ãæäŸããŸããããã«ãããã¹ã¯ãªããã¯ãªãªãžã³ã«é¢ä¿ãªããä»ã®ãŠã£ã³ã㊠(äŸ: iframeãæ°ãããŠã£ã³ããŠãã¿ã) ã«ã¡ãã»ãŒãžãéä¿¡ã§ããŸããåä¿¡åŽã®ãŠã£ã³ããŠã¯ãããã®ã¡ãã»ãŒãžãåŸ
ã¡åããé©åã«åŠçããããšãã§ããŸãã
ã¡ãã»ãŒãžãéä¿¡ããããã®åºæ¬çãªæ§æã¯æ¬¡ã®ãšããã§ã:
otherWindow.postMessage(message, targetOrigin);
otherWindow: ã¿ãŒã²ãããŠã£ã³ããŠãžã®åç §ã§ã (äŸ:window.parent,iframe.contentWindow, ãŸãã¯window.openããååŸãããŠã£ã³ããŠãªããžã§ã¯ã)ãmessage: éä¿¡ãããããŒã¿ã§ããã·ãªã¢ã©ã€ãºå¯èœãªä»»æã®JavaScriptãªããžã§ã¯ã (äŸ: æååãæ°å€ããªããžã§ã¯ããé å) ãæå®ã§ããŸããtargetOrigin: ã¡ãã»ãŒãžãéä¿¡ããããªãªãžã³ãæå®ããŸããããã¯æ¥µããŠéèŠãªã»ãã¥ãªãã£ãã©ã¡ãŒã¿ã§ãã
åä¿¡åŽã§ã¯ãmessage ã€ãã³ãããªãã¹ã³ããå¿
èŠããããŸã:
window.addEventListener('message', function(event) {
// ...
});
event ãªããžã§ã¯ãã«ã¯ã以äžã®ããããã£ãå«ãŸããŠããŸã:
event.data: ä»ã®ãŠã£ã³ããŠããéä¿¡ãããã¡ãã»ãŒãžãevent.origin: ã¡ãã»ãŒãžãéä¿¡ãããŠã£ã³ããŠã®ãªãªãžã³ãevent.source: ã¡ãã»ãŒãžãéä¿¡ãããŠã£ã³ããŠãžã®åç §ã
ã»ãã¥ãªãã£ãªã¹ã¯ãšè匱æ§
postMessageã¯SOPã®å¶éãåé¿ããæ¹æ³ãæäŸããŸãããæ
éã«å®è£
ããªããšæœåšçãªã»ãã¥ãªãã£ãªã¹ã¯ããããããŸãã以äžã¯äžè¬çãªè匱æ§ã§ã:
1. ã¿ãŒã²ãããªãªãžã³ã®äžäžèŽ
event.origin ããããã£ã®æ€èšŒãæ ãããšã¯ãé倧ãªè匱æ§ã§ããåä¿¡è
ãã¡ãã»ãŒãžãç²ç®çã«ä¿¡é Œãããšãä»»æã®ãŠã§ããµã€ããæªæã®ããããŒã¿ãéä¿¡ã§ããŠããŸããŸããã¡ãã»ãŒãžãåŠçããåã«ãå¿
ã event.origin ãæåŸ
ããããªãªãžã³ãšäžèŽããããšã確èªããŠãã ããã
äŸ (è匱ãªã³ãŒã):
window.addEventListener('message', function(event) {
// ãã®ãããªå®è£
ã¯ããªãã§ãã ããïŒ
processMessage(event.data);
});
äŸ (å®å šãªã³ãŒã):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('ä¿¡é Œã§ããªããªãªãžã³ããã®ã¡ãã»ãŒãžãåä¿¡ããŸãã:', event.origin);
return;
}
processMessage(event.data);
});
2. ããŒã¿ã€ã³ãžã§ã¯ã·ã§ã³
åä¿¡ããããŒã¿ (event.data) ãå®è¡å¯èœãªã³ãŒããšããŠæ±ã£ãããçŽæ¥DOMã«æ³šå
¥ããããããšãã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã° (XSS) ã®è匱æ§ã«ã€ãªããå¯èœæ§ããããŸãã䜿çšããåã«ãå¿
ãåä¿¡ããŒã¿ããµãã¿ã€ãºããæ€èšŒããŠãã ããã
äŸ (è匱ãªã³ãŒã):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // ãã®ãããªå®è£
ã¯ããªãã§ãã ããïŒ
}
});
äŸ (å®å šãªã³ãŒã):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // é©åãªãµãã¿ã€ãºé¢æ°ãå®è£
ãã
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// ããã«å
ç¢ãªãµãã¿ã€ãºåŠçãå®è£
ããŸãã
// äŸ: DOMPurifyãåæ§ã®ã©ã€ãã©ãªã䜿çšãã
return DOMPurify.sanitize(data);
}
3. äžéè (MITM) æ»æ
å®å šã§ãªããã£ãã« (HTTP) ãä»ããŠéä¿¡ãè¡ãããå ŽåãMITMæ»æè ã¯ã¡ãã»ãŒãžãååããæ¹ããããå¯èœæ§ããããŸããå®å šãªéä¿¡ã®ããã«ã¯ãåžžã«HTTPSã䜿çšããŠãã ããã
4. ã¯ãã¹ãµã€ããªã¯ãšã¹ããã©ãŒãžã§ãª (CSRF)
åä¿¡è ãé©åãªæ€èšŒãªãã«åä¿¡ã¡ãã»ãŒãžã«åºã¥ããŠã¢ã¯ã·ã§ã³ãå®è¡ããå Žåãæ»æè ã¯ã¡ãã»ãŒãžãåœé ããŠåä¿¡è ãéšããæå³ããªãã¢ã¯ã·ã§ã³ãå®è¡ãããå¯èœæ§ããããŸããã¡ãã»ãŒãžã«ç§å¯ã®ããŒã¯ã³ãå«ããåä¿¡åŽã§ãããæ€èšŒãããªã©ãCSRFä¿è·ã¡ã«ããºã ãå®è£ ããŠãã ããã
5. targetOriginã§ã®ã¯ã€ã«ãã«ãŒãã®äœ¿çš
targetOriginã*ã«èšå®ãããšãã©ã®ãªãªãžã³ã§ãã¡ãã»ãŒãžãåä¿¡ã§ããããã«ãªããŸããããã¯ãªãªãžã³ããŒã¹ã®ã»ãã¥ãªãã£ã®ç®çãç¡å¹ã«ããããã絶察ã«å¿
èŠãªå Žåãé€ãé¿ããã¹ãã§ãã*ã䜿çšããªããã°ãªããªãå Žåã¯ãã¡ãã»ãŒãžèªèšŒã³ãŒã (MAC) ãªã©ãä»ã®åŒ·åãªã»ãã¥ãªãã£å¯Ÿçãå®è£
ããŠãã ããã
äŸ (ããã¯é¿ããã¹ã):
otherWindow.postMessage(message, '*'); // 絶察ã«å¿
èŠãªå Žåãé€ãã'*'ã®äœ¿çšã¯é¿ããŠãã ãã
ã»ãã¥ãªãã£ãã¿ãŒã³ãšãã¹ããã©ã¯ãã£ã¹
postMessageã«é¢é£ãããªã¹ã¯ã軜æžããããã«ã以äžã®ã»ãã¥ãªãã£ãã¿ãŒã³ãšãã¹ããã©ã¯ãã£ã¹ã«åŸã£ãŠãã ãã:
1. 峿 Œãªãªãªãžã³æ€èšŒ
åä¿¡åŽã§åžžã« event.origin ããããã£ãæ€èšŒããŠãã ãããä¿¡é Œã§ãããªãªãžã³ã®äºåå®çŸ©æžã¿ãªã¹ããšæ¯èŒããŸããæ¯èŒã«ã¯å³å¯ç䟡 (===) ã䜿çšããŠãã ããã
2. ããŒã¿ã®ãµãã¿ã€ãºãšæ€èšŒ
postMessage ãéããŠåä¿¡ãããã¹ãŠã®ããŒã¿ã¯ã䜿çšããåã«ãµãã¿ã€ãºããæ€èšŒããŠãã ãããããŒã¿ãã©ã®ããã«äœ¿çšããããã«å¿ããŠãé©åãªãµãã¿ã€ãºæè¡ (äŸ: HTMLãšã¹ã±ãŒããURLãšã³ã³ãŒãã£ã³ã°ãå
¥åæ€èšŒ) ã䜿çšããŸããHTMLã®ãµãã¿ã€ãºã«ã¯DOMPurifyã®ãããªã©ã€ãã©ãªã䜿çšããŠãã ããã
3. ã¡ãã»ãŒãžèªèšŒã³ãŒã (MAC)
ã¡ãã»ãŒãžã®å®å šæ§ãšçæ£æ§ãä¿èšŒããããã«ãã¡ãã»ãŒãžã«ã¡ãã»ãŒãžèªèšŒã³ãŒã (MAC) ãå«ããŸããéä¿¡è ã¯å ±æç§å¯éµã䜿çšããŠMACãèšç®ããã¡ãã»ãŒãžã«å«ããŸããåä¿¡è ã¯åãå ±æç§å¯éµã䜿çšããŠMACãåèšç®ããåä¿¡ããMACãšæ¯èŒããŸããããããäžèŽããã°ãã¡ãã»ãŒãžã¯çæ£ã§ãããæ¹ãããããŠããªããšèŠãªãããŸãã
äŸ (HMAC-SHA256ã䜿çš):
// éä¿¡åŽ
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);
}
// åä¿¡åŽ
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('ä¿¡é Œã§ããªããªãªãžã³ããã®ã¡ãã»ãŒãžãåä¿¡ããŸãã:', 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('ã¡ãã»ãŒãžã¯çæ£ã§ãïŒ');
processMessage(message); // ã¡ãã»ãŒãžã®åŠçãç¶è¡
} else {
console.error('ã¡ãã»ãŒãžçœ²åã®æ€èšŒã«å€±æããŸããïŒ');
}
}
éèŠ: å ±æç§å¯éµã¯å®å šã«çæã»ä¿ç®¡ãããªããã°ãªããŸãããã³ãŒãå ã«ããŒãããŒãã³ãŒãã£ã³ã°ããããšã¯é¿ããŠãã ããã
4. ãã³ã¹ãšã¿ã€ã ã¹ã¿ã³ãã®äœ¿çš
ãªãã¬ã€æ»æãé²ãããã«ãã¡ãã»ãŒãžã«ãŠããŒã¯ãªãã³ã¹ (äžåºŠã ã䜿çšãããæ°å€) ãšã¿ã€ã ã¹ã¿ã³ããå«ããŸããåä¿¡è ã¯ããã®ãã³ã¹ã以åã«äœ¿çšãããŠããªãããšãããã³ã¿ã€ã ã¹ã¿ã³ãã蚱容å¯èœãªæéæ å ã§ããããšãæ€èšŒã§ããŸããããã«ãããæ»æè ã以åã«ååããã¡ãã»ãŒãžãåéãããªã¹ã¯ã軜æžãããŸãã
5. æå°æš©éã®åå
ä»ã®ãŠã£ã³ããŠã«ã¯ãå¿ èŠæå°éã®æš©éã®ã¿ãä»äžããŠãã ãããäŸãã°ãä»ã®ãŠã£ã³ããŠãããŒã¿ã®èªã¿åãããå¿ èŠãšããªãå ŽåãããŒã¿ã®æžã蟌ã¿ãèš±å¯ããªãã§ãã ãããæå°æš©éã®ååã念é ã«çœ®ããŠéä¿¡ãããã³ã«ãèšèšããŠãã ããã
6. ã³ã³ãã³ãã»ãã¥ãªãã£ããªã·ãŒ (CSP)
ã³ã³ãã³ãã»ãã¥ãªãã£ããªã·ãŒ (CSP) ã䜿çšããŠãã¹ã¯ãªãããããŒãã§ãããœãŒã¹ãã¹ã¯ãªãããå®è¡ã§ããã¢ã¯ã·ã§ã³ãå¶éããŸããããã¯ãpostMessage ããŒã¿ã®äžé©åãªåŠçããçããå¯èœæ§ã®ããXSSè匱æ§ã®åœ±é¿ã軜æžããã®ã«åœ¹ç«ã¡ãŸãã
7. å ¥åæ€èšŒ
åä¿¡ããŒã¿ã®æ§é ãšåœ¢åŒãåžžã«æ€èšŒããŠãã ãããæç¢ºãªã¡ãã»ãŒãžåœ¢åŒãå®çŸ©ããåä¿¡ããŒã¿ããã®åœ¢åŒã«æºæ ããŠããããšã確èªããŸããããã«ãããäºæããªãåäœãè匱æ§ãé²ãããšãã§ããŸãã
8. å®å šãªããŒã¿ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³
ã¡ãã»ãŒãžã®ã·ãªã¢ã©ã€ãºãšãã·ãªã¢ã©ã€ãºã«ã¯ãJSONãªã©ã®å®å
šãªããŒã¿ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³åœ¢åŒã䜿çšããŠãã ãããeval() ã Function() ã®ããã«ã³ãŒãå®è¡ãèš±å¯ãã圢åŒã®äœ¿çšã¯é¿ããŠãã ããã
9. ã¡ãã»ãŒãžãµã€ãºã®å¶é
postMessage ãéããŠéä¿¡ãããã¡ãã»ãŒãžã®ãµã€ãºãå¶éããŠãã ããã倧ããªã¡ãã»ãŒãžã¯éå°ãªãªãœãŒã¹ãæ¶è²»ãããµãŒãã¹æåŠ (DoS) æ»æã«ã€ãªããå¯èœæ§ããããŸãã
10. 宿çãªã»ãã¥ãªãã£ç£æ»
ã³ãŒãã®å®æçãªã»ãã¥ãªãã£ç£æ»ã宿œããŠãæœåšçãªè匱æ§ãç¹å®ãã察åŠããŠãã ãããpostMessage ã®å®è£
ã«ç¹ã«æ³šæãæãããã¹ãŠã®ã»ãã¥ãªãã£ãã¹ããã©ã¯ãã£ã¹ãéµå®ãããŠããããšã確èªããŠãã ããã
ã·ããªãªäŸ: iframeãšèŠªããŒãžéã®å®å šãªéä¿¡
https://iframe.example.comã§ãã¹ããããŠããiframeããhttps://parent.example.comã§ãã¹ããããŠãã芪ããŒãžãšéä¿¡ããå¿
èŠãããã·ããªãªãèããŸããiframeã¯ãåŠçã®ããã«ãŠãŒã¶ãŒããŒã¿ã芪ããŒãžã«éä¿¡ããå¿
èŠããããŸãã
Iframe (https://iframe.example.com):
// å
±æç§å¯éµãçæ (å®å
šãªéµçææ¹æ³ã«çœ®ãæããŠãã ãã)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// ãŠãŒã¶ãŒããŒã¿ãååŸ
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// ãŠãŒã¶ãŒããŒã¿ã芪ããŒãžã«éä¿¡
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);
芪ããŒãž (https://parent.example.com):
// å
±æç§å¯éµ (iframeã®éµãšäžèŽããå¿
èŠããããŸã)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('ä¿¡é Œã§ããªããªãªãžã³ããã®ã¡ãã»ãŒãžãåä¿¡ããŸãã:', 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('ã¡ãã»ãŒãžã¯çæ£ã§ãïŒ');
// ãŠãŒã¶ãŒããŒã¿ãåŠç
console.log('ãŠãŒã¶ãŒããŒã¿:', userData);
} else {
console.error('ã¡ãã»ãŒãžçœ²åã®æ€èšŒã«å€±æããŸããïŒ');
}
});
éèŠãªæ³šæç¹:
YOUR_SECURE_SHARED_SECRETããå®å šã«çæãããå ±æç§å¯éµã«çœ®ãæããŠãã ããã- å ±æç§å¯éµã¯ãiframeãšèŠªããŒãžã®äž¡æ¹ã§åãã§ãªããã°ãªããŸããã
- ãã®äŸã§ã¯ãã¡ãã»ãŒãžèªèšŒã«HMAC-SHA256ã䜿çšããŠããŸãã
çµè«
postMessage APIã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã§ã¯ãã¹ãªãªãžã³éä¿¡ãå¯èœã«ãã匷åãªããŒã«ã§ããããããæœåšçãªã»ãã¥ãªãã£ãªã¹ã¯ãçè§£ãããããã®ãªã¹ã¯ã軜æžããããã«é©åãªã»ãã¥ãªãã£ãã¿ãŒã³ãå®è£
ããããšãäžå¯æ¬ ã§ãããã®ã¬ã€ãã§æŠèª¬ããã»ãã¥ãªãã£ãã¿ãŒã³ãšãã¹ããã©ã¯ãã£ã¹ã«åŸãããšã§ãpostMessageãå®å
šã«äœ¿çšããŠãå
ç¢ã§å®å
šãªãŠã§ãã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããŸãã
åžžã«ã»ãã¥ãªãã£ãæåªå ãããŠã§ãéçºã®ææ°ã®ã»ãã¥ãªãã£ãã¹ããã©ã¯ãã£ã¹ãåžžã«ææ¡ããŠããããšãå¿ããªãã§ãã ãããã¢ããªã±ãŒã·ã§ã³ãæœåšçãªè匱æ§ããä¿è·ãããŠããããšã確èªããããã«ãã³ãŒããšã»ãã¥ãªãã£æ§æã宿çã«èŠçŽããŠãã ããã