The Firewall Fetches Its Own Exploit: OPNsense’s GeoIP Importer Takes a Filename From the Feed
NVD published five OPNsense records in one second on 15 October 2026 — CVE-2026-57155 at 16:18:17.300Z and CVE-2026-58391, CVE-2026-58392, CVE-2026-58394 and CVE-2026-58395 at 16:18:18Z — all sourced to security-advisories@github.com, all vulnStatus Received, and all saying the same thing about versions: prior to 26.1.11. That release was tagged by Franco Fichtner on 1 July 2026 at 08:57:38Z with the commit message “stable release”. The code fix is older still: 23 June. So this is not a new hole in an edge firewall; it is a hole that was closed one hundred and six days before any machine-readable record of it existed.
The headline record is worth the wait. CVE-2026-57155 carries CVSS 3.1 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) under CWE-22, CWE-73 and CWE-78, and CISA’s SSVC decision point recorded the same day reads exploitation: poc, automatable: no, technicalImpact: total. It describes a defensive appliance that downloads a threat-intelligence feed from an operator-named URL, unpacks it as root, and lets a column inside that feed choose the filename it writes.
- CVE-2026-57155 (9.9, CWE-22/73/78, GHSA-wjqq-rfmm-v5h3). The Firewall Alias GeoIP importer writes one file per country and names it from the unvalidated
country_codecolumn of the downloaded CSV. A country code of../../../../../etc/newsyslog.conf.d/zzz_pwnescapes/usr/local/share/GeoIP/aliasand writes attacker-chosen content as root. Reachable by a non-administrative user holding only theFirewall: Alias: Editprivilege. - CVE-2026-58395 (4.3). The generic MVC safe-delete routine interpolates the URL delete token straight into an XPath query, turning the shared endpoint into a read oracle over
config.xmlvia HTTP 500 error text. The reporter counts twenty-one endpoints reached through that one routine. - CVE-2026-58391 (5.4), CVE-2026-58392 (5.4), CVE-2026-58394 (5.2). Three stored XSS: category names and alias descriptions rendered into a
title="..."attribute escaped withENT_NOQUOTES; the NTP GPSinitcmdescaped in its base64 form and then decoded back onto the page; certificate descriptions printed into an<option>tag with no escaping at all.
The feed is the input
The GeoIP alias feature exists so an operator can write a rule meaning “all of country X”. OPNsense periodically fetches a country-IP database and expands it into alias files. The importer runs as root because it writes into a system directory, and it fetches from whatever URL is configured because the whole point is that you choose your GeoIP provider. The advisory states the consequence of that combination plainly: “The GeoIP source URL field has no host restriction, so the attacker can simply point it at a server they control.”
Two functions unpack the archive, process_zip() and process_gzip(), and the advisory notes they share the identical flaw. The write sink, quoted from the advisory’s own annotation of src/opnsense/scripts/filter/lib/alias/geoip.py:
_target_dir = '/usr/local/share/GeoIP/alias' # line 46
...
country_code = country_codes[parts[1]] # line 88
if country_code not in output_handles:
output_handles[country_code] = open(
'%s/%s-%s' % (cls._target_dir, country_code, proto), 'w') # line 90-91 - WRITE SINK
...
output_handles[country_code].write("%s\n" % parts[0]) # line 94 - file content
Both halves of the written file come from the archive. The path comes from the country_iso_code column of the locations CSV; the content comes from the network column of the blocks CSV, joined on geoname_id. The importer believes it is writing network ranges into country-named files. It is in fact writing caller-supplied bytes into a caller-supplied path, as root.
The suffix that shaped the exploit
One structural constraint survives the traversal: every filename ends in -IPv4 or -IPv6, because the importer appends the protocol. Normally a forced suffix is what keeps an arbitrary-write primitive from becoming code execution — you cannot drop a .php shell or a crontab line whose name must end in something else. The reported chain is a neat answer to that constraint rather than a way around it: FreeBSD’s newsyslog reads every file matching /etc/newsyslog.conf.d/*, so the suffix does not disqualify the fragment, and the default OPNsense image runs it hourly as root:
# Rotate log files every hour, if necessary.
0 * * * * root newsyslog
So the exploit writes two files instead of one: a newsyslog entry with an R (run-command) action, and an oversized bait log file so the size-based rotation trigger fires on the next run. On the hour, root’s cron rotates the bait and executes the command. Three authenticated API calls get there — POST /api/firewall/alias/set to store the attacker URL, POST /api/firewall/alias/reconfigure to render it into /usr/local/etc/filter_geoip.conf, and POST /api/firewall/alias/update/geoip to trigger the configd update.geoip action that runs geoip.py as root. The advisory summarises the privilege arithmetic: “This escalates an alias-only account to full root control of the firewall.” We did not reproduce any of this; the researcher states reproduction on 26.1.6_2 and 26.1.10.
That is the part worth sitting with. Firewall: Alias: Edit is the privilege you hand to the person who maintains address lists. It is the textbook safe delegation — no rules, no interfaces, no shell. The PR:L in the vector is doing almost no work, because the privilege it describes is one almost everyone gives away.
Two lines, and what they say about the original
The fix is commit c46aced9 by Ad Schellevis, 23 June 2026 at 14:53:50Z, cherry-picked to the stable branch as e7fbfaa7 the same second, titled “Firewall: Aliases - safeguard ISO country codes in alias download” with the body “PR: GHSA-wjqq-rfmm-v5h3”. It is two lines changed in one file:
- if len(parts[4]) >= 1:
+ if len(parts[4]) == 2 and parts[4].isalnum():
- else:
+ elif len(record[country_field]) == 2 and record[country_field].isalnum():
Read the left-hand side carefully. The zip path did have a check on the country code — it checked that the string was not empty. The gzip path had no check at all; the fixed line replaces a bare else. A length-at-least-one test is the shape of validation written to guard against a malformed feed, not a hostile one: it answers “did this row parse?” and never “should this value be allowed to pick a path?”. The replacement is the correct question asked in the only form the data supports — an ISO country code is exactly two alphanumeric characters, and nothing else is a country. This is the same lesson the researcher draws from the three XSS findings in the same batch, in their words: “validate and escape as close to the sink as possible, because every extra step in between is a chance for the protection to quietly fall off.”
We have covered this shape repeatedly in the last fortnight from the agent side — eighteen CVEs where a file name reached a shell, a container name interpolated into a root command, an image annotation that decided how the sandbox was built. OPNsense is the infrastructure version of the same error, and it predates all of them: a value inside the data chose where the privileged process wrote. The only thing that makes a firewall feed special is that ingesting untrusted third-party data is the feature, which is exactly why the parser deserved hostile-input treatment from the first commit.
The record is thinner than the advisory
Two gaps are worth flagging for anyone driving remediation from tooling rather than from this page.
The affected range in the CVE is narrower than the vendor’s. The GHSA lists affected versions as <= 26.1.10, <= 26.4.1 and patched versions as 26.1.11, 26.4.1p1 — two product lines. The NVD record’s affected data carries only opnsense core < 26.1.11, and every one of the five descriptions says “Prior to version 26.1.11”. A scanner matching on the CVE’s version data has no expression for the second line at all. Check your build against the advisory, not the CVE.
There is no OSV or global-advisory record. At time of writing, GET https://api.github.com/advisories/GHSA-wjqq-rfmm-v5h3 returns 404, and api.osv.dev returns 404 for both the GHSA and the CVE identifier. The advisory page itself states “No package listed” under Package, and that is the likely mechanism: an advisory with no package-ecosystem coordinate has nothing for the global database to index against, so it never propagates to the feeds that osv-scanner, Dependabot and most SCA products consume. This is the inverse of the problem we described for Renovate, where the advisories existed but the fix shipped as an unlabelled changelog line. Here the advisory is detailed, public since 1 July, scored Critical by the vendor — and machine-invisible. Appliance software consistently falls outside package-ecosystem tooling, and a published GHSA is not the same thing as a record your scanners will ever see.
What to do
- Confirm the build, not the marketing version. Community edition needs 26.1.11 or later; the business line needs 26.4.1p1 or later per the vendor advisory. Current community tags are well past this (the 26.7.x series), so most actively updated firewalls are already covered — the exposed population is the appliance nobody has touched since spring, which on an edge firewall is a common state.
- Treat
Firewall: Alias: Editas a privileged grant until you are patched. It is the only credential the chain needs. Enumerate who holds it, including service accounts and any role that inherited it from a group. - Audit the configured GeoIP source URL. It lives in
config.xmland is rendered into/usr/local/etc/filter_geoip.conf. A URL pointing anywhere other than your intended provider is the first stage of this chain and is cheap to check. Pin it, and keep it pinned after upgrading. - If you were running an affected build with the privilege delegated, look for artefacts rather than alerts. The chain produces files, not crashes: unexpected entries under
/etc/newsyslog.conf.d/(particularly names ending-IPv4or-IPv6), unexplained oversized files in/var/log, and any file outside/usr/local/share/GeoIP/aliascarrying those suffixes. An import that succeeded looks like a successful import in the logs. - Generalise past this file. Every place your security stack ingests a third-party feed — blocklists, GeoIP, threat intel, rule bundles — is a parser running with elevated privilege over data you do not control. Ask of each one which fields influence a path, a command, or a configuration, and whether the validation on them is a hostile-input check or a malformed-input check.
Verification note. All five CVE identifiers, publication timestamps, CVSS 3.1 scores and vectors, CWE assignments, affected-version data, vulnStatus values, source identifiers, and the CISA SSVC decision points were read first-hand from the NVD 2.0 API. The advisory summary, affected and patched version lists, root-cause annotation of geoip.py, the three-request chain, the newsyslog escalation, the impact statement, severity and credit were read first-hand from GHSA-wjqq-rfmm-v5h3 on the opnsense/core repository; every sentence in quotation marks is verbatim from that advisory or from the reporter’s write-up. Commits c46aced9c47d956167e294911113bc334fea5f48 and e7fbfaa7f5e070da667e3f243da11fd3d4b731fa, their author, timestamp, message and complete two-line diff, plus the 26.1.11 tag object, tagger and date, were read first-hand from the GitHub API. The researcher’s methodology, the five-finding table and the XSS analysis were read first-hand from the linked write-up. The GitHub global-advisory and OSV 404 responses were measured at time of writing. Brave Search returned only CVE-mirror aggregators and automated scoring pages; the reporter’s write-up is the only first-hand analysis. Nothing was installed, no OPNsense instance was deployed or contacted, no archive was crafted, no proof-of-concept was downloaded or executed, and no third-party request was sent. Remediation guidance is our editorial analysis. We have no evidence of exploitation in the wild and imply none.
Sources:
- NVD — CVE-2026-57155, root file write via GeoIP alias importer (9.9, CWE-22/73/78, prior to 26.1.11)
- OPNsense GHSA-wjqq-rfmm-v5h3 — Root RCE via Arbitrary File Write in GeoIP Alias Importer (published 1 July 2026)
- opnsense/core
c46aced9— “Firewall: Aliases - safeguard ISO country codes in alias download” (23 June 2026) - Jonas Ampferl (Hacking Cult) — Chasing the OPNsense RCE: the five-finding research write-up
- NVD — CVE-2026-58395, XPath injection in the shared MVC safe-delete path