- Critical Thinking - Bug Bounty Podcast
- Posts
- Abusing iframes from a Client-side Hacker
Abusing iframes from a Client-side Hacker
In this episode Justin does a mini deep dive into the world of iframes, starting with why they’re significant, their attributes, and how to attack them.
Hacker TL;DR
iframes
iframes let you embed other pages into your site and interact with them. They're useful for clickjacking, OAuth flows, and any scenario where you can benefit from controlling the context of another page. Frame references (like
window.frames
,opener
, andparent
) allow messaging, DOM access (if same-origin), and more.iframe Attributes
Attributes like
src
,srcdoc
,allow
,csp
, andsandbox
can significantly change how an iframe behaves. For example,srcdoc
lets you inject payloads directly into the iframe with same-origin privileges. Thecsp
attribute can be used to block security features from loading.allow
controls access to powerful APIs when embedded.Frame References + window.name Hijacking
If you know what name a popup will be opened with, you can pre-create a frame with the same name. The browser will reuse it, giving you control over what loads inside. This can be used to intercept navigation or inject malicious content into a window the victim expects to trust.
Combining iframe Embedding + XSS
In the iframe sandwich attack, you embed a vulnerable page and use XSS inside it to inject another iframe pointing back to your own domain. This creates a structure where the victim is framed by your content on both sides, allowing full control over the interaction surface.
Demonstrating iframe interaction invisibly
You can make an iframe follow the user's cursor while still allowing interaction with your page. This shows that you can trick users into clicking on embedded content without blocking input or breaking UI flow — a simple but effective form of clickjacking.

