Two CVEs, one chain. CVE-2024-0012 is an authentication bypass in the PAN-OS management web interface; CVE-2024-9474 is a privilege escalation that ends at root. Neither is much alone. Bolt them together and a single unauthenticated request to a Palo Alto firewall’s management interface runs commands as root. Palo Alto shipped both in November 2024; both were exploited in the wild inside days, and both landed in the CISA Known Exploited Vulnerabilities catalog shortly after.
This is n-day reconstruction, not new work. The primitives were public before we looked; the point here is the chain mechanics and the gap between what the patch closes and what an operator on the box does with each half.
The appliance stack
PAN-OS serves its management interface from a fairly ordinary web stack. Nginx sits at the front as a reverse proxy; behind it Apache serves the PHP application out of /var/appweb/htdocs/. Authentication is not decided inside every PHP file. Instead nginx tags each request with an internal header, X-pan-AuthCheck, and the PHP preprocessor trusts that header to decide whether a request is allowed through without a session.
The relevant check lives in uiEnvSetup.php. Roughly: if the request’s X-pan-AuthCheck value is not the string “off”, enforce authentication; if it is “off”, skip it. The header is meant to be set by nginx, never by the client. The file /etc/nginx/conf/proxy_default.conf sets it per route, with a variable that resolves to “off” only for the intentionally unauthenticated /unauth/ paths. Everywhere else the reverse proxy is supposed to overwrite whatever the client sent.
Supposed to. That “supposed to” is the whole bug.
CVE-2024-0012, the bypass
There was a class of routes where nginx did not set X-pan-AuthCheck at all: the handlers matching .js.map URIs. For those, nginx passed the request through without stamping the header. So a client-supplied X-pan-AuthCheck header survived all the way to PHP. uiEnvSetup.php read the client’s value, saw “off”, and treated the request as pre-authenticated.
The reproducer is small. Take a protected management endpoint, append /.js.map to the path so the request matches the unstamped handler, and send a header that nginx will not overwrite:
POST /php/utils/createRemoteAppwebSession.php/.js.map HTTP/1.1
Host: firewall.target.example
X-PAN-AUTHCHECK: off
Content-Type: application/x-www-form-urlencoded
user=admin
That is CVE-2024-0012. No credentials, no session, no primitive beyond one header and a path suffix. NVD scores it CVSS v3.1 base 9.8; Palo Alto’s own advisory uses CVSS v4.0 and lands at 9.3. Either way the result is an unauthenticated administrator on the management plane.
The fix is equally small. Patched nginx config in /etc/nginx/conf/locations.conf clears the client header explicitly and defaults X-pan-AuthCheck to “on” rather than leaving it unset. The gap the patch closes is exactly the unstamped route; nothing about the PHP-side trust model changed.
CVE-2024-9474, the escalation to root
An unauthenticated admin can already rewrite firewall config, which is bad enough. CVE-2024-9474 turns “admin on the web interface” into “root on the appliance”. The endpoint is the same one from the bypass above, createRemoteAppwebSession.php. It accepts a POST parameter, user, and writes it straight into the session as the username. No sanitising on the way in.
Later, the audit-logging path reads that username back. In AuditLog.php the stored username is interpolated into a command that shells out to /usr/local/bin/pan_elog, roughly of the form pan_elog -u audit -m <message> -o <username>, and the whole string is handed to a pexecute() helper. The username is never escaped. So a username carrying shell command substitution, backticks wrapped around a payload, executes when the audit record is written. That helper runs as root; the session it builds is even created with userRole set to superuser. Command substitution in a log field, run as uid 0.
NVD scores CVE-2024-9474 at CVSS v3.1 base 7.2. On its own that reads like a mid-tier authenticated bug, which it is; the score assumes the attacker already holds management access. Chain it behind CVE-2024-0012 and the “already authenticated” precondition evaporates.
Why the two halves fit
The chain is clean because both halves touch the same request. CVE-2024-0012 reaches createRemoteAppwebSession.php without a session; the same POST that carries X-PAN-AUTHCHECK: off carries the poisoned user parameter that CVE-2024-9474 later detonates through the audit log. One request shape, two bugs, root at the end. That is why field reporting collapsed the pair into a single exploited chain within days of disclosure rather than tracking them as separate issues. The watchTowr writeup laid out the header handling and the pan_elog path in detail.
The gap worth naming: the patch for 0012 fixes header handling, and the patch for 9474 escapes the username before it reaches pan_elog. Both are correct fixes. Neither addresses the design decision that let an HTTP header decide authentication, or the one that let a session field flow unescaped into a root shell. Vendors patch the reachable instance; the pattern that produced it usually stays. Reconstruct enough of these and the pattern is the interesting part, not the individual CVE. Same reason we keep returning to enterprise application surfaces: the primitive changes, the class of mistake does not.
Reconstruction environment
We rebuilt the reasoning against a VM-Series image running a pre-fix 11.1 build, management interface bound to an isolated segment, no upstream exposure. The management web interface is the only surface in play here; the data plane and the SSL VPN are not involved. That distinction matters for exposure work. A firewall whose management interface is reachable only from a jump host is not in the same position as one with the management interface answering on an internet-facing address, which, per the exploitation reporting, is where the real hits landed.
What survives on a box that was hit
Two artefacts survive this chain. First, management-plane HTTP requests with a path segment ending in .js.map against POST endpoints under /php/, in particular createRemoteAppwebSession.php; legitimate source-map requests are GETs for static assets, not POSTs to session utilities. Second, audit-log or system-log entries where a username field holds shell metacharacters, backticks, $(), semicolons; a real PAN-OS username never does. Both are cheap to grep for and both are hard for an attacker to strip, because both are load-bearing for the chain.
If the management interface was internet-facing on an unpatched build, assume config exfiltration and credential theft, not only the RCE; an admin session reads everything the appliance holds. Patch to a fixed 10.2, 11.0, 11.1 or 11.2 build, restrict the management interface to a dedicated network, and rotate anything the box knew. The two fixes close the two instances; the segmentation is what limits the next header trick nobody has named yet.