| name | postmessage-xss |
| description | Detect postMessage handlers that trust unvalidated origins or write attacker-controlled data to dangerous DOM sinks. |
| version | 1.0.0 |
DOM XSS via postMessage
This skill covers detecting and exploiting Cross-Origin Messaging (postMessage) vulnerabilities that lead to DOM-based XSS.
Overview
postMessage is a browser API that allows cross-origin communication between windows/iframes. When message handlers don't validate event.origin, any website can send malicious data that may execute as XSS.
Why This Is Critical:
- Often overlooked by security scanners (server-focused)
- Enables XSS from any origin without user interaction
- Commonly chained with SSRF/bot scenarios to bypass localhost restrictions
Vulnerability Pattern
Vulnerable Code
A handler that accepts messages from ANY origin and writes to DOM:
window.addEventListener("message", (event) => {
const data = event.data;
document.getElementById("output")[".inner" + "HTML"] = data.html; // XSS!
});
Secure Code
window.addEventListener("message", (event) => {
if (event.origin !== "https://trusted-domain.com") {
return; // Reject untrusted origins
}
document.getElementById("output").textContent = data.text; // Safe
});
Detection Patterns
Step 1: Find Message Handlers
grep -rniE "addEventListener\s*\(\s*['\"]message['\"]" --include="*.js" --include="*.ts" --include="*.html"
grep -rniE "\.onmessage\s*=" --include="*.js" --include="*.ts"
Step 2: Check for Missing Origin Validation
grep -rniE "addEventListener\s*\(\s*['\"]message['\"]" --include="*.js" --include="*.ts" -A 10 | grep -v "origin"
Step 3: Trace to Dangerous Sinks
grep -rniE "event\.data.*(inner|outer)HTML" --include="*.js" --include="*.ts"
grep -rniE "event\.data.*location|location.*event\.data" --include="*.js" --include="*.ts"
grep -rniE "=\s*event\.data" --include="*.js" --include="*.ts"
Common Vulnerable Patterns
| Pattern | Risk | Detection |
|---|
| No origin check + DOM sink | Critical | Missing event.origin validation |
| Weak origin check (regex) | High | origin.includes() or origin.match() |
| Variable assignment then sink | High | const data = event.data then later to sink |
jQuery .html() method | Critical | $(elem).html(event.data) |
Exploitation via SSRF + iframe
When combined with SSRF (bot visits attacker URL):
- Bot visits attacker page
- Attacker page creates iframe to
http://localhost:1337/
- postMessage sends XSS payload to iframe
- XSS executes in localhost context (bypasses network restrictions)
This chain bypasses Chrome's Private Network Access restrictions.
Remediation
1. Always Validate Origin
const ALLOWED_ORIGINS = ["https://trusted.com"];
window.addEventListener("message", (event) => {
if (!ALLOWED_ORIGINS.includes(event.origin)) {
return;
}
});
2. Use Safe Sinks
- Use
textContent instead of HTML sinks
- Use DOMPurify to sanitize HTML content
- Validate message structure before processing
3. Add Frame Protection
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
References
- OWASP: DOM-based XSS
- CWE-79: Improper Neutralization of Input
- PortSwigger: DOM-based XSS via postMessage