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,但是只能建立 string、array、integer和 stdClass 物件,不過 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 並且可以正常轉換為字串

因此可以構造一個 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/