# Panorama UI XSS leads to Remote Code Execution via Kick/Disconnect Message

Panorama UI parsed kick/disconnect messages as raw HTML, letting a malicious server execute JS in victims' CS:GO client and launch local files for RCE.

- Payout: hidden
- Severity: critical
- Program: Valve
- Found by: shayhelman
- Disclosed: 2019-10-08
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/631956
- Library page: https://bugbountylibrary.com/r/h1%3A631956
- Tags: area:os domain:desktop impact:ato impact:rce kind:bounty platform:hackerone sev:critical technique:chained technique:source-review vuln:rce vuln:xss

**Target:** CS:GO Panorama UI popup_generic.xml disconnect/kick message
**Root cause:** Panorama layout popup_generic.xml renders the disconnect/kick message with html="true", so attacker-controlled kick text is parsed as raw HTML/JS instead of being escaped.
**Steps:**
1. Extract `csgo\panorama\code.pbin` and grep layout files for `html="true"`.
2. Identify `panorama\layout\popups\popup_generic.xml` used by disconnect popups.
3. Confirm locally: `disconnect "<img src='https://i.imgur.com/IbJKM0M.jpg'>"` renders the image.
4. Setup dedicated server with SourceMod/Metamod and a plugin calling `KickClient(client, full)` with no length limit.
5. Send `sm_testkick <a onmouseover="javascript:SteamOverlayAPI.OpenExternalBrowserURL('file://C:/Windows/System32/calc.exe')">The remote host stopped receiving communications and closed the connection</a>`.
6. Victim joins server, gets kicked, hovers the centered message text to trigger JS.
**How found:** Recalled a prior lobby-chat XSS caused by `html="true"` in chat.xml; grepped all Panorama layout files for `html="true"` and found popup_generic.xml used by disconnect messages, then tested image injection via `disconnect` and remote delivery via SourceMod KickClient.
**Exploitation:** Attacker runs a dummy CS:GO server with an autokick message containing the HTML/JS payload; victim joins and is kicked, the popup renders the payload, and a mouseover fires JavaScript in the Panorama context. Using SteamOverlayAPI.OpenExternalBrowserURL with a file:// URL launches a local executable (calc.exe shown) for full system access. Payload can be persisted via `$.Schedule(1, function)` and C2 established via `eval()` + `$.AsyncWebRequest()`.
**Escalation:** Chains XSS in game UI to OS command/file execution via Steam overlay API; can persist JS and manage multiple victims as a botnet; also usable to invoke any CSGO Panorama API function.
**Insight:** When a UI framework has an `html="true"`-style raw-render flag, grep every layout/template for that flag and cross-reference which ones render attacker-influenced strings (kick reasons, names, chat) rather than only obvious chat surfaces.

