← Back to Feed CACHED · 2026-09-23 04:45:31 · CACHE_KEY CVE-2026-78902
CVE-2026-78902 · CWE-79 · Disclosed 2026-09-23

pfBlockerNG Stored XSS to RCE via Unsanitized DNS Reply Log Data

ASSESSED — NOISGATE
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

Like slipping a detonator into a security guard's daily logbook that fires the moment they flip to that page

CVE-2026-78902 is a stored cross-site scripting flaw in pfBlockerNG (versions prior to 3.2.16_1) that chains to unauthenticated remote code execution as root on the underlying pfSense firewall. When Unbound Python mode with DNS Reply Logging is active — the default logging behavior when Python mode is configured — pfBlockerNG writes raw DNS reply data, including attacker-controlled TXT record content, into /var/log/pfblockerng/dns_reply.log and /var/log/pfblockerng/unified.log without sanitization. A crafted DNS reply containing JavaScript persists in these logs and surfaces on the aggregated Stats page, surviving the 200-row DNS Reply table rotation. When an administrator views the pfBlockerNG Alerts or Stats page in the webConfigurator, the payload fires in their browser, fetches /diag_command.php to steal a CSRF token, and executes arbitrary OS commands as root. Affected: pfBlockerNG < 3.2.16_1 on all pfSense CE and pfSense Plus releases.

No vendor CVSS score exists for this CVE. Noisgate calculates the CVSS 3.1 base at 9.6 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H) — network-reachable, zero authentication, scope change from browser to operating system, full confidentiality/integrity/availability impact. The only attacker constraint is user interaction: an admin must view a pfBlockerNG dashboard page. This is a routine action for any pfBlockerNG operator. pfSense is canonically a network edge appliance — it IS the perimeter firewall — and pfBlockerNG is one of the most popular packages in the ecosystem with DNS Reply Logging enabled by default in Python mode. Root compromise of the perimeter firewall is not a single-host event; it is a network-wide catastrophe. Noisgate assesses this at CRITICAL (9.1), reflecting minor friction from the user-interaction timing dependency while respecting the network-edge role floor.

"Root on your firewall via a single poisoned DNS reply — no auth, no direct access needed."
02 · The Attack Path

6 steps from start to impact.

STEP 01

Register domain, arm DNS server

The attacker registers any domain and configures their authoritative DNS server to return TXT records containing a JavaScript payload (e.g., <script>fetch('/diag_command.php')…</script>). No access to the target environment is needed — just a domain (~$1) and a DNS server. The payload is designed for the pfSense webConfigurator DOM context.
Conditions required:
  • Attacker controls an authoritative DNS server for any domain
Where this breaks in practice:
  • None — trivial and costs under $10; no target-specific knowledge required
STEP 02

Trigger DNS resolution through target pfSense

The attacker causes any host behind the pfSense firewall to resolve their malicious domain. A phishing email with an embedded image, a link on any website, a tracking pixel, or a compromised ad network suffices. The pfSense Unbound resolver processes the query and receives the malicious DNS TXT reply containing the payload.
Conditions required:
  • Any client behind the pfSense firewall resolves the attacker's domain
  • pfBlockerNG installed with Unbound Python mode enabled
Where this breaks in practice:
  • Requires at least one DNS query from any host behind the firewall — extremely low bar given typical browsing activity
Detection/coverage: DNS query logging may capture the domain name, but TXT record content in the reply is not typically inspected by DNS-layer security tools or threat feeds
STEP 03

Log poisoning via unsanitized DNS reply

pfBlockerNG's Unbound Python mode writes the DNS reply data — including the malicious JavaScript from the TXT record — directly to /var/log/pfblockerng/dns_reply.log and /var/log/pfblockerng/unified.log without any input sanitization or HTML encoding. DNS Reply Logging is the default behavior when Python mode is configured. The payload persists in the aggregated Stats page view, surviving beyond the 200-row DNS Reply table rotation.
Conditions required:
  • DNS Reply Logging is active (default in Python mode)
Where this breaks in practice:
  • Log rotation could eventually purge the payload, but the Stats page aggregation preserves it across rotations
