Overview

GLPI 是使用 PHP 開發的資訊資產管理系統,具備豐富的整合性功能,可連接 AD、各類 IT 應用與設備。由於其大量介接與自動化能力,長期以來具備多個潛在攻擊面,這次分析的主要是 CVE-2025-24799/CVE-2025-24801 兩個漏洞

  • CVE-2025-24799:GLPI Inventory API 中有一個 pre-auth SQL injection 漏洞,可經由 XML 傳入惡意的 device ID,導致 SQL injection,可以進一步控制資料庫。
  • CVE-2025-24801:透過預設啟用功能與系統機制,可將 SQLi 的漏洞利用擴展從 LFI 到 RCE,達成完整攻擊鏈。

此攻擊鏈由於無須經過身分驗證因此可以由未驗證攻擊者觸發,導致 RCE。

Product Version

  • Product:GLPI
  • Version:10.0.17
  • Language/Platform:PHP 8.x, Apache/Nginx, MySQL
  • Mode:Inventory Agent API、Calendar、Marketplace、TCPDF

Root Cause Analysis

Pre-auth SQL Injection (CVE-2025-24799)

漏洞在 GLPI 的 Inventory 原生功能(通常是啟用狀態),不用身分驗證即可觸發,漏洞點在 /src/Agent.php 裡面的 handleAgent() 呼叫到 dbEscapeRecursive() 的地方,首先看一下 handleAgent()

/src/Agent.php:handleAgent()

 1<?php
 2    public function handleAgent($metadata)
 3    {
 4        /** @var array $CFG_GLPI */
 5        global $CFG_GLPI;
 6
 7        $deviceid = $metadata['deviceid'];
 8
 9        $aid = false;
10        if ($this->getFromDBByCrit(Sanitizer::dbEscapeRecursive(['deviceid' => $deviceid]))) {
11            $aid = $this->fields['id'];
12        }

可以發現 handleAgent() 會接收 user input 並且儲存到 $deviceid 等變數,接下來經過 Sanitizer function dbEscapeRecursive() 再傳給 getFromDBByCrit(),接下來看一下 getFromDBByCrit()

getFromDBByCrit()

 1<?php
 2    public static function dbEscapeRecursive(array $values): array
 3    {
 4        return array_map(
 5            function ($value) {
 6                if (is_array($value)) {
 7                    return self::dbEscapeRecursive($value);
 8                }
 9                if (is_string($value)) {
10                    return self::dbEscape($value);
11                }
12                return $value;
13            },
14            $values
15        );
16    }

會發現這一個 function 會接收 array 或是 string 做輸出,並且使用 dbEscapeRecursive 或是 dbEscape 做 Escape,所以可以看一下如果傳入如果傳入不是 array、string 就可以繞過這兩個 escape 的 function,那接著看一下可能可以傳入什麼結構,所以看一下 handleRequest()

handleRequest()

1<?php
2        switch ($this->mode) {
3            case self::XML_MODE:
4                return $this->handleXMLRequest($data);
5            case self::JSON_MODE:
6                return $this->handleJSONRequest($data);
7        }

看一下可以傳入 XML 或是 json 做 request,那雖然 json 可以做 json_decode,但是只能建立 stringarrayintegerstdClass 物件,不過 XML 可以依據 user input 建立 SimpleXMLElement物件

handleXMLRequest()

1<?php
2    public function handleXMLRequest($data): bool
3    {
4        libxml_use_internal_errors(true);
5
6        if (mb_detect_encoding($data, 'UTF-8', true) === false) {
7            $data = iconv('ISO-8859-1', 'UTF-8', $data);
8        }
9        $xml = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);

由此可見可以繞過 dbEscapeRecursive 等 function 並且可以正常轉換為字串

image

因此可以構造一個 XML request 去做 time base 的 SQL Injection

Leveraging the database read to an authentication bypass

經過前面的 time base SQL injection 代表可以獲得資料庫的讀取權限,所以可以嘗試獲取密碼,但密碼是使用 bcrypt儲存,所以如果要恢復明文密碼會有難度

api_token

既然不太能走恢復明文密碼,那如果資料庫裡面有 api_token 那就可以直接讀 api_token 並且直接透過 api 驗證存取,但是 server 還會需要有一個 cookie 才可以正常回應

personal_token

這個 personal_token 適用於日曆功能,可以使用這個 token 共用日曆,而這個 token 使用 Session::authWithToken 驗證,並且在列印日曆後丟棄

因此可以透過在 script 結束前觸發錯誤,並且使用該 token 恢復 session,因此可以繞過身分驗證

Authenticated remote code execution

method1:marketplace

拿到 admin 權限之後就可以進去 GLPI 的 marketplace 可以嘗試安裝易受攻擊的 plugin,或是直接從管理介面設定 proxy server

method 2:Local File Inclusion

pdf 匯出功能有 LFI 問題,admin 可以透過此功能使用 TCPDF 將各種表格匯出成 pdf,並且可以自訂字體,而不管是 GLPI 或是 TCPDF 都沒有針對字體檔案檢查有無目錄遍歷,而 pdf 字體其實只是儲存在 TCPDF fonts 資料夾的 php 文件,所以如果字體名稱可控就可以去指令任意 php 文件

 1<?php
 2        if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
 3            // build a standard filenames for specified font
 4            $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
 5            $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
 6            if (TCPDF_STATIC::empty_string($fontfile)) {
 7                $missing_style = true;
 8                // try to remove the style part
 9                $tmp_fontfile = str_replace(' ', '', $family).'.php';
10                $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
11            }
12        }
13        // include font file
14        if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
15            $type=null;
16            $name=null;
17            $desc=null;
18            $up=-null;
19            $ut=null;
20            $cw=null;
21            $cbbox=null;
22            $dw=null;
23            $enc=null;
24            $cidinfo=null;
25            $file=null;
26            $ctg=null;
27            $diff=null;
28            $originalsize=null;
29            $size1=null;
30            $size2=null;
31            include($fontfile);

不過要利用這個漏洞要先在管理介面更改設定為可以上傳 php 文件(預設不可以上傳 php 文件),可以再在 /front/documenttype.php更改設定,並且透過 /front/config.form.php取得 GLPI_TMP_DIR路徑,接下來即可透過 /ajax/fileupload.php 上傳檔案

reference

https://blog.lexfo.fr/glpi-sql-to-rce.html https://research.kudelskisecurity.com/2025/03/14/pre-authentication-sql-injection-to-rce-in-glpi-cve-2025-24799-cve-2025-24801/