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...
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 Gestionnaire Libre de Parc Informatique, and is a french software solution meant to manage various assets, such as machines, software, or licenses.
Since GLPI is a free piece of software, I decided to install my own local instance, and I have to admit, it was really straightforward. After a few minutes, I had it running on a fresh Ubuntu server VM. After a few days of research, a pair of vulnerabilities finally came to light: as a low-privileged user, it was possible to take over the super-admin’s account.
By using the built-in post-only
account, features are quite limited. This account is only allowed to create tickets, but is not supposed to enumerate others users nor to see advanced settings.
However, one can notice a first interesting thing by creating a new ticket, and trying to add watchers: an AJAX call is made to /ajax/actors.php
, returning details about existing users and groups (while the Users
menu was not available to this user) …
One can see that the query contains a parameter named entity_restrict
. If the application is used to manage multiple entities (kind of tenants), it can be abused to enumerate the users of another one, which is not supposed to be permitted. For instance, resending the same request with the entity_restrict
equal to 1 would return additional users, belonging to another entity. This issue is tracked as CVE-2024-27937.
By taking a look at the source code of /ajax/actors.php
, one can see that such AJAX call would eventually trigger the function User::getSqlSearchResult
, but only the following fields are returned to the user:
$results[] = [
'id' => "User_$ID",
'text' => $text,
'title' => sprintf(__('%1$s - %2$s'), $text, $user['name']),
'itemtype' => "User",
'items_id' => $ID,
'use_notification' => strlen($user['default_email'] ?? "") > 0 ? 1 : 0,
'default_email' => $user['default_email'],
'alternative_email' => '',
];
Interesting, because one could therefore leak personal details of other users, but not sufficient
GLPI creates numerous relationships between objects, with User
s creating Ticket
s about Computer
s, pieces of Software
, etc. The application therefore heavily relies on dropdown list widgets to make it easy, the latter being populated based on the requested object types and user’s rights. For instance, the /ajax
folder contains the following scripts, meant to build such widgets:
getDropdownFindNum.php
dropdownMassiveActionAddActor.php
dropdownProjectTaskTicket.php
getDropdownUsers.php
dropdownValidator.php
dropdownShowIPNetwork.php
dropdownNotificationEvent.php
dropdownMassiveActionAuthMethods.php
dropdownConnectNetworkPort.php
dropdownConnectNetworkPortDeviceType.php
getDropdownConnect.php
dropdownMassiveAction.php
getDropdownNumber.php
dropdownAllItems.php
dropdownMassiveActionField.php
dropdownUnicityFields.php
getShareDashboardDropdownValue.php
dropdownSoftwareLicense.php
dropdownInstallVersion.php
dropdownMassiveActionOs.php
dropdownTrackingDeviceType.php
dropdownTicketCategories.php
dropdownFieldsBlacklist.php
dropdownNotificationTemplate.php
dropdownTypeCertificates.php
dropdownLocation.php
getAbstractRightDropdownValue.php
dropdownValuesBlacklist.php
dropdownMassiveActionAddValidator.php
dropdownDelegationUsers.php
dropdownConnect.php
getDropdownValue.php
dropdownRubDocument.php
dropdownItilActors.php
These scripts call routines of the class Dropdown
, which is meant to Print out an HTML “<select>” for a dropdown with preselected value, as said by a comment in the aforementionned class. Normally, these routines are supposed to only display specific types of objects, for example (in dropdownLocation.php
) this one with Location
instances:
echo Location::dropdown([
'value' => $locations_id,
'entity' => $entities_id,
'entity_sons' => $is_recursive,
]);
But what if we could ask for a dropdown containing arbitrary objects ?
GLPI offers a lot of menus, and I quickly got overwhelmed by the huge amount of requests that my browser sent. However, a request caught my attention, while trying to link a ticket to another one:
This request seemed to return a set of objects (Ticket
) with some parameters. Interestingly enough, the field id
, specified with the parameter displaywith
, is returned in the answer. It really sounds like an SQL-query-as-a-service, so what if one asks for the item type User
, displayed with the field password
?
The file /ajax/getdropdownValue.php
is as follows, forwarding the POST’ed data to Dropdown::getDropdownValue
:
A first check against what is named an IDOR token is made (line 2’619), and if everything goes well, a new object is created based on the advertised itemtype
. If the parameter displaywith
exists, then it persists inside the $post
array, and instructs the function to include specific additional fields in the response. By reading the code thoroughly, one can realise that the POST’ed data are used to build the query without checking the access rights regarding the expected table. So normally, I should have been able to enumerate the table glpi_users
and ask for any field in this table, but obvisouly, there was a catch …
N.B. the value of itemtype
is not exactly the name of a DB table. It is supposed to be the name of a PHP class, inherited from the class CommonDBTM
. Each child class is supposed to have its matching DB table, therefore one should set it to User
(the PHP class) to read from the table glpi_users
(or equivalent).
To make it work, the request must be authenticated, and this is done with the cookies. Moreover, AJAX calls made through POST need to contain the header X-Glpi-Csrf-Token
. The appropriate value can be found in the meta
tag glpi:csrf_token
, in HTML pages returned by the application:
<meta property="glpi:csrf_token" content="1fed5029e3d6a73af3352791f7ecff41e79c7644cbd89cf41d1b3e46e6ca99b6">
One important thing to notice is that dropdown lists are not directly written inside the HTML pages returned by the server. Instead, a piece of JavaScript code is returned, that would retrieve on-demand the list of the objects to display, and populate the dropdown lists. Therefore, an additional parameter named _idor_token
must be put in the request body sent by JavaScript, in order to prevent from unauthorised access (hence the call to validatIDOR
). The idea is that an IDOR token is tied to an object type, a set of rights, an entity, and for a limited time. Multiple _idor_tokens
can be found in a page (one per dropdown list), and for instance, a first one is used to get the list of the tickets, another one for the list of the users, and another one for the list of the machines.
Different ways exist to obtain an IDOR token, and they can be found by searching for calls to Session::getNewIDORToken
in the source code. One of them is done in User::dropdown
, which can be called from /ajax/dropdownValidator.php
.
In the response, one can see that an IDOR token to enumerate users is returned. However, querying the endpoint /ajax/getdropdownValue.php
by specifying the parameter _idor_token
and itemtype
does not work, and returns a blank page. By debugging the programme, one can realise that it is because of a failing check during the IDOR token validation.
To investigate and get clues about what was going wrong, I added a few lines in the file src/Session.php
to print the current state of each IDOR token (not the cleanest way to debug, but easier and sufficient enough):
By taking a look at the debug log, one can realise that the expected token is tied to an associative array with the keys itemtype
, right
and entity_restrict
while our token only contains itemtype
. I then asked for a new token, with the expected claims:
N.B: note that the names of the POST parameters and the keys of the expected elements in the array of IDOR tokens are not the same, but this is how it works. To have an appropriate entity_restrict
, the POST parameter to set in the first request is entity
.
Now, by setting the parameter displaywith[]=password
(can be specified multiple times), one can therefore extract all the hashes from the database. However, there can be hashed with bcrypt, and if they are strong enough, they might be uncrackable.
Yay
Another way to abuse such feature and compromise an account is to extract email addresses (first vulnerability), and ask for a password reset. The token would be stored as password_forget_token
in the DB. The emails addresses are not stored in the same table, but as we saw at the beginning while requesting /ajax/actors.php
, they can be easily obtained.
Since IDOR tokens are tied to an item type, one needs to obtain a valid one for each DB table. So far, it was possible because the script /ajax/dropdownValidator.php
could return a piece of code to retrieve the list of the users, but what if we want to dump the content of the table glpi_configs
? A more generic way to obtain tokens exists, because of an arbitrary object creation. Let’s take /ajax/cable.php
as an example (not the only one):
The item type is taken right from $_POST["itemtype"]
, and the static routine dropdown
is called on the advertised class, meaning that any child of CommonDBTM
could be enumerated. To begin with, a request can be sent to /ajax/cable.php
, specifying the appropriate action
and itemtype
parameters:
In the first request, one can see that application returns a token alongside the attributes right=id
and entity_restrict=-1
. In the second request, these attributes must match, otherwise nothing is returned.
Now, let’s retry it with the table glpi_configs
:
This issue is tracked as CVE-2024-27930.
In a real world scenario, an attacker might try to enumerate the email addresses to find the admin’s one, ask for a password reset link, and abuse the second vulnerability to recover it. Once connected as super admin, there is a plenty of interesting things to do. I guess that one of the most straightforward ways to RCE is to add a plugin, like shellcommands.
The issues were reported to the vendor and patched in a few days. Let’s note that the description for CVE-2024-27930 was registered as:
GLPI is a Free Asset and IT Management Software package, Data center management, ITIL Service Desk, licenses tracking and software auditing. An authenticated user can access sensitive fields data from items on which he has read access. This issue has been patched in version 10.0.13.
This is not entirely true, since an authenticated user can access any type of object (child of CommonDBTM
), regardless of their privileges.
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...