GLPI - SQL injection through dynamic reports

GLPI -  SQL injection through dynamic reports

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.

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)

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.

Currently, over 5,000 GLPI instances are exposed to the internet, presenting a significant research opportunity.

Initial Approach

The first step was to download the full source code from GitHub.

GLPI is written in PHP, Twig (Template Engine), and JavaScript for client-side actions.

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".

This method is sometimes effective, but less so for applications with advanced security measures like SAST/DAST solutions integrated into their CI/CD pipelines.

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

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

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.

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.

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.

In this case, the variable $rand is defined a little further up in the same file as follows:

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.

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.

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.

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.

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.

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:

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.

Our next mission is to identify where the constructEntryValues the function is invoked within the code.

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

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.

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.

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.

Okay, let's break it down:

  1. The code sets the variable $itemtype using user-controlled input.
  2. There's a check to see if the display_type parameter is being sent by the user.
  3. 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".
  4. 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 function decodeArrayFromInput. 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."

Let's take a closer look at what's happening in this decodeArrayFromInput function.

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.

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.

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.

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.

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.

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.

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.

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)".

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

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:

{"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}
JSON with syntax highlight

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.

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).

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).

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.


February 19, 2023 - Reported via
February 22, 2023 - TecLib developers ask for more information
February 22, 2023 - Sent some working PoC on TecLib SaaS with screenshots and step-by-step instructions
February 24, 2023 - TecLib developers confirm the issue
March 29, 2023 -  CVE assigned - CVE-2023-28838
April 5, 2023 - TecLib developers marked this as fixed in 10.0.7 with commit 3b1f59
April 5, 2023 - TecLib developers release an advisory (SQL injection through dynamic reports)

SQL injection through dynamic reports
### Impact A SQL Injection vulnerability allow users with access rights to statistics or reports to extract all data from database and, in some cases write a webshell on the server. ### Worka…