
Read-only audit tooling for CVE-2026-0300 (PAN-OS User-ID Authentication Portal exposure)
Read-only audit tooling for CVE-2026-0300 — the unauthenticated buffer overflow in the PAN-OS User-ID Authentication Portal (formerly known as "Captive Portal") on PA-Series and VM-Series firewalls.
This repo accompanies Tailwind Resource Group security advisory TRG-SA-2026-003 and provides:
audit_portal.py — Tier 1: single-firewall auditaudit_fleet.py — Tier 2: Panorama-driven fleet sweep (concurrent)panos_audit.py — shared library used by both CLIsThe scripts are read-only. They issue show-style queries against the PAN-OS XML API and never modify configuration. The API key is sent via the X-PAN-KEY HTTP header rather than as a URL query parameter, so it does not appear in PAN management-plane access logs, TLS-inspecting proxy logs, or the auditor's process arguments.
The CVE record uses the modern feature name "User-ID Authentication Portal." The PAN-OS XML schema, CLI knobs, and documentation URL paths still use the legacy captive-portal vocabulary. Operators auditing fleets with show config running | match authentication-portal will silently miss every config because that string never appears as a configuration element. This tooling searches the schema-correct element name regardless of when the config was authored.
The full Tailwind Analysis on this naming gotcha is in TRG-SA-2026-003, "Captive Portal's legacy footprint."
pip install required--insecure is provided for one-shot bring-up audits onlyexport PAN_API_KEY="<key>"
./audit_portal.py --hostname fw1.example.com
Example table output:
vsys: vsys1
configured: True
enabled: True
redirect_host: auth-portal.internal.example.com
mode: redirect
bound: ethernet1/3(internal-trust), ethernet1/4(guest)
tls_verified: True
risk: EXPOSED_LIKELY
Other output formats: --output json or --output csv.
Other flags: --vsys <name> for single-vsys audit, --timeout/--retries for slow upstreams, --debug for verbose error details on stderr.
export PAN_API_KEY="<panorama-key>"
./audit_fleet.py --hostname panorama.example.com --workers 16 --output csv > fleet-audit.csv
Each row in the CSV is one device-vsys with portal status, redirect host, mode, interface bindings, and a risk classification. The script uses Panorama's target=<serial> API parameter so it never connects to managed firewalls directly — one connection to Panorama sweeps the whole fleet.
Notable flags:
--workers N — concurrent audit threads (default 8, max 32). A 1000-device fleet completes in minutes rather than the hour-plus a serial sweep would take.--timeout, --retries — per-API-call settings. Transient URLError and HTTP 5xx are retried with bounded backoff.--exclude-disconnected — by default, disconnected devices are included with risk=DISCONNECTED so they remain visible during the patch window. Use this flag to drop them.--min-expected-devices N — warn (stderr) if Panorama returns fewer devices than N. Useful to detect response truncation against a known fleet size.--serial-filter '0007*' — glob-filter by serial to audit a subset.--debug — print verbose per-device error details to stderr (includes upstream reason strings; off by default to avoid leaking hostnames into customer-facing CSVs).The risk column in output is one of:
| Label | Meaning |
|---|---|
INFO_NOT_CONFIGURED | No <captive-portal> block on the device. Out of scope for CVE-2026-0300. |
INFO_DISABLED | Portal is configured but enabled=no. Vulnerable code path is not active. |
REVIEW_POLICY_NO_BINDINGS | Portal enabled, but no interface bindings found in config. Verify via WebUI. |
EXPOSED_LIKELY | Portal enabled and bound to a zone whose name suggests untrusted exposure (untrust, dmz, guest, external, internet, wan, public). Investigate first. |
REVIEW_POLICY | Portal enabled and bound to internal-named zones. Walk security policy to confirm reachability. |
EXPOSED_LIKELY is a hint, not a verdict — zone names are operator conventions, not enforcement. The script cannot replace a security-policy walk; it sorts a fleet output to where attention should land first.
Do:
<captive-portal> blocks per vsys (the schema element name is always captive-portal, regardless of UI naming or PAN-OS version).EXPOSED_LIKELY rows surface first in fleet output.X-PAN-KEY header so it stays out of URL access logs and process arguments.Do not:
The PAN-OS API key inherits the role and permissions of the user who generates it.
Important caveat: there is no read-only XML API grant in PAN-OS.
Admin Role permissions on the XML API tab are binary Enable/Disable per category — Configuration, Operational Requests, Commit, Export, Import, Report, Log, User-ID Agent. Enabling Configuration for XML API permits both
showandset/edit/delete/move/rename/cloneactions. Enabling Operational Requests permits arbitrary<request>operations including<request><restart>. The Web UI and REST API tabs offer Read-Only as an option; the XML API tab does not.An "audit user" created by enabling these XML API categories holds full XML API write authority on the device. Treat the resulting key as a privileged credential: scope its lifetime to the audit window, store it in a secrets manager, and disable the user when the work is complete.
Canonical PAN reference: Get Your API Key (PAN-OS XML API).
On the firewall (or Panorama, if you're using audit_fleet.py):
audit-config-and-op.audit-cve-2026-0300.audit-config-and-op.