shells.systems Open in urlscan Pro
188.114.96.3  Public Scan

URL: https://shells.systems/extracting-plaintext-credentials-from-palo-alto-global-protect/
Submission: On November 21 via manual from US — Scanned from NL

Form analysis 2 forms found in the DOM

GET https://shells.systems/

<form action="https://shells.systems/" method="get" role="search" id="searchform_topbar" class="search-top-bar-popup search-form">
  <label>
    <span class="screen-reader-text">Search for:</span>
    <input type="search" class="search-field-top-bar" id="search-field-top-bar" placeholder="Search …" value="" name="s">
  </label>
  <button type="submit" class="search-submit search-top-bar-submit" id="search-top-bar-submit">
    <span class="fa fa-search header-search-icon"></span>
    <span class="screen-reader-text"> Search </span>
  </button>
</form>

POST https://shells.systems/wp-comments-post.php

<form action="https://shells.systems/wp-comments-post.php" method="post" id="commentform" class="comment-form" novalidate="">
  <p class="comment-notes"><span id="email-notes">Your email address will not be published.</span> Required fields are marked <span class="required">*</span></p>
  <p class="comment-form-comment"><label for="comment">Comment</label> <textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required="required"></textarea></p>
  <p class="comment-form-author"><label for="author">Name <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" required="required"></p>
  <p class="comment-form-email"><label for="email">Email <span class="required">*</span></label> <input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" required="required"></p>
  <p class="comment-form-url"><label for="url">Website</label> <input id="url" name="url" type="url" value="" size="30" maxlength="200"></p>
  <p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Post Comment"> <input type="hidden" name="comment_post_ID" value="2486" id="comment_post_ID">
    <input type="hidden" name="comment_parent" id="comment_parent" value="0">
  </p>
  <p style="display: none;"><input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="a0f98485f9"></p>
  <p style="display: none !important;"><label>Δ<textarea name="ak_hp_textarea" cols="45" rows="8" maxlength="100"></textarea></label><input type="hidden" id="ak_js_1" name="ak_js" value="1732198199163">
    <script>
      document.getElementById("ak_js_1").setAttribute("value", (new Date()).getTime());
    </script>
  </p>
</form>

Text Content

Search for: Search
Skip to content

Shells.Systems

WE POP SHELLS


EXTRACTING PLAINTEXT CREDENTIALS FROM PALO ALTO GLOBAL PROTECT

Posted on 2024-11-192024-11-19 by Ian

Estimated Reading Time: 5 minutes



On a recent Red Team engagement, I was poking around having a look at different
files and trying to see if I could extract any information that would allow me
to move laterally through the network. I was hopeful, as always, that I would
land on domain_admin_passwords_2024.xlsx or something (don’t laugh – we’ve all
found that file at least once!). Unfortunately, this time, that file wasn’t
present on the endpoint that I had landed on, so I had to settle for some Palo
Alto Global Protect logs instead.

In  C:\Users\username\AppData\Local\Palo Alto Networks\GlobalProtect there was a
file called panGPA.log that contained something interesting:

Can you see what is odd yet?

It struck me as odd the way that the passcode and password were obfuscated. Why
would they be different lengths? Surely, they wouldn’t have just replaced each
character of the passcode/word with an asterisk? Because that would mean they
would need to know the plaintext version – and there really isn’t a reason apart
from pure laziness to do that.

With that in mind, it was time to fire up trusty x64dbg and see what is going on
under the hood.

Assuming (I know, I know – but trust me, it’s not a huge mental leap this time)
that panGPA.log was a log file for PanGPA.exe, we load that into our favourite
debugger and have a look through some of the references.

You will notice the use of ROT26 encryption

Hmmm – let’s have a look at those shall we? Out of the three, this one looks the
most interesting:

Well, well, well. What do we have ‘ere then?

I picked this one as you can see the instruction to mov edx,0x2a, where 0x2a is
the “*” character.

In fact – there are multiple references to mov edx,0x2a in this code section and
searching for all instances of this command lands us generally in code sections
that look like they are processing XML – which is exactly what we found in the
log. Let’s go digging …

Give or take 5 hours…