Detection/coverage: Manual grep for <script>, javascript:, onerror=, or encoded JS fragments in /var/log/pfblockerng/*.log; no built-in pfSense detection rule covers this
STEP 04

Stored XSS fires on admin page view

When a pfSense administrator navigates to the pfBlockerNG Alerts page or the aggregated Stats page in the webConfigurator, the poisoned log data is rendered into the HTML response without output encoding. The injected JavaScript executes in the administrator's authenticated browser session with full access to the webConfigurator DOM and session cookies. This is a stored XSS — no social engineering of the URL is needed.
Conditions required:
  • Administrator must view the pfBlockerNG Alerts or Stats page
  • Active authenticated session in webConfigurator
Where this breaks in practice:
  • Timing dependent — attacker waits for the admin to check the dashboard
  • pfBlockerNG operators routinely check these pages for DNS blocking metrics, so the wait is typically hours to days, not weeks
Detection/coverage: Content Security Policy (CSP) could block inline scripts, but pfSense webConfigurator does not enforce strict CSP by default
STEP 05

CSRF token theft → root command execution

The XSS payload performs a same-origin fetch() to /diag_command.php, extracts the CSRF token from the returned HTML, and submits an arbitrary OS command via the diagnostic command execution form. Because the pfSense webConfigurator runs as root, the command executes with full root privileges on the FreeBSD host. The published NetSPI PoC demonstrates spawning a reverse shell in a single request.
Conditions required:
  • XSS executes in admin browser session
  • /diag_command.php accessible (default endpoint in all pfSense installs)
Where this breaks in practice:
  • CSRF tokens exist but are trivially bypassed via same-origin XSS
  • Browser extensions like NoScript could theoretically block inline execution, but this is not an enterprise control
Detection/coverage: POST to /diag_command.php will appear in webConfigurator access logs, but it is a legitimate admin endpoint — detecting abuse requires behavioral correlation
STEP 06

Root shell on perimeter firewall

The attacker now holds a root reverse shell on the pfSense firewall. From this position they can: modify firewall/NAT rules to open the internal network, intercept and MITM all transiting traffic, exfiltrate VPN credentials and PKI certificates, disable logging and IDS/IPS (Snort/Suricata), poison DNS for all clients, and pivot laterally to every host behind the firewall. The network perimeter is fully compromised.
Conditions required:
  • Outbound connectivity from pfSense to attacker C2 (or DNS/ICMP tunneling)
Where this breaks in practice:
  • pfSense as the gateway device typically has unrestricted outbound access — egress filtering on the firewall box itself is rare
Detection/coverage: Upstream network monitoring might flag unexpected outbound connections from the firewall management IP, but the firewall itself is typically the outermost monitoring point — a compromised firewall can suppress its own alerts
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNo confirmed in-the-wild exploitation as of 2026-09-23. Not listed in the CISA KEV catalog. Given the full PoC publication today, exploitation in the wild is expected to follow within days to weeks.
Proof-of-conceptFull chain PoC published by NetSPI in their disclosure blog post — demonstrates DNS TXT reply → stored XSS → CSRF token steal → reverse shell as root. Reproduction requires only a domain, an authoritative DNS server, and a one-line JavaScript payload. Attack complexity is low.
EPSS scoreNot yet scored by the FIRST EPSS model — CVE published 2026-09-23. Given the full PoC, zero-auth attack vector, and network-edge target class, expect EPSS to settle in the top 5% once scored.
KEV statusNot listed as of 2026-09-23. Monitor for addition given the PoC availability and the high-value target class (network perimeter firewalls).
CVSS vector (noisgate-calculated)CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H — 9.6 base. Network attack vector (DNS reply over the wire). No privileges required. User interaction required (admin views dashboard page). Scope changed (browser XSS → OS-level root RCE). Full CIA impact on the pfSense host.
Affected versionspfBlockerNG < 3.2.16_1 on pfSense CE (all versions) and pfSense Plus (all versions). Vulnerable when Unbound Python mode is configured with DNS Reply Logging — the default logging behavior in Python mode.
Fixed versionpfBlockerNG 3.2.16_1 — available now via pfSense Package Manager (System → Package Manager → Installed Packages → pfBlockerNG → Update). Fix adds input sanitization to prevent unsafe content from being written to log files.
Scanning / exposure dataShodan indexes ~30,000 pfSense instances with web interfaces exposed to the internet. However, the attack vector is DNS replies, not direct HTTP — any pfSense instance resolving external DNS is reachable regardless of webConfigurator exposure. The total pfSense installed base (including non-internet-exposed) is estimated at several hundred thousand.
Disclosure timeline2026-09-23: NetSPI blog post and full PoC published. Netgate patch (pfBlockerNG 3.2.16_1) shipped prior to public disclosure (responsible disclosure). CVE-2026-78902 assigned after validation by NetSPI.
Researcher / organizationNetSPI — responsible disclosure to Netgate. This follows the pattern of prior pfBlockerNG vulnerabilities (e.g., CVE-2022-31814, unauthenticated RCE via Host header injection, CVSS 9.8) and pfSense webConfigurator XSS issues researched by Touhid M Shaikh and others.
04 · The Call

Final Verdict
= UNCHANGED to CRITICAL (9.1/10)

Why this verdict

  • Zero authentication, zero direct access: The attack starts with a DNS reply — the attacker needs no credentials, no access to the pfSense web interface, and no adjacency to the target network. They need only one client behind the firewall to resolve a domain they control. This is trivially achievable.
  • Full root RCE with published PoC: NetSPI published the complete attack chain. The PoC demonstrates a working reverse shell as root. No memory corruption, no heap grooming, no race condition — just unsanitized log data rendered in HTML. The barrier to weaponization is near-zero.
  • DNS Reply Logging is default-on in Python mode: When pfBlockerNG is configured with Unbound Python mode (the recommended and most common mode for DNSBL functionality), DNS Reply Logging is active by default. This means the vulnerable configuration is the *common* configuration, not an edge case.
  • Role multiplier: pfSense is canonically a network edge appliance — 100% of pfBlockerNG installations run on pfSense, which serves as the perimeter firewall. Root compromise of the firewall yields: (a) complete traffic interception and modification (fleet-scale data exposure), (b) firewall rule manipulation (opening the internal network), (c) VPN credential and certificate theft, (d) DNS poisoning for all clients, (e) IDS/IPS suppression, (f) lateral pivot to every internal host. The blast radius is network-wide, not host-scoped. This sets a CRITICAL floor per noisgate high-value-role policy.
  • User interaction is real but routine friction: The admin must view the pfBlockerNG Alerts or Stats page. This is the *primary monitoring dashboard* for pfBlockerNG operators — checked daily or multiple times per day in active deployments. This friction degrades the calculated CVSS from 9.6 to an assessed 9.1 but does not reduce below CRITICAL.

Why not higher?

The calculated CVSS 3.1 base is 9.6. The reassessed score of 9.1 reflects two real-world conditions: (1) DNS Reply Logging, while default-on in Python mode, can be explicitly disabled — some fraction of pfBlockerNG installs will not have the vulnerable log path active; (2) the stored XSS requires an administrator to navigate to a specific pfBlockerNG dashboard page, introducing a timing dependency measured in hours-to-days rather than immediate exploitation. These friction points are real but do not reduce below CRITICAL given the blast radius.

Why not lower?

pfSense is a network edge appliance by definition. Root RCE on the perimeter firewall is a fleet-scale compromise event — the attacker controls the network boundary. pfBlockerNG runs exclusively on pfSense, so 100% of the affected component's install base occupies the high-value network-edge role. The attack requires zero authentication, zero direct network access to the target, and the full PoC is publicly available. Downgrading to HIGH would require evidence that a meaningful fraction of pfBlockerNG installs do NOT sit at the network perimeter, which is structurally impossible. A generic 'requires user interaction' argument is insufficient to break the CRITICAL floor when the interaction is routine admin behavior on the affected component's primary dashboard.

05 · Compensating Control

What to do — in priority order.

  1. Update pfBlockerNG to v3.2.16_1 immediately — This is the definitive fix. The patched version sanitizes DNS reply data before writing to logs, eliminating the injection vector. Available now via System → Package Manager → Installed Packages. Per the noisgate mitigation SLA for CRITICAL, deploy within 3 days. Per the noisgate remediation SLA, complete fleet-wide patching within 90 days.
  2. Disable DNS Reply Logging NOW as interim mitigation — If you cannot update within hours, navigate to Firewall → pfBlockerNG → DNSBL → DNS Reply Logging and disable it. This eliminates the log-poisoning attack surface entirely. DNS-based blocking continues to function — you only lose per-reply telemetry. Apply within hours as a same-day compensating control.
  3. Audit pfBlockerNG logs for injected payloads — Run grep -riE '<script|javascript:|onerror=|onload=|eval\(' /var/log/pfblockerng/*.log on every pfSense box. If matches are found, treat the firewall as compromised — initiate incident response, capture forensic images, rotate all credentials accessible from that network segment, and rebuild the pfSense box from clean media.
  4. Restrict webConfigurator to a dedicated management VLAN — Bind the webConfigurator to a management-only interface per Netgate best practices. This does not prevent the XSS (the attack arrives via DNS, not HTTP), but limits which networks an admin session can originate from and reduces the probability of a compromised workstation being used for pfSense administration.
  5. Monitor outbound connections from the pfSense management IP — A successful exploit spawns a reverse shell from the firewall. Configure upstream monitoring (SIEM, flow data, or an out-of-band tap) to alert on unexpected outbound TCP connections originating from pfSense management IPs to external destinations. Since a compromised firewall can suppress its own logs, out-of-band monitoring is critical.
What doesn't work
  • WAF in front of pfSense — The attack vector is DNS replies arriving at the Unbound resolver, not HTTP requests to the web interface. A WAF cannot inspect or filter DNS TXT record content.
  • Network IDS/IPS running on pfSense (Snort/Suricata) — The malicious payload arrives in a standard DNS TXT reply. Default IDS rulesets do not inspect DNS TXT record content for JavaScript. A custom signature is possible but fragile and easily evaded with encoding. Additionally, if the attacker achieves root, they can disable the IDS.
  • CSRF protection on /diag_command.php — pfSense implements CSRF tokens, but stored XSS executes in the same origin and trivially reads the token from the DOM before form submission. CSRF protection is fully bypassed by same-origin XSS by design.
  • Browser-level controls (NoScript, Content Blockers) — Relying on per-admin browser extensions is not a viable enterprise compensating control. There is no guarantee admins use these, and they are not centrally managed or enforced in most environments.
06 · Verification

Crowdsourced verification payload.

Run this script on the pfSense appliance itself via SSH or the pfSense console shell (option 8). Requires root privileges (standard for pfSense shell access). Invoke with: sh /tmp/check_cve_2026_78902.sh

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/bin/sh
# CVE-2026-78902 — pfBlockerNG Stored XSS to RCE via DNS Reply Log Poisoning
# Target: pfSense appliance (FreeBSD)
# Requires: root shell access
# Outputs: VULNERABLE / PATCHED / UNKNOWN

FIXED_VERSION="3.2.16_1"
FOUND=0
VULN=0

echo "=== CVE-2026-78902 Check ==="
echo ""

# Try both package name variants
for PKG in pfSense-pkg-pfBlockerNG-devel pfSense-pkg-pfBlockerNG; do
  INSTALLED=$(pkg query '%v' "$PKG" 2>/dev/null)
  if [ -n "$INSTALLED" ]; then
    FOUND=1
    echo "[*] Package: $PKG"
    echo "[*] Installed version: $INSTALLED"
    echo "[*] Fixed version:     $FIXED_VERSION"
    echo ""

    # Compare versions using FreeBSD pkg version
    CMP=$(pkg version -t "$INSTALLED" "$FIXED_VERSION" 2>/dev/null)
    if [ "$CMP" = "<" ]; then
      VULN=1
    fi
    break
  fi
done

if [ "$FOUND" -eq 0 ]; then
  echo "UNKNOWN — pfBlockerNG is not installed."
  echo "This CVE does not apply if pfBlockerNG is not in use."
  exit 2
fi

# Check DNS Reply Logging status
DNS_REPLY_LOG="/var/log/pfblockerng/dns_reply.log"
if [ -f "$DNS_REPLY_LOG" ]; then
  echo "[!] DNS Reply Log exists — logging is likely ACTIVE (attack surface present)."
  # Scan for evidence of exploitation
  SUSPICIOUS=$(grep -ciE '<script|javascript:|onerror=|onload=|eval\(|document\.cookie|fetch\(' "$DNS_REPLY_LOG" 2>/dev/null)
  if [ "$SUSPICIOUS" -gt 0 ]; then
    echo "[!!!] ALERT: $SUSPICIOUS suspicious line(s) found in DNS reply log!"
    echo "[!!!] Possible exploitation attempt. Investigate IMMEDIATELY."
    echo "[!!!] Run: grep -iE '<script|javascript:|onerror=' $DNS_REPLY_LOG"
  else
    echo "[+] No obvious XSS payloads found in DNS reply log."
  fi
else
  echo "[+] DNS Reply Log not found — logging may be disabled (reduced risk)."
fi

echo ""

# Final verdict
if [ "$VULN" -eq 1 ]; then
  echo "VULNERABLE — pfBlockerNG $INSTALLED is below fixed version $FIXED_VERSION."
  echo "ACTION: Update via System > Package Manager > Installed Packages."
  echo "INTERIM: Disable DNS Reply Logging in Firewall > pfBlockerNG > DNSBL."
  exit 1
else
  echo "PATCHED — pfBlockerNG $INSTALLED is at or above fixed version $FIXED_VERSION."
  exit 0
fi
07 · Sources

Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.