
Laboratório Docker PoC: encadeando bypass de upload de arquivos + XSS armazenado para criar contas de administrador. Recurso educacional para pen testers.
Uma aplicação web deliberadamente vulnerável demonstrando como um bypass de upload de arquivo se encadeia com XSS armazenado para criar contas de administrador backdoor, mesmo com proteções CSP, CORS e CSRF em vigor.
Postagem completa no blog: KurtiseBear Blog
Este é um laboratório educacional para treinamento de segurança defensiva. Não implante isso em nenhum lugar publicamente acessível.
A aplicação possui controles de segurança reais:
'self' (mas com 'unsafe-inline' e 'unsafe-eval')Um atacante com uma conta de usuário de baixo privilégio encadeia duas vulnerabilidades para contornar todas elas:
Bypass de upload de arquivo — O formulário de upload restringe a .pdf através de um atributo accept do lado do cliente, mas o servidor não realiza nenhuma validação de tipo de arquivo. Um atacante faz upload de um arquivo .js contendo JavaScript. O endpoint de download o serve a partir da mesma origem, então CSP e CORS não o bloqueiam.
XSS armazenado via assunto da mensagem — O recurso de mensagens armazena a entrada do usuário sem sanitização. A caixa de entrada do administrador renderiza o assunto da mensagem como HTML bruto. O payload XSS usa um manipulador `` para buscar o script enviado e executá-lo com eval(). CSP permite isso porque 'unsafe-inline' e 'unsafe-eval' são permitidos.
CSRF ausente no endpoint da API — A API de gerenciamento de usuários (/api/manage-user.php) não valida tokens CSRF, mesmo que os endpoints de formulário o façam. O payload XSS chama esta API usando a sessão de mesma origem do administrador. Mesmo se CSRF estivesse presente, JavaScript de mesma origem poderia ler o token do DOM.
O resultado: quando um administrador abre sua caixa de entrada, o XSS dispara, o JavaScript cria uma conta de administrador backdoor usando a sessão do administrador. Todas as defesas estão em vigor e funcionando. A cadeia funciona porque nunca sai da origem.
docker-compose up -d
Aguarde 10 a 15 segundos para o MySQL inicializar, depois visite http://localhost:8080
| Função | Senha | |
|---|---|---|
| Admin | [email protected] | admin |
| Usuário | [email protected] | user |
Navegue até http://localhost:8080 e faça login com [email protected] / user.
Vá para Upload Files. O formulário diz "PDF only" mas só impõe isso do lado do cliente. Ou:
accept=".pdf" do input de arquivo, ouFaça upload do payload.js fornecido (ou do seu próprio). Anote o ID do arquivo retornado (ex.: 1).
O arquivo enviado agora é servido de /api/download.php?file_id=1 na mesma origem. CSP não bloqueará requisições para este endpoint porque é 'self'.
Vá para Send Message. No campo de assunto, insira:
r.blob()).then(b=>b.text()).then(eval)">
(Substitua 1 pelo ID real do arquivo do passo 2.)
Coloque qualquer coisa no corpo. Marque prioridade se quiser que fique no topo da caixa de entrada. Envie.
O manipulador onerror funciona porque CSP permite 'unsafe-inline'. O eval() funciona porque CSP permite 'unsafe-eval'. A requisição fetch para o endpoint de download funciona porque é de mesma origem.
Saia. Faça login como [email protected] / admin. Vá para Inbox.
O assunto da mensagem é renderizado como HTML bruto. A tag `` falha ao carregar, o manipulador onerror é acionado, busca o payload enviado e o eval() o executa. O payload faz um POST para /api/manage-user.php usando o cookie de sessão do administrador (anexado automaticamente para requisições de mesma origem). Nenhum token CSRF é necessário porque o endpoint da API não verifica um.
Vá para Users. Você deve ver um novo usuário: BackdoorAdmin com função admin e email [email protected].
Saia e faça login com [email protected] / Compromised1! para confirmar.
CSP bloqueia scripts externos
--> Mas o payload está hospedado na mesma origem via upload de arquivo
--> E unsafe-inline/unsafe-eval permitem o manipulador onerror e o eval()
CORS bloqueia requisições de origem cruzada
--> Mas toda requisição na cadeia é de mesma origem
Tokens CSRF protegem envios de formulário
--> Mas o endpoint da API não os valida
--> E mesmo que validasse, JS de mesma origem pode ler tokens do DOM
Cookies de sessão têm proteções padrão
--> Mas requisições de mesma origem os carregam automaticamente
As defesas estão todas funcionando corretamente. Elas foram projetadas para parar ataques de origem cruzada. Esta cadeia nunca sai da origem.
O que realmente quebraria esta cadeia:
htmlspecialchars() em toda saída controlada pelo usuário. A caixa de entrada renderiza $row['subject'] bruto. Isso elimina o XSS completamente.'unsafe-inline' e 'unsafe-eval'. Use nonces ou hashes para scripts inline legítimos. Isso bloqueia o manipulador onerror e o eval().docker-compose down -v
Esta aplicação é deliberadamente vulnerável. É projetada apenas para fins educacionais e de treinamento de segurança defensiva. Não a implante em nenhuma rede acessível a usuários não confiáveis. Não use essas técnicas contra sistemas sem autorização explícita por escrito.