Intro
GLPI (Gestionnaire libre de parc informatique) is a popular open-source software in France and Brazil. It is used to create a mapping of a network through an inventory plugin, but also to gather users’ issues through a ticket system.
Starting research
As I was wondering how the update mechanism worked in GLPI, I saw something really interesting in this file.
It is important to note that most of the GLPI files are not accessible without authentication. Because of this, the attack surface on this software is reduced. However, the update.php script is accessible by an unauthenticated user. And this file contains juicy information. I started looking at it, and I immediately saw that this script under certain parameters, disclosed telemetry information.

This parameter allows the current session to recover valuable information known as Telemetry in GLPI. As we can see in the following code, having this value in the session allows us to access the telemetry page:

And as an unauthenticated user we can get the following result:

The interesting bit of information here is the token used and we will see why soon.
It is important to note that from here a user can at least enumerate the whole telemetry of the target, recover the versions of the stack used, but also the plugins in use by GLPI. The information is normally only accessible to high-privileged users. With this step we are not authenticated on the target, but we widen our attack surface.
Attacking dashboards
During previous GLPI research I was looking for the registration token present in the telemetry. This token is generated at install and used to identify the software instance. It is also stored in a database.

The most important thing here is that this token is needed to share dashboard through external website (for instance to display a dashboard in an iframe).


This link allows anyone to see a dashboard, here is how look the dashboard when we access it:

This link is generated with the token we got previously:

We can see that the link is generated using known values and making it a UUIDv5. From there we can craft links to access any GLPI dashboard.
But how is the dashboard generated?
Sub dashboard
In fact, the dashboard is dynamic; it is an arrangement of cards that are known as Widgets within GLPI. So each card is made dynamically, and then it is returned and placed to create a dashboard. Here is an example of outgoing requests for generating a dashboard:

As we can see, each card is recovered by a request. If you run one of these requests, you will get a single card of a dashboard:

You can add and remove card when customising a dashboard, and there are different types of cards:

In fact, there are a lot of cards, there is even a card that can render markdown in the dashboard if you want to take notes:

So I wondered how does it work under the hood. How many cards are available in the back end, and can an attacker abuse them ?
Analyzing widgets
Taking a quick look at the requests sent by the browser, I found that the cards are listed in the following function:

As we can see, there is a widgettype associated with each card and a provider. At first, the provider is called to get the arguments used for the widget:

Then the widget is called with these arguments to get the html that will be rendered to the user on the dashboard:

By looking at this, I was wondering if there was a vulnerability in one of those widgets. It increases our surface attack by a lot, and might potentially lead to privilege escalation. It is also important to note that we do not fully control the variables given in the call_user_func function.
Exploring widgets
The widget class is coded in this file: src/Dashboard/Widget.php. By taking a look at it we can find a lot of static functions available to render different widgets (pie, half-pie, donut, bar, numbers…). At first I started taking a look at a widget that creates a link with the database. This widget is a search widget that allows user to display database information such as Tickets in use. However this widget, despite its ability to retrieve some information, does not allow for a direct compromise. So I took a step back, and went for another widget.
Widget pie
At this point, I did not know what to look for, there are only style related functions in this class. Anyway, never give up, I continue to look into the source. I found that some widgets are styled dynamically. By going deeper into this rendering process, I saw that SCSS was used in order to return CSS of some widget. And what was interesting is that we control some arguments of the template!
A function clearly caught my interest, by compiling the given arguments to a CSS format:

It obviously seems interesting even if we do not know what SCSS is…
SCSS Injection
GLPI uses a library called scssphp in order to compile SCSS to CSS dynamically. This library is open source and located here: http://scssphp.github.io/scssphp/.
I did not know anything about this library or SCSS at first. To get an idea of what it was, I started reading the code and I also took a look at the examples and tests available on the GitHub page. What is important to remember is that SCSS is a language which once compiled, is just CSS.
Here is an example of using SCSS:

A lot of features are available in SCSS, such as functions, variables, string interpolation, loop, etc. A popular function in SCSS is import which allows us to import a SCSS file into our current file.
Obviously when seeing an import function, I tried to import a file like /etc/passwd. So I set up GLPI, leaked the token in use, and sent a request to generate a dashboard card with dynamically compiled SCSS, and the payload import(“../../../../../../../../etc/passwd”); And as you can imagine, it just FAILED !
Obviously I do not always mention the time lost in rabbit hole, but I lost a lot of time trying to exploit uncommon paths that were not vulnerable.
Anyway, here we have a BIG problem cause GLPI sanitizes all input given in order to prevent XSS attacks and also SQL Injection attacks. So the payload import(“…”), becomes import("…"), which cannot be compiled by our library. By looking at the example I started understanding that this library provides some additional functions such as quote. The quote function allows any input to be converted as string:

So by using this function we are able to create a string without quotes! Then I relaunched my payload, and here is what I have got:

We got an error, by checking the source code of the import function inside the compiler I noticed why:

As we can see, the import function will only resolve path with certain extensions. So this path is a failure (again!). At this point I was very curious of the different functions available in this library. How does the compiler know which functions to call and can we abuse them ?
By looking at the source code of this class, I understood that the functions were stored in the format ‘lib’ . $func_name:

So the compiler resolved all functions by checking if a method exists in its own class starting with the prefix “lib”. So I started looking at some functions by their name because there are 88 of them:

Then I reviewed the source code of functions with interesting names. When looking at the functions one by one, the last one caught my attention:

There is a function named libScssphpGlob! As you might know, glob is a reference to the globbing mechanism on linux, and it is usually used in order to list files or directories using a pattern. Let’s take a look at the code for this function:

As shown above, this function allows the user to list files (not directories) matching a given pattern. Even if this function triggers an error or warning, the code is still working and usable for our attack.
Why is listing files such a big deal in PHP ?
It is important to remember that in PHP, sessions are stored by default on the filesystem. All of those sessions files have a name which is a unique random identifier. This identifier is stored as the cookie in the browser. Usually, a cookie named PHPSESSID contains this identifier.
So if you can list files in a PHP application, you can often hijack users sessions. And here we are, we can setup our plan to recover the sessions in use by the application.
On GLPI, the sessions are stored inside the folder /files/_sessions/.
Exploit chain
- Set our session with Telemetry Info
- Leak the token of the GLPI instance
- Craft the UUIDv5 needed to access a dashboard without authentication
- Request the HTML of a card which eventually uses SCSS
- Inject malicious SCSS to call the function libScssphpGlob()
- List the sessions available on GLPI
- Hijack the session of a high-privilege user
Demo
Check for the vulnerability
All GLPI instances between version 9.5.0 and 10.0.16 are vulnerable by default.
The function allowing to list files from SCSS has been removed in a recent version of the library, thus it is recommended to update your vendor folder.
GLPI also recommends changing the default files directory. However, an attacker will still be able to find sessions, even if the folder is somewhere else on the filesystem.
Recommendations
- Upgrade GLPI
- Upgrade GLPI libraries
Proof of Concept
We developed a tool named glpwnme that allows to check for some GLPI vulnerabilities:
https://github.com/Orange-Cyberdefense/glpwnme
Disclosure Timeline
- 10/29/2024 – Report leakymetry to GLPI
- 10/29/2024 – CVE-2024-50339 reserved
- 11/06/2024 – GLPI version 10.0.17 released (containing patch)
- 12/11/2024 – GHSA published