Lab 5 - Bypassing CSP with JSONP and Iframe Injection
Este desafio de nível 5 consiste em explorar uma vulnerabilidade de Cross-Site Scripting (XSS) em uma aplicação web protegida por Content Security Policy (CSP), utilizando uma técnica de JSONP em um domínio permitido (apis.google.com) combinada com injeção via iframe para executar código JavaScript malicioso.
URL do Desafio: https://brunomenozzi.com/desafios/xss5.php
O objetivo é executar um alert(document.domain)
no contexto da página, contornando as restrições impostas pelo CSP.
-
Ao acessar a página sem parâmetros ou com
message
vazio, somos redirecionados para: https://brunomenozzi.com/desafios/xss5.php?message=You+Are+Not+Logged+InO parâmetro
message
é refletido diretamente no corpo da página dentro de uma<p><strong>Caro user, </strong><?php echo $message; ?></p>
. -
Analisando o código-fonte e headers, observamos os cabeçalhos de segurança, incluindo o Content Security Policy:

Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com; img-src 'self' https://brunomenozzi.com; style-src 'self'; object-src 'none'; base-uri 'self'; report-uri /csp-report-endpoint;
default-src 'self'
: Recursos padrão só do mesmo domínio.script-src 'self' https://apis.google.com
: Scripts permitidos do mesmo domínio ou de apis.google.com.img-src 'self' https://brunomenozzi.com
: Imagens do mesmo domínio ou especificamente de brunomenozzi.com.style-src 'self'
: Estilos só do mesmo domínio, sem inline.- Outros: Bloqueia objects, base-uri restrito.
Além disso, há X-Content-Type-Options: nosniff
que previne MIME sniffing, X-Frame-Options: DENY
que impede framing, e X-XSS-Protection: 1; mode=block
para proteção XSS.
- Testamos uma injeção básica no parâmetro
message
:
Resultado: O script inline é refletido, mas bloqueado pelo CSP (scripts inline não permitidos, só ‘self’ ou apis.google.com). Um relatório de violação CSP é enviado.https://brunomenozzi.com/desafios/xss5.php?message=<script>alert(1)</script>

Conclusão: O CSP bloqueia scripts inline e externos não autorizados. Precisamos de um script de um domínio permitido, como apis.google.com, ou ‘self’. O nosniff
impede truques de MIME como no lab anterior.
-
Para encontrar bypasses, acessamos o CSP Checker (https://cspbypass.com/) e colamos o CSP vigente.
-
O checker sugere potenciais vetores de ataque baseados nos domínios permitidos.
Para https://apis.google.com, ele destaca endpoints vulneráveis sob JSONP (JSON with Padding), que podem ser usados para injetar scripts. Um dos payloads sugeridos é um que explora o endpoint de autocomplete da Google, que retorna um JSONP com um callback customizado (podemos modificar ele).

Aqui tem uma lista com todos os cenários de bypass previstos pela comunidade: https://github.com/renniepak/CSPBypass/raw/refs/heads/main/data.tsv
Para explorar, precisamos:
- Injetar uma tag
<script>
apontando para o endpoint JSONP de apis.google.com. - Usar um callback que chame uma função para escrever o conteúdo injetado (de
q
) em um documento controlado. - Criar um iframe que carregue uma página “escrevível”, como uma página de erro (404 ou inválida), que resulte em um documento em branco ou manipulável no mesmo domínio.
Teste de Injeção:
- Criamos um iframe com ID
x
e src/%GG
(URL que causa erro). - Em seguida, uma tag
<script>
carregando o JSONP comq|<script>alert(document.domain)</script>
ecallback}x.contentDocument.write
.
Construindo o Payload Final:
Injetamos no message
:
<IFRAME id=x src="/%GG"></IFRAME><SCRIPT src="https://apis.google.com/complete/search?client=chrome&q=<SCRIPT>alert(document.domain)</SCRIPT>&callback=x.contentDocument.write"></SCRIPT>
Para encoding, acessamos um URL encoder online (como https://www.urlencoder.org/) e codificamos o payload.
Por que o Iframe é Necessário?*
- O payload JSONP retorna algo como:
callback(["sugestões", ...])
, mas seq
contiver<script>alert(document.domain)</script>
, o array incluirá isso. - Ao definir o
callback
comox.contentDocument.write
, ondex
é o ID do iframe, o JSONP chamax.contentDocument.write(["<script>alert...</script>", ...])
, escrevendo o array como string no documento do iframe. - Isso injeta o
<script>
no iframe, que executa no contexto do domínio pai (mesmo origem se o iframe for ‘self’). - O src do iframe como
/%GG
é uma URL inválida ou não existente, forçando o navegador a carregar uma página de erro (como 404 do servidor ou about:blank-like). Isso cria um documento vazio ou com erro no mesmo domínio, permitindo quecontentDocument.write
substitua seu conteúdo sem violar same-origin policy. Sem isso, escrever diretamente no documento principal poderia ser bloqueado ou não funcionar devido ao CSP.
THAT’S ALL FOLKS

- JSONP Bypass: JSONP permite cross-domain scripts via
<script src>
. O endpoint de apis.google.com retorna dados com um callback, que executa como JS. Ao controlar o callback parax.contentDocument.write
, ele escreve o array (incluindo oq
injetado) como string no iframe, parseando como HTML e executando o<script>
dentro. - Iframe para Escrita Controlada: O iframe com src inválida (
/%GG
) carrega um documento de erro no mesmo origem, vazio o suficiente para ser sobrescrito sem erros de cross-origin. Isso permite injetar e executar JS no contexto do domínio alvo, contornando CSP que permite o script src mas não inline direto. - Bypass de CSP: O script vem de apis.google.com (permitido), e o JS injetado roda no iframe de ‘self’. O CSP não checa o conteúdo do JSONP, só a origem do script.
- Outros Headers:
nosniff
não afeta pois não há MIME mismatch;X-Frame-Options
não impede iframes internos; XSS-Protection pode bloquear, mas o payload evita detecção.
Insight: O endpoint complete/search
é um serviço de sugestões de busca do Google que suporta JSONP via parâmetro callback
. Como apis.google.com está permitido em script-src
, podemos carregar um script de lá. O truque é usar o callback para chamar uma função que escreva no documento, injetando o conteúdo de q
como HTML/JS. No entanto, para escrever no DOM, precisamos de um contexto onde possamos controlar a escrita, como um iframe manipulável.
Quando a URL final é acessada, o navegador:
- Reflete o iframe e a tag
<script>
nomessage
. - O iframe carrega uma página de erro vazia.
- O
<script src>
carrega o JSONP, que chamax.contentDocument.write
com dados incluindo<script>alert(document.domain)</script>
. - O write injeta o script no iframe, executando o alert no domínio brunomenozzi.com.
Este desafio mostra como domínios permitidos em CSP podem ser abusados via endpoints JSONP, e como iframes manipuláveis facilitam injeções.
Se você estivesse usando ferramentas como o Google CSP Evaluator (https://csp-evaluator.withgoogle.com/), ele avisaria que a inclusão de scripts na lista de permissões de determinados domínios abre vulnerabilidades se o domínio for compatível com JSONP, como no nosso caso:
