OPNsense: Stored XSS via OpenVPN common_name leads to root RCE

A RADIUS/LDAP-controlled OpenVPN common_name breaks out of an HTML attribute in the OPNsense status views and runs script in the root admin session, escalating all the way to root command execution.

Advisory ID: TP-2026-030
Product: OPNsense (open-source firewall and routing platform)
Vulnerability type: Stored cross-site scripting to root RCE (CWE-79)
CVE: CVE-2026-58390
CVSS 3.1: 8.0 (High) · CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H
Affected versions: <= 26.1.10
Fixed in: 26.1.11
Vendor advisory: GHSA-26cj-h9rj-g5pf
Reported: 14 June 2026

Summary

OPNsense is a widely deployed open-source firewall and routing platform. The OpenVPN connection-status views (dashboard widget and status page) render the client common_name into an HTML attribute without escaping the double quote. The MVC response layer escapes API output with htmlspecialchars(ENT_NOQUOTES), which encodes <, > and & but not ". When an OpenVPN server is configured with username-as-common-name and a RADIUS or LDAP backend, the common_name equals the attacker-chosen login username. A "-bearing username breaks out of the attribute and becomes a live event handler that runs in the root admin's browser as soon as they open the view. turingpoint verified the full chain and reported it; the vendor fixed it in 26.1.11.

Root cause

OpenVPNClients.js interpolates data-common-name="${client.common_name}" raw via .append(). API output is escaped by the MVC response layer (Mvc/Response.php) with htmlspecialchars($result, ENT_NOQUOTES), which encodes <, > and & but leaves the double quote untouched. With username-as-common-name (openvpn.inc) the common_name equals the raw client auth username, which user_pass_verify.php passes through unsanitized. The local database blocks quotes via the [a-z0-9._-] mask in UsernameField.php, but RADIUS and LDAP usernames bypass that check. The script runs in the admin's dashboard session, and the OPNsense admin GUI drives configd as root.

Proof of Concept

# 1. OpenVPN server with username-as-common-name + RADIUS/LDAP backend.
# 2. Attacker registers a directory username with a " breakout:

    x" onmouseover="window.xssfired=1" data-z="

# 3. Attacker establishes an OpenVPN tunnel; the common_name is stored in
#    the session list.
# 4. Admin opens the dashboard widget or connection-status page:

    data-common-name="x" onmouseover="window.xssfired=1" data-z=""

#    -> the attribute breaks open, the event handler fires in the root admin session.

# 5. Escalation from the XSS context:
    - full theft of /conf/config.xml (credentials, keys, PSKs)
    - root command execution via configd
    - root password reset via POST /api/auth/user/set -> SSH as uid=0(root)

Live-verified on a real OPNsense instance (headless Chrome): the real OpenVPN tunnel preserves the " in the common_name, the session search returns it unfiltered, the DOM breakout sets window.xssfired=1, and from the admin session the root chain reproduces to uid=0(root).

Impact

  • Script execution in the root admin session as soon as an admin opens the OpenVPN status view.
  • Full theft of /conf/config.xml (credentials, keys, pre-shared keys).
  • Root command execution via configd and a root password reset via POST /api/auth/user/set, hence SSH access as root.
  • Persistent exploitation, since the malicious username stays stored.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.