This post details the journey of our Research Team in uncovering a SQL Injection vulnerability in GLPI, an open-source ITSM tool predominantly used in Brazil and France.
这篇文章详细介绍了我们的研究团队发现 GLPI 中的 SQL 注入漏洞的旅程,GLPI 是一种主要在巴西和法国使用的开源 ITSM 工具。
We observed many companies using GLPI, and we decided to focus on finding vulnerabilities that could allow access to users’ credentials and potentially some Remote Code Execution (RCE)
我们观察到许多公司都在使用 GLPI,因此我们决定专注于寻找可能允许访问用户凭据和潜在远程代码执行 (RCE) 的漏洞
As an 0pen-source software, we downloaded its entire source code for an in-depth code review analysis and some manual dynamic testing. This approach helped us understand the application structure leading to insights on potential vulnerabilities for credential disclosure and RCE.
作为一款 0pen-source 软件,我们下载了它的全部源代码,进行了深入的代码审查分析和一些手动动态测试。这种方法帮助我们了解了应用程序结构,从而深入了解了凭据泄露和 RCE 的潜在漏洞。
Currently, over 5,000 GLPI instances are exposed to the internet, presenting a significant research opportunity.
目前,超过 5,000 个 GLPI 实例暴露在互联网上,这提供了一个重要的研究机会。
Initial Approach 初始方法
The first step was to download the full source code from GitHub.
第一步是从 GitHub 下载完整的源代码。
GLPI is written in PHP, Twig (Template Engine), and JavaScript for client-side actions.
GLPI 是用 PHP、Twig(模板引擎)和 JavaScript 编写的,用于客户端操作。
Code Review Insights 代码审查见解
During our review, we used regex/grep to identify common vulnerabilities such as SQL Injection (SQLi), Cross-Site Scripting (XSS), and Command Injection. We looked for typical coding errors like direct usage of $_GET, $_POST, $_REQUEST in critical functions like “system”, “mysqli_query”, “eval”, and “echo”.
在审查过程中,我们使用 regex/grep 来识别常见漏洞,例如 SQL 注入 (SQLi)、跨站点脚本 (XSS) 和命令注入。我们寻找典型的编码错误,例如在“system”、“mysqli_query”、“eval”和“echo”等关键函数中直接使用 $_GET、$_POST、$_REQUEST。
This method is sometimes effective, but less so for applications with advanced security measures like SAST/DAST solutions integrated into their CI/CD pipelines.
此方法有时有效,但对于具有高级安全措施(如 SAST/DAST 解决方案)集成到其 CI/CD 管道中的应用程序来说,效果不太好。
For such mature applications, we had to delve deeper into the source code, tracing data flow and user-controlled input to uncover points where malicious code could be injected.
对于如此成熟的应用程序,我们必须更深入地研究源代码,跟踪数据流和用户控制的输入,以发现可能注入恶意代码的点。
Things we find by searching for $_GET, $_POST, and others, as I mentioned earlier, can be seen in the screenshot above, where we identify a case of reflected XSS. However, this still doesn’t help us achieve our main goal, which is to gain access to user credentials or achieve Remote Code Execution (RCE) in the application
正如我之前提到的,我们通过搜索 $_GET、$_POST 等找到的东西可以在上面的屏幕截图中看到,我们在其中识别了一个反射 XSS 的情况。但是,这仍然无法帮助我们实现主要目标,即访问用户凭据或在应用程序中实现远程代码执行 (RCE)
Another aspect we always like to analyze in a PHP application is whenever there’s a string concatenation with a variable. This often presents a good opportunity for vulnerabilities such as SQL injection, XSS (as seen in the previous screenshot), and other types of vulnerabilities
我们总是喜欢在 PHP 应用程序中分析的另一个方面是,每当有字符串与变量连接时。这通常为漏洞提供了很好的机会,例如 SQL 注入、XSS(如前面的屏幕截图所示)和其他类型的漏洞
By searching for: ‘.$ or “.$ in Visual Studio Code, will return every instance where a concatenation occurs in the code, as shown in the screenshot below.
通过在 Visual Studio Code 中搜索:“.$”或“.$”,将返回代码中发生串联的每个实例,如下面的屏幕截图所示。
Do you agree that it’s terrible to analyze all occurrences, right? One thing I also like to do is to ‘collapse’ [the view] to display only the file names, and I always go to the files that draw more attention.
你是否同意分析所有事件是可怕的,对吧?我还喜欢做的一件事是“折叠”[视图]以仅显示文件名,并且我总是转到引起更多关注的文件。
Now it becomes easier to decide which files to look at first. A file that catches attention is:
现在,决定首先查看哪些文件变得更加容易。引起注意的文件是:
Files named ‘Database’ or ‘DB’, I analyzed all these files, looking for dangerous concatenations that could lead to compromising the application.
名为“Database”或“DB”的文件,我分析了所有这些文件,寻找可能导致应用程序受损的危险串联。
The first result of the research seemingly could lead to an XSS (Cross-Site Scripting) vulnerability. However, when we approach it this way, directly searching for vulnerabilities, we can’t ascertain if it is indeed exploitable, because we still don’t know whether the input in question is controlled by user input or not.
该研究的第一个结果似乎可能导致XSS(跨站点脚本)漏洞。然而,当我们以这种方式处理它时,直接搜索漏洞,我们无法确定它是否确实可以被利用,因为我们仍然不知道有问题的输入是否由用户输入控制。
In this case, the variable $rand is defined a little further up in the same file as follows:
在本例中,变量 $rand 在同一文件中进一步定义,如下所示:
It was created in a way that we can not have control over this aspect.
它是以一种我们无法控制这方面的方式创建的。
After spending several hours analyzing files and persisting, I arrive at this function.
在花了几个小时分析文件并坚持下去之后,我得到了这个函数。
When I came across this function, I immediately thought: We have a SQL Injection.
当我遇到这个函数时,我立即想到:我们有一个 SQL 注入。
Let’s analyze this. We have a variable $end
that is being sent as a parameter to this function, and this variable does not undergo any kind of concatenation. It is sent directly to the QueryExpression
class. In this case, we don’t even need to examine what QueryExpression
does, because it won’t see our injection happening. The injection occurs earlier, during the process of concatenating the string with the variable $end
.
让我们来分析一下。我们有一个变量作为参数发送到这个函数,这个变量 $end
不经历任何形式的串联。它直接发送到 QueryExpression
班级。在这种情况下,我们甚至不需要检查什么 QueryExpression
,因为它不会看到我们的注射发生。注入发生在将字符串与变量 $end
连接的过程中。
Connecting the sink to the source
将接收器连接到源
As I mentioned earlier, it’s common to identify vulnerabilities that are not “reachable” through user input, and therefore, they are not considered “vulnerabilities” because they are not exploitable. To identify a potential injection point, we need to locate all the places where a call is made to the “getDateCriteria” function passing the $end
parameter.
正如我之前提到的,通过用户输入识别无法“访问”的漏洞是很常见的,因此,它们不被视为“漏洞”,因为它们不可利用。为了识别潜在的注入点,我们需要找到调用传递 $end
参数的“getDateCriteria”函数的所有位置。
Here’s the issue, though: there are two functions with the same name.
不过,问题来了:有两个同名的函数。
The advantage is that the getDateCriteria
function in the “db.function.php” file calls the getDateCriteria
function from the DbUtils
class without performing any validation either.
优点是“db.function.php”文件中的 getDateCriteria
函数从 DbUtils
类中调用函数, getDateCriteria
而无需执行任何验证。
In other words, any interaction with this function name will lead to the same vulnerability, so it’s not as much of a problem as it might seem…
换句话说,与此函数名称的任何交互都会导致相同的漏洞,因此它并不像看起来那么大……
We have more than 40 occurrences of calls to the function; our mission will be to analyze each of these calls and check if, in any case, there is a $end
variable that is user-controlled.
我们对函数的调用次数超过 40 次;我们的任务是分析这些调用中的每一个,并检查在任何情况下是否存在用户控制的 $end
变量。
Analyzing the calls in the “Stat.php” file, we can see that there are several instances where a $end
variable is passed directly to the vulnerable function:
分析“Stat.php”文件中的调用,我们可以看到有几个实例将 $end
变量直接传递给易受攻击的函数:
Now, we need to identify where this call originated from and how we can reach the various “cases” within the “switch-case” structure. Several variables could render the vulnerability unexploitable.
At the top of this same file, we can identify that this “switch-case” is within a function called “constructEntryValues”. This static function receives the variable $end
as its fourth parameter, and we can see that the variable does not undergo any kind of modification or validation against injection within this function either. So, up to this point, our potential injection point remains viable.
在同一文件的顶部,我们可以确定这个“switch-case”位于一个名为“constructEntryValues”的函数中。这个静态函数接收变量 $end
作为其第四个参数,我们可以看到该变量也没有在此函数中进行任何形式的修改或验证。因此,到目前为止,我们潜在的注入点仍然可行。
Our next mission is to identify where the constructEntryValues
the function is invoked within the code.
我们的下一个任务是确定函数在代码中的调用位置 constructEntryValues
。
We have only 18 occurrences of calls to the static function, and we can see that several of these calls are happening within the same “Stat” file, using the static call with self::constructEntryValues
我们只有 18 次对静态函数的调用,我们可以看到其中几个调用发生在同一个“Stat”文件中,使用静态调用 self::constructEntryValues
Analyzing the calls, we can see that the “fourth” parameter, which is renamed in the code to $end
, was originally called $date2
before entering constructEntryValues
. Therefore, from this level upwards in the application, we can consider the variable $date2
as a potential point of exploitation for this SQL Injection.
分析调用,我们可以看到,在代码中重命名为 $end
的“fourth”参数最初是在输入 constructEntryValues
之前调用 $date2
的。因此,从应用程序中的这个级别开始,我们可以将该变量 $date2
视为此 SQL 注入的潜在利用点。
Most of the occurrences of the call to the constructEntryValues
function are within the showTable
function, which receives the variable $date2
as its fourth parameter. This variable, in turn, also does not undergo any kind of modification before being sent to the vulnerable function.
Following the trail of the poorly written code, we have managed to identify all the calls to the “showTable” function.
As we trace our way to the “top” of the data flow, we notice a decreasing number of occurrences of calls to the functions corresponding to the vulnerability in this particular source code. Now, we’re down to only 5 results, and of those, only 4 are actual calls to the function.
当我们追踪到数据流的“顶部”时,我们注意到对与此特定源代码中的漏洞相对应的函数的调用次数在减少。现在,我们只剩下 5 个结果,其中只有 4 个是对函数的实际调用。
Examining the first occurrence, we need to pay close attention to what is happening. We’ll see that the fourth parameter in the call to the ShowTable
function is $param['date2']
. In this case, it is now an array, and we need to control this item of the array. It is defined a bit higher up in the code, so let’s look closely at what’s happening there.
检查第一次发生的情况,我们需要密切关注正在发生的事情。我们将看到对 ShowTable
函数调用中的第四个参数是 $param['date2']
。在这种情况下,它现在是一个数组,我们需要控制数组的这个项目。它在代码中的定义更高一些,所以让我们仔细看看那里发生了什么。
Okay, let’s break it down:
好的,让我们分解一下:
- The code sets the variable
$itemtype
using user-controlled input.
该代码使用用户控制的输入设置变量$itemtype
。 - There’s a check to see if the
display_type
parameter is being sent by the user.
有一个检查,看看display_type
参数是否由用户发送。 - After this validation, a “switch-case” statement checks if the option in
$itemtype
(which is user-controlled) is “KnowbaseItem” or “Stat”. To reach the vulnerable function, we need the value of$itemtype
to be “Stat”.
在此验证之后,“switch-case”语句将检查(由用户控制)中的$itemtype
选项是“KnowbaseItem”还是“Stat”。为了达到易受攻击的函数,我们需要$itemtype
的值为 “Stat”。 - In the final step, there is a validation to check if the
item_type_param
parameter is being sent by the user. If it is, it is passed to the functiondecodeArrayFromInput
. At first glance, you might think, “Here, they must be validating the user input, and we probably won’t be able to execute our SQL injection.”
在最后一步中,将进行验证以检查用户是否正在发送参数item_type_param
。如果是,则将其传递给函数decodeArrayFromInput
。乍一看,你可能会想,“在这里,他们必须验证用户输入,我们可能无法执行我们的 SQL 注入。
Let’s take a closer look at what’s happening in this decodeArrayFromInput
function.
让我们仔细看看这个 decodeArrayFromInput
函数中发生了什么。
That’s an intriguing discovery. The absence of validation in the decodeArrayFromInput
function, combined with its behavior of decoding the value sent in “base64” and then performing a json_decode
, presents an interesting opportunity.
这是一个有趣的发现。 decodeArrayFromInput
函数中没有验证,再加上它对“base64”中发送的值进行解码,然后执行 json_decode
的行为提供了一个有趣的机会。
The GET parameter item_type_param
being a JSON string encoded in base64 is actually an advantageous situation. This is because any SQL Injection payload that we craft can be encoded in base64, which might bypass potential Web Application Firewall (WAF) protections.
GET 参数 item_type_param
是以 base64 编码的 JSON 字符串,实际上是一种有利的情况。这是因为我们构建的任何 SQL 注入有效负载都可以以 base64 编码,这可能会绕过潜在的 Web 应用程序防火墙 (WAF) 保护。
Since the payload will be encrypted in base64, it may not be easily detected by security systems looking for typical SQL Injection patterns. This opens up a potentially exploitable vulnerability in the system.
由于有效负载将以 base64 加密,因此寻找典型 SQL 注入模式的安全系统可能不容易检测到它。这会在系统中打开一个可能被利用的漏洞。
Putting All the pieces together
把所有的碎片放在一起
Now is the time to integrate everything we’ve gathered so far and correlate it with a dynamic test of the application, meaning testing it while it’s running. Here’s how we can proceed:
现在是时候集成我们目前收集到的所有内容,并将其与应用程序的动态测试相关联,这意味着在运行时对其进行测试。以下是我们如何进行:
Now that we are examining the application in its running state, and with the knowledge that the file containing the vulnerability is “report.dynamic.php”, it becomes easier to understand the functionality by observing the page.
现在,我们正在检查处于运行状态的应用程序,并且知道包含漏洞的文件是“report.dynamic.php”,通过观察页面可以更轻松地理解该功能。
Seeing the call being executed as anticipated in your analysis is a significant step. The parameter “item_type” being sent as “Stat” and the “item_type_param” being sent in “base64” aligns with your earlier findings.
在分析中查看按预期执行的调用是重要的一步。作为“Stat”发送的参数“item_type”和以“base64”发送的“item_type_param”与您之前的发现一致。
When decoding the base64, we can identify the JSON object, also as predicted in the static analysis. We know that the injection point is “date2,” so we need to change the value of “date2” to extract data from this application’s database.
在解码 base64 时,我们可以识别 JSON 对象,这也如静态分析中预测的那样。我们知道注入点是“date2”,因此我们需要更改“date2”的值以从此应用程序的数据库中提取数据。
In this case, we can see that it is not possible to use any tool to automate the process, as the result of the query will be rendered and displayed in the PDF. Before simply injecting the famous ‘or 1=1 –‘, it’s important to understand one key point: the parameter value must not generate an exception.
在这种情况下,我们可以看到不可能使用任何工具来自动化该过程,因为查询的结果将呈现并显示在 PDF 中。在简单地注入著名的“or 1=1 –”之前,重要的是要了解一个关键点:参数值不得生成异常。
For this, it’s necessary to fully understand what is happening in the application’s code. Remember, the injection point is:
为此,有必要充分了解应用程序代码中发生的情况。请记住,注射点是:
The result would be something like: “ADDDATE(‘2023-12-15’, INTERVAL 1 DAY)”.
结果类似于:“ADDDATE(’2023-12-15’, INTERVAL 1 DAY)”。
In this case, we need to inject a single quote to close the value of ADDDATE, complete the query, close the parentheses of ADDDATE, and then perform our injection. We need to achieve something like
在这种情况下,我们需要注入一个引号来关闭 ADDDATE 的值,完成查询,关闭 ADDDATE 的括号,然后执行注入。我们需要实现类似
2023-02-18', INTERVAL 1 DAY))))) UNION SELECT 1,2,(select concat(name,'-',password) from glpi_users limit 1),4;-- -
The query in question will extract the user’s name and password from the database and display this data in the PDF that will be rendered. Our JSON ends up looking something like the following:
有问题的查询将从数据库中提取用户名和密码,并在将要呈现的 PDF 中显示此数据。我们的 JSON 最终如下所示:
{"type":"user","date1":"2022-02-19","date2":"2023-02-19', INTERVAL 1 DAY))))) UNION SELECT 1,2,(select concat(name,'-',password) from glpi_users limit 1),4;-- -","value2":0,"start":0}
Remembering that we need to encode the JSON in base64 before sending it in the URL, then, after encoding the JSON and passing the parameter, we can see the result inside the PDF with the username GLPI and the password encrypted with bcrypt.
请记住,我们需要先以 base64 对 JSON 进行编码,然后再将其发送到 URL 中,然后,在对 JSON 进行编码并传递参数后,我们可以在 PDF 中看到结果,用户名为 GLPI,密码为 bcrypt 加密。
From this moment, it is possible to execute any SQL command on the GLPI instance, read any data in the database, and, depending on the setup, read-protected files from the server. In some scenarios, it’s even feasible to write a webshell and gain access to the server (RCE).
从这一刻起,可以在 GLPI 实例上执行任何 SQL 命令,读取数据库中的任何数据,并根据设置从服务器读取受保护的文件。在某些情况下,编写 webshell 并获取对服务器 (RCE) 的访问权限甚至是可行的。
As a proof of concept, it was possible to demonstrate the same impact on the SaaS service of TecLib (the company responsible for the software).
作为概念验证,可以证明对 TecLib(负责软件的公司)的 SaaS 服务有同样的影响。
An important point to mention is that: This attack only works for authenticated users; however, the user does not need to have administrative privileges to execute this attack.
需要提及的重要一点是:此攻击仅适用于经过身份验证的用户;但是,用户不需要具有管理权限即可执行此攻击。
A basic user with a “tech” level, in this case, could carry out this attack and gain access to all the data within the GLPI server.
在这种情况下,具有“技术”级别的基本用户可以执行此攻击并访问 GLPI 服务器中的所有数据。
Timeline 时间线
February 19, 2023 – Reported via Huntr.dev
2023 年 2 月 19 日 – 通过 Huntr.dev 报道
February 22, 2023 – TecLib developers ask for more information
2023 年 2 月 22 日 – TecLib 开发人员要求提供更多信息
February 22, 2023 – Sent some working PoC on TecLib SaaS with screenshots and step-by-step instructions
2023 年 2 月 22 日 – 在 TecLib SaaS 上发送了一些工作 PoC,其中包含屏幕截图和分步说明
February 24, 2023 – TecLib developers confirm the issue
2023 年 2 月 24 日 – TecLib 开发人员确认了该问题
March 29, 2023 – CVE assigned – CVE-2023-28838
2023 年 3 月 29 日 – 已分配 CVE – CVE-2023-28838
April 5, 2023 – TecLib developers marked this as fixed in 10.0.7 with commit 3b1f59
2023 年 4 月 5 日 – TecLib 开发人员在 10.0.7 中通过提交 3b1f59 将此问题标记为已修复
April 5, 2023 – TecLib developers release an advisory (SQL injection through dynamic reports)
2023 年 4 月 5 日 – TecLib 开发人员发布公告(通过动态报告注入 SQL)
原文始发于Carlos Vieira:GLPI – SQL injection through dynamic reports