After _a lot_ of digging around we can start to put a picture together of what
is happening. PanGPA ‘speaks’ to PanGPS over port 4767. It makes XML based
requests and receives XML replies.

A typical response may look like something like this :

<?xml version="1.0" encoding="UTF-8"?>.
<response>..
	<type>hello</type>..
	<status>Connected</status>..
	<protocol>IPSec</protocol>..
	<portal-config-version>4100</portal-config-version>..
	<error-must-show/>..
	<error-must-show-level>error</error-must-show-level>..
	<error/>..
	<uptime>433</uptime>..
	<byte-received>10495609</byte-received>..
	<byte-sent>4743428</byte-sent>..
	<packet-received>14169</packet-received>..
	<packet-sent>9217</packet-sent>..
	<incorrect-packet-received>0</incorrect-packet-received>..
	<incorrect-packet-sent>0</incorrect-packet-sent>..
	<server-ip>x.x.x.x</server-ip>..
	<local-ip>y.y.y.y</local-ip>..
	<local-ipv6/>..
	<connect-mode>0</connect-mode>..
	<product-version>6.2.4-652</product-version>..
	<product-code>”{00243e9f-d787-4b07-a109-a1c885f2c032}”</product-code>..
	<portal-status>Connected</portal-status>..
	<user-name>user@domain.com</user-name>..
	<username-type>cc</username-type>..
	<state>Connected</state>..
	<check-version>no</check-version>..
	<portal>express.gpcloudservice.com</portal>..
	<discover-ready>yes</discover-ready>..
	<mdm-is-enabled>no</mdm-is-enabled>.
</response>


Let’s start setting some breakpoints and see if we can build up a picture of
what information flows back and forth between panGPA and panGPS. For now, let’s
just focus on the * used for obscuring some of these details in the logs.
Setting a breakpoint of every instance of this hits paydirt immediately:

It is raining plaintext passwords …


Yeah … the red ones are a password in plaintext. The full XML response was
located in memory and contained the username as well.

It was also possible to find the deactivation password and the uninstall
password.

I cannot see how this is compliant with any current security framework such as
NIST, FedRAMP etc. etc.

<rant>

Storing credentials in memory in plain text is such a basic security flaw – it
leaves systems completely open to basic memory scraping or dump techniques to
easily extract sensitive data, rendering any encryption efforts elsewhere
irrelevant. It is negligent, it violates best practices, it disregards the
principle of least privilege, and it creates an unnecessary attack surface that
increases the risk of credential theft and subsequent lateral movement. In an
age where memory attacks like Mimikatz are finally being retired, this sort of
shoddy coding is inexcusable and reflects a deep misunderstanding of basic
secure coding principles and threat mitigation.

The client is literally only speaking to the panGPS service and the Palo Alto
endpoints. There is no reason AFAICT that this data ever needs to be in
plaintext on the client endpoint apart from the very first time it is entered.
Encrypt it dammit.

</rant>

You’re a global security company. Do better dammit

With that rant out of the way, for our Red Team engagement, this was gold. The
credentials to access the VPN were different than the ones used to log on to the
machine – so now we had a second set of credentials for relay into the
environment. Happy days indeed for us, less so for the client.

I have put together a proof of concept to demonstrate extracting these
credentials from memory (focusing on username and password initially). The code
can be found at https://github.com/t3hbb/PanGP_Extractor but basically we
terminate the existing panGPA process, attach to a newly launched suspended
panGPA process, set a breakpoint, resume, and then read the memory at RSI when
the breakpoint is triggered.

Output looks like this :

Palo Alto – for all of your plaintext credential needs

It would be better, stealthier and generally cleaner if we spoke directly to
panGPS itself and impersonated the panGPA client. This would eliminate
stop/starting the VPN to extract data – total downtime is only a few seconds but
still…

Maybe something for another day

As always, any tips on improving the code/methodology gratefully received.




CategoriesRed Team, Uncategorized


POST NAVIGATION

PreviousPrevious post: Cortex XDR Ransomware Protection, Chocolate Teapots and
Inflatable Dartboards


LEAVE A REPLY CANCEL REPLY

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

Comment

Name *

Email *

Website





Δ

Proudly powered by WordPress