# 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/)