# CVE-2025-24799/CVE-2025-24801 分析報告
## 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()
```php=
<?php
public function handleAgent($metadata)
{
/** @var array $CFG_GLPI */
global $CFG_GLPI;
$deviceid = $metadata['deviceid'];
$aid = false;
if ($this->getFromDBByCrit(Sanitizer::dbEscapeRecursive(['deviceid' => $deviceid]))) {
$aid = $this->fields['id'];
}
```
可以發現 handleAgent() 會接收 user input 並且儲存到 `$deviceid` 等變數,接下來經過 Sanitizer function dbEscapeRecursive() 再傳給 getFromDBByCrit(),接下來看一下 getFromDBByCrit()
getFromDBByCrit()
```php=
<?php
public static function dbEscapeRecursive(array $values): array
{
return array_map(
function ($value) {
if (is_array($value)) {
return self::dbEscapeRecursive($value);
}
if (is_string($value)) {
return self::dbEscape($value);
}
return $value;
},
$values
);
}
```
會發現這一個 function 會接收 array 或是 string 做輸出,並且使用 dbEscapeRecursive 或是 dbEscape 做 Escape,所以可以看一下如果傳入如果傳入不是 array、string 就可以繞過這兩個 escape 的 function,那接著看一下可能可以傳入什麼結構,所以看一下 handleRequest()
handleRequest()
```php=
<?php
switch ($this->mode) {
case self::XML_MODE:
return $this->handleXMLRequest($data);
case self::JSON_MODE:
return $this->handleJSONRequest($data);
}
```
看一下可以傳入 XML 或是 json 做 request,那雖然 json 可以做 json_decode,但是只能建立 `string`、`array`、`integer`和 `stdClass` 物件,不過 XML 可以依據 user input 建立 `SimpleXMLElement`物件
handleXMLRequest()
```php=
<?php
public function handleXMLRequest($data): bool
{
libxml_use_internal_errors(true);
if (mb_detect_encoding($data, 'UTF-8', true) === false) {
$data = iconv('ISO-8859-1', 'UTF-8', $data);
}
$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 文件
```php=
<?php
if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
// build a standard filenames for specified font
$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
if (TCPDF_STATIC::empty_string($fontfile)) {
$missing_style = true;
// try to remove the style part
$tmp_fontfile = str_replace(' ', '', $family).'.php';
$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
}
}
// include font file
if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
$type=null;
$name=null;
$desc=null;
$up=-null;
$ut=null;
$cw=null;
$cbbox=null;
$dw=null;
$enc=null;
$cidinfo=null;
$file=null;
$ctg=null;
$diff=null;
$originalsize=null;
$size1=null;
$size2=null;
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://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/](https://research.kudelskisecurity.com/2025/03/14/pre-authentication-sql-injection-to-rce-in-glpi-cve-2025-24799-cve-2025-24801/)