
Laboratório local que reproduz XSS armazenado na geração de documentação OpenAPI do oRPC (CVE-2026-33331), com versões vulnerável e corrigida para comparação e um PoC independente.
Laboratório de prova de conceito para XSS armazenado na geração de documentação OpenAPI do orpc
O oRPC constrói páginas de documentação de API a partir de uma especificação OpenAPI. Antes da v1.13.9, ele despejava a especificação diretamente em uma tag <script> usando JSON.stringify() sem codificação HTML. Qualquer campo controlado por um atacante, como , poderia fechar essa tag prematuramente e executar script.
info.descriptionEste repositório fornece um laboratório local para reproduzir o bug, além de uma versão corrigida para comparação.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-33331 |
| GHSA | GHSA-7f6v-3gx7-27q8 |
| CWE | CWE-79 (Neutralização Incorreta de Entrada Durante a Geração de Página Web) |
| Produto | middleapi/orpc |
| Afetado | Todas as versões abaixo da v1.13.9 |
| Corrigido | v1.13.9 |
| Descoberta | Abhay Kumar (@abhayclasher) |
O código vulnerável em packages/openapi/src/plugins/openapi-reference.ts renderiza a especificação assim:
<script id="spec" type="application/json">${JSON.stringify(spec)}</script>
JSON.stringify() produz JSON válido, mas não escapa < ou >. O navegador ainda trata </script> como uma tag de fechamento HTML, mesmo dentro de um <script type="application/json">.
Um payload em info.description que aciona a execução:
"</script><script>alert('XSS')</script>"
O navegador vê:
<script id="spec" type="application/json">{"info":{"description":"
</script><script>alert('XSS')</script>"}}
</script>
O primeiro </script> fecha o bloco. Tudo o que vem depois é executado como JavaScript inline.
Isso se aplica quando uma aplicação constrói especificações OpenAPI a partir de entrada controlada pelo usuário, que é exatamente o caso que o oRPC habilita por meio de suas definições de roteador.
git clone https://github.com/abhayclasher/CVE-2026-33331.git
cd CVE-2026-33331/app
npm install
node server.js
git clone https://github.com/abhayclasher/CVE-2026-33331.git
cd CVE-2026-33331
docker compose up -d
Após o servidor iniciar, visite estas URLs:
| URL | Descrição |
|---|---|
http://localhost:3000/docs | Página de documentação vulnerável — o payload XSS é executado no carregamento |
http://localhost:3000/docs/safe | Mesma página com o patch da v1.13.9 aplicado |
http://localhost:3000/spec.json | JSON bruto da especificação OpenAPI |
A versão vulnerável disparará um alert() no carregamento da página, confirmando a execução do script. A versão corrigida renderiza a mesma especificação maliciosa com segurança — o payload é exibido como texto simples.
Você também pode executar o PoC autônomo:
cd poc
node exploit.js
Isso inicia um servidor mínimo na porta 3000 que demonstra o mesmo bug sem o contexto completo da aplicação.
1. O atacante controla um campo na especificação OpenAPI (ex.: description)
2. O oRPC gera o HTML da documentação com JSON.stringify(spec)
3. O </script> no payload fecha a tag <script> prematuramente
4. O navegador executa o que vem a seguir como JavaScript inline
┌─────────────────────────────────────────────────────┐
│ 1. Especificação maliciosa criada: │
│ │
│ info.description = │
│ "</script><script>alert('XSS')</script>" │
│ │
│ 2. O orpc incorpora a especificação no HTML: │
│ │
│ <script id="spec" type="application/json"> │
│ {"info":{"description":"</script> <-- tag │
│ <script>alert('XSS')</script> <-- payload │
│ "}} │
│ </script> │
│ │
│ 3. O navegador executa alert('XSS') │
└─────────────────────────────────────────────────────┘
A versão 1.13.9 introduz escapeJsonForHtml(), que substitui caracteres sensíveis a HTML por escapes Unicode antes de incorporar o JSON na tag script:
const escapeJsonForHtml = (obj) => JSON.stringify(obj)
.replace(/&/g, '\\u0026')
.replace(/'/g, '\\u0027')
.replace(/</g, '\\u003C')
.replace(/>/g, '\\u003E')
.replace(/\//g, '\\u002F');
Os escapes Unicode funcionam porque permanecem JSON válido — JSON.parse() reconstrói a string original — mas nunca parecem HTML para o parser do navegador.
O patch também mudou a leitura do JSON de um atributo dataset.config do DOM (que quebrava quando os valores continham parênteses) para atribuir o JSON escapado diretamente a uma variável inline.
Veja o commit completo: 4f0efa8
CVE-2026-33331/
├── README.md # Este arquivo
├── docker-compose.yml # Configuração do laboratório Docker
├── app/
│ ├── Dockerfile # Configuração de build do container
│ ├── package.json # Dependências Node.js
│ └── server.js # Renderizador de documentação vulnerável + corrigido
└── poc/
└── exploit.js # Prova de conceito mínima autônoma
Relatado por Abhay Kumar. Este repositório destina-se apenas ao uso educacional local. Não implante o servidor vulnerável em uma rede pública.
Apenas para fins educacionais — use em ambientes isolados