ThreatLocker Cloud Control leverages built-in intelligence to assess whether a connection from a protected device originates from a trusted network.
By analyzing connection patterns from protected computers and mobile devices, it automatically identifies and allows trusted connections.
Find out more here:
iframes
An inline frame is an HTML element that lets you embed another page inside your website. And as hackers, that’s of course useful for clickjacking.
Let’s say you’re able to embed an oAuth auth flow into your website, with one click from the victim on your site you’re able to get a full ATO, with just one click. So don’t overlook this vuln just because of the bad rep it gets from being reported by people who don’t know what they’re doing, it’s incredibly useful in a lot of different scenarios in the right hands.
Another thing to know about iframes is frame references, which are extremely important when exploiting a lot of client-side vulnerabilities. Because if you have a reference to a frame, then you can send postMessage to it, if it’s same-origin you can jump into that page and manipulate the DOM, etc. So nowadays, having a frame reference to a top level page really enables you to do a lot of stuff, the most useful being the sending postMessages feature.
A postMessage lets a window send messages to another window (like an iframe), and the other side can listen and respond. It’s a JavaScript method used to communicate between different windows, tabs, and frames, even across origins.
To wrap this up: an iframe
is one way to get a frame reference, but depending on the setup, a few others might work too — like opener
, parent
, top
, frames
, or accessing .contentWindow
directly from an iframe element.
src & srcdoc
These control what the iframe is pointed at, they control what content gets loaded inside of the iframe.
src: Points the iframe to a url, it can be a .js url so you can get XSS, could be an absolute url so you can point it to a different website embedding a victim page.
srcdoc: Define what should be inside the iframe, lets you write the HTML directly into the iframe, so the content is embedded, NOT fetched from a url.
A few tips:
srcdoc
gets preference if bothsrc
andsrcdoc
exist.srcdoc
is always treated as same-origin, because it’s created inside the page.This is especially useful because
<script>
triggers when the page is loaded, but if you do<iframe srcdoc="<script>payload</script>"></iframe>
it will trigger even after the page is fully loaded.Other ways you can get around the
<script>
load time limitation is by using event handlers likeonload
,onerror
, you know the rest. (but just in case you need a refresher)
CSP
Content Security Policy helps control which resources a page is allowed to load. A good catch is that the csp
attribute can be applied directly inside the <iframe
, even if you don’t control the source URL or the server. <iframe src="a-domain-here" csp="script-src 'none';"></iframe>
in this example, even if a-domain-here
tries to run JS, the browser will block it inside the iframe.
As an attacker, when you come against the CSP what you’ll try to do is get out/around that CSP. It’s important to pay attention to how that iframe is being loaded and how security features are being implemented. It’s not uncommon to see security features and scripts being loaded from other places, and when you control that CSP attribute you can try to block those protections from loading. So, think about how to fiddle with the CSP when you can embed a page into an iframe, because you control that, and it opens up some really interesting possibilities.
Allow
The allow attribute defines the permission policy for that specific page, it controls the access to resources like clipboard, microphone, camera and location.
Recently, someone told Justin (we cannot name you here for obvious reasons, but I know you’re reading — thank you!) that iframes inherent whatever permission the top level page has. So, if the page embedding the iframe has access to the clipboard, the page inside the iframe can have it too. Cool, right?
By default, certain powerful features (like clipboard access, camera, mic, etc.) are not available to cross-origin iframes. But when a top-level page has permission and intentionally embeds a page with the appropriate allow
attribute, it can delegate those permissions down into the iframe.
As hackers, we should keep an eye out for:
Inject a cross-origin iframe and set permissive
allow
attributes — see what features the browser grants.Test for things like:
allow="clipboard-read clipboard-write"
allow="camera; microphone"
allow="display-capture"
Look for top-level pages that already have permission (e.g. via user gesture) and see if your iframe inherits them.
If the target embeds your page and grants those permissions, you might be able to access powerful browser APIs with a user click.
Name
The name attribute gives your <iframe>
a unique identifier that can be targeted or referenced from scripts or links. It doesn’t change when you navigate to a new URL, you can name it anything, and that name will persist across navigations.
Another cool thing is that the name
attribute can be used to specify which window window.open()
calls should target. Forms and links can also be pointed at a named iframe using the target
attribute.
referrerPolicy
The referrerpolicy attribute defines how much of the parent page’s URL should be sent as the Referer
header when the iframe loads its content, helping limit information leaks to external sites.
But browsers also honor the <meta name="referrer" content="...">
tag, which can globally affect the referrer policy of the entire page even when injected dynamically into the DOM. In some cases, like DOMPurify-based sanitization, you might not be able to inject script tags or event handlers, but you can try a <meta>
tag and the browser can still apply its effects for some reason.
Sandbox
The sandbox
attribute tells the browser to run the iframe in a restricted environment. If you pass no arguments to it, it becomes a really hardened iframe where you cannot execute JS or submit form, no top-level navigation, plugins, etc. Nothing works unless you allow it. As an attacker, you should be aware of <iframe sandbox="allow-same-origin allow-scripts">
but there are also some other interesting ones:
(no value)
: Applies all restrictionsallow-forms
: Allows form submissionallow-modals
: Allows to open modal windowsallow-popups
: Allows popupsallow-scripts
: Allows to run scriptsallow-same-origin
: Allows the iframe content to be treated as being from the same originallow-presentation
: Allows to start a presentation sessionallow-pointer-lock
: Allows to use the Pointer Lock APIallow-top-navigation
: Allows the iframe content to navigate its top-level browsing contextallow-orientation-lock
: Allows to lock the screen orientationallow-popups-to-escape-sandbox
: Allows popups to open new windows without inheriting the sandboxingallow-top-navigation-by-user-activation
: Allows the iframe content to navigate its top-level browsing context, but only if initiated by user
iframe Attacks
window.name Hijacking
You have a page that you control and you
window.open()
a victim pageThe victim clicks a button on that page, which triggers another
window.open()
with an specific name, one you already knowIf you create an iframe on your own page using that same name, you can hijack the new window.
Now, the victim page and your attacker page are part of a linked browsing context because of the opener relationship.
Because of that, they can reference each other’s frames
Now that you know the origin of the second window the victim will open, you embed an iframe inside your attacker page with the same name as the window the victim is about to open.
When the user clicks the button, instead of popping up a new window, the browser will reuse the iframe you control — and load the victim’s content into it.
Hopefully I did a good job explaining it.
This is interesting because there’s often implicit trust between opener and opened windows, especially if the target assumes it’s being opened by a trusted origin.
Once you control the parent, you can:
Redirect the iframe to a malicious page
Intercept or spoof postMessages
Abuse access to frame references
Potentially bypass navigation restrictions
Identify a Victim Page Vulnerable to XSS:
Find a page (
victim.com/page
) that reflects user input without proper sanitisation, allowing for XSS injection.
Craft a Malicious Page (Attacker's Page):
Create a page (
attacker.com
) that embeds the vulnerable victim page within an iframe.
Inject Malicious Script into the Victim Page:
Use the XSS vulnerability to inject a script that, for example, creates another iframe pointing back to the attacker's domain.
Create the "Sandwich" Structure:
The structure now looks like:
Top Layer:
attacker.com
(attacker's control)Middle Layer:
victim.com/page
(vulnerable page with injected script)Bottom Layer:
attacker.com
(embedded via injected iframe)
Leverage the Structure for Malicious Activities:
The attacker can now:
Execute scripts in the context of the victim's page.
Steal sensitive information.
Perform actions on behalf of the user.
Clickjacking
Not much to talk about it here, Justin basically gave us a cool tip that you can make an iframe that follows the user’s cursor around and it still lets them interact with the page. Here’s a simple page to showcase that:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cursor tracker idk</title>
<style>
body {
margin: 0;
height: 100vh;
background: #f0f0f0;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
#tracker-iframe {
position: fixed;
width: 155px;
height: 105px;
opacity: 0.1;
border: none;
z-index: 99999;
}
#debug-dot {
position: fixed;
width: 10px;
height: 10px;
background-color: blue;
border-radius: 50%;
z-index: 10000;
pointer-events: none;
}
#counter-display {
font-size: 2rem;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div id="counter-display">Counter: 0</div>
<button id="click-me">Click Me</button>
<iframe
id="tracker-iframe" src="<https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d831455.1954411116!2d139.11046701767086!3d35.507446682702074!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x605d1b87f02e57e7%3A0x2e01618b22571b89!2sTokyo!5e0!3m2!1sen!2sjp!4v1744788647055!5m2!1sen!2sjp>"
allowfullscreen=""
loading="lazy"
referrerpolicy="no-referrer-when-downgrade">
</iframe>
<div id="debug-dot"></div>
<script>
const iframe = document.getElementById('tracker-iframe');
const dot = document.getElementById('debug-dot');
const counterDisplay = document.getElementById('counter-display');
const button = document.getElementById('click-me');
let counter = 0;
document.addEventListener('mousemove', (e) => {
const x = e.clientX;
const y = e.clientY;
dot.style.left = `${x - 5}px`;
dot.style.top = `${y - 5}px`;
iframe.style.left = `${x - 150}px`;
iframe.style.top = `${y - 100}px`;
});
button.addEventListener('click', () => {
counter++;
counterDisplay.textContent = `Counter: ${counter}`;
});
</script>
</body>
</html>
iframe Fun Facts
Content-Security-Policy’s frame-ancestors will override X-Frame-Options
iframes can redirect their parents and openers by default
Number of iframes on a page is a famous cross-site leak with window.frames
That's all for this week!
As always, keep hacking!