---
# System prepended metadata

title: CVE-2025-24799/CVE-2025-24801 分析報告
tags: [CVE Analysis]

---

# 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 並且可以正常轉換為字串

![image](https://hackmd.io/_uploads/HkjB9F8Lll.png)

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