
Lokales Labor zur Reproduktion von gespeichertem XSS in der OpenAPI-Dokumentgenerierung von oRPC (CVE-2026-33331), mit verwundbaren und gepatchten Versionen zum Vergleich sowie einem eigenständigen PoC.
Proof-of-Concept-Labor für Stored XSS in der OpenAPI-Dokumentationsgenerierung von orpc
oRPC erstellt API-Dokumentationsseiten aus einer OpenAPI-Spezifikation. Vor v1.13.9 wurde die Spezifikation direkt in ein <script>-Tag mit JSON.stringify() ohne HTML-Kodierung eingefügt. Jedes vom Angreifer kontrollierte Feld, wie info.description, konnte dieses Tag vorzeitig schließen und Skript ausführen.
Dieses Repository bietet Ihnen ein lokales Labor zur Reproduktion des Fehlers sowie eine gepatchte Version zum Vergleich.
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-33331 |
| GHSA | GHSA-7f6v-3gx7-27q8 |
| CWE | CWE-79 (Unzureichende Neutralisierung von Eingaben während der Webseitengenerierung) |
| Produkt | middleapi/orpc |
| Betroffen | Alle Versionen unterhalb von v1.13.9 |
| Gepatcht | v1.13.9 |
| Entdeckung | Abhay Kumar (@abhayclasher) |
Der verwundbare Code in packages/openapi/src/plugins/openapi-reference.ts rendert die Spezifikation wie folgt:
<script id="spec" type="application/json">${JSON.stringify(spec)}</script>
JSON.stringify() erzeugt gültiges JSON, escapet jedoch keine < oder >. Der Browser behandelt </script> weiterhin als HTML-Schließtag, selbst innerhalb eines <script type="application/json">.
Eine Payload in info.description, die die Ausführung auslöst:
"</script><script>alert('XSS')</script>"
Der Browser sieht:
<script id="spec" type="application/json">{"info":{"description":"
</script><script>alert('XSS')</script>"}}
</script>
Das erste </script> schließt den Block. Alles danach wird als Inline-JavaScript ausgeführt.
Dies gilt, wenn eine Anwendung OpenAPI-Spezifikationen aus benutzergesteuerten Eingaben erstellt, was genau der Fall ist, den oRPC über seine Router-Definitionen ermöglicht.
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
Nachdem der Server gestartet ist, besuchen Sie diese URLs:
| URL | Beschreibung |
|---|---|
http://localhost:3000/docs | Verwundbare Dokumentationsseite — XSS-Payload wird beim Laden ausgeführt |
http://localhost:3000/docs/safe | Dieselbe Seite mit dem v1.13.9-Patch |
http://localhost:3000/spec.json | Rohe OpenAPI-Spezifikation als JSON |
Die verwundbare Version löst beim Laden der Seite ein alert() aus, was die Skriptausführung bestätigt. Die gepatchte Version rendert dieselbe bösartige Spezifikation sicher — die Payload wird als Klartext angezeigt.
Sie können auch das eigenständige PoC ausführen:
cd poc
node exploit.js
Dies startet einen minimalen Server auf Port 3000, der denselben Fehler ohne den vollständigen Anwendungskontext demonstriert.
1. Angreifer kontrolliert ein Feld in der OpenAPI-Spezifikation (z. B. description)
2. oRPC generiert das Dokumentations-HTML mit JSON.stringify(spec)
3. Das </script> in der Payload schließt das <script>-Tag vorzeitig
4. Der Browser führt alles Folgende als Inline-JavaScript aus
┌─────────────────────────────────────────────────────┐
│ 1. Bösartige Spezifikation erstellt: │
│ │
│ info.description = │
│ "</script><script>alert('XSS')</script>" │
│ │
│ 2. orpc bettet Spezifikation in HTML ein: │
│ │
│ <script id="spec" type="application/json"> │
│ {"info":{"description":"</script> <-- Tag │
│ <script>alert('XSS')</script> <-- Payload │
│ "}} │
│ </script> │
│ │
│ 3. Browser führt alert('XSS') aus │
└─────────────────────────────────────────────────────┘
Version 1.13.9 führt escapeJsonForHtml() ein, das HTML-empfindliche Zeichen durch Unicode-Escapes ersetzt, bevor JSON in das Skript-Tag eingebettet wird:
const escapeJsonForHtml = (obj) => JSON.stringify(obj)
.replace(/&/g, '\\u0026')
.replace(/'/g, '\\u0027')
.replace(/</g, '\\u003C')
.replace(/>/g, '\\u003E')
.replace(/\//g, '\\u002F');
Unicode-Escapes funktionieren, weil sie gültiges JSON bleiben — JSON.parse() rekonstruiert die ursprüngliche Zeichenfolge — aber sie sehen für den HTML-Parser des Browsers nie wie HTML aus.
Der Patch änderte auch das Lesen von JSON aus einem DOM-dataset.config-Attribut (das brach, wenn Werte Klammern enthielten) hin zur direkten Zuweisung des escapeten JSON an eine Inline-Variable.
Vollständigen Commit ansehen: 4f0efa8
CVE-2026-33331/
├── README.md # Diese Datei
├── docker-compose.yml # Docker-Labor-Konfiguration
├── app/
│ ├── Dockerfile # Container-Build-Konfiguration
│ ├── package.json # Node.js-Abhängigkeiten
│ └── server.js # Verwundbarer + gepatchter Dokumentations-Renderer
└── poc/
└── exploit.js # Eigenständiges minimales Proof-of-Concept
Gemeldet von Abhay Kumar. Dieses Repository ist nur für lokale Bildungszwecke gedacht. Stellen Sie den verwundbaren Server nicht in einem öffentlichen Netzwerk bereit.
Nur für Bildungszwecke — nur in isolierten Umgebungen verwenden