Analysis of some serious vulnerabilities on GLPI products

Tram Ho

In the previous article, I analyzed CVE-2022-35914, in this article, I will analyze the bypass authentication vulnerability through SQL injection with the code CVE-2022-35947.

Analysis CVE-2022-35947

According to the description of this vulnerability at https://github.com/glpi-project/glpi/security/advisories/GHSA-7p3q-cffg-c8xh , it is known that an attacker can take advantage of the vulnerability to login to an account. of any user with an API token when Enable login with external token is enabled (it is enabled by default). Also here we know that the faulty glpi version is < 10.0.3 and is fixed in version 10.0.3.

image.png

Diff patch

From the information above, we proceed to diff versions 10.0.2 and 10.0.3 to find the source of the vulnerability. Commit bug fix here: https://github.com/glpi-project/glpi/commit/564309d2c1180d5ba1615f4bbaf6623df81b4962

image.png

glpi fixes the SQLi bug by ensuring that the token passed to the getFromDBbyToken function is a string. if not string will give an error immediately.

Analysis

Let’s find out why glpi fixes such a bug. Jump into the getFromDBbyToken function in src/User.php

image.png

Here there are 2 jobs we have to do. The first is to find the taint flow from source to this function, the second is to find the taint flow from this function to the sink. about sink or source, you can read my old posts again. To find this taint fow you can use ide like PhpStorm , in this article I use VSCode to trace and debug by echo or var_dump the variables I’m interested in.

Train flow from source to function getFromDBbyToken

At the getFromDBbyToken function, right-click and select Go to References to list all the places where this function is used.

image.png

Here appear 2 places to call getFromDBbyToken in the file Auth.php and Session.php . Re-reading the description of the vulnerability, we know this is a bypass authentication vulnerability, so there’s a high chance that what we’re interested in will be in Auth.php . Jump into getAlternateAuthSystemsUserLogin function in the Auth.php file. This function will handle different logins based on $authtype passed in. If $authtype=self::API then the function will call getFromDBbyToken .

image.png

At line 610, the parameter passed to getFromDBbyToken taken from the variable $_REQUEST['user_token'] is completely controllable. However, we do not know how to jump into this case, our first job does not stop here. Continue the reverse trace from the getAlternateAuthSystemsUserLogin function.

image.png

This function will be called in the login function. This function will perform the login of the user. The login request will look like this:

image.png

Observe the code from line 750-754

if $noauto=false and self::checkAlternateAuthSystems() return values ​​other than false then getAlternateAuthSystemsUserLogin will be called with the $authtype parameter being the return value of self::checkAlternateAuthSystems() . Jump into self::checkAlternateAuthSystems() .

image.png

At line 1324, if the request contains a noAUTO parameter, the function will return false immediately. So when we send login request, we need to remove this parameter. Next observe the code line 1360, if user_token parameter exists in the request, the function will return self::API . This is exactly what we need.

In a nutshell, the tain from source to the getFromDBbyToken function looks like this:

login(no noAUTO parameter and user_token parameter)

=> getAlternateAuthSystemsUserLogin($authtype)

=>getFromDBbyToken($_REQUEST[‘user_token’], ‘api_token’)

Taint flow from getFromDBbyToken function to sink

Our next job will have to find taint from getFromDBbyToken to sink.

The getFromDBbyToken function takes a user_token parameter and passes it to the getFromDBByCrit function

Before going to the getFromDBByCrit function we need to note one thing. Version 10.0.3 only accepts getFromDBbyToken function as a string input, so perhaps if the input is a data type other than string it will cause an error. Here we aim for the array data type. I will explain why below.

Jump into the getFromDBByCrit function.

Here we have to observe the value of the variables passed, I use var_dump to observe the variable $crit .

image.png

image.png

With user_token=1 , $crit is currently an array with the key glpi_users.api_token and the value of type 1 string. Next, the $crit variable is overwritten back into an array where the key WHERE value is the value. initial value of the variable $crit

image.png

Next $crit will be passed to the function $DB->request($crit); . Jump into this function.

This function calls execute($tableorsql, $crit, $debug)

Here, the query will be built using the buildQuery($table, $crit, $debug) function and then executed with $this->res = ($this->conn ? $this->conn->query($this->sql) : false);

Go into the buildQuery function. Note, the $table parameter is $crit I described above, and currently $table is an array .

image.png

Reading the code logic, at line 146, the $table variable will be set to glpi_users . Next at line 203, since the $crit array contains a key of WHERE , $where variable will be set to an array with the key glpi_users.api_token and the value 1

image.png

At line 311, the sql variable containing the query will be concatenated as follows.

jump into the function analyseCrit

In this function, our $name variable currently has the value glpi_users.api_token

image.png

So we’ll jump to line 557

go into the function analyseCriterion . Note, the $value parameter is currently the user_token value we passed in.

At line 582. If value is an array consisting of 2 elements, the first word is one of the operators in the list, then $comparison = $value[0]; and the value after operator will be $value[1] .

image.png

The function will then return a concatenated string from those two elements

This is the sink we are looking for. Let me explain a little more. If the user_token value is a 2-element array. Where the first element is LIKE , and the second element is %a% , the query after string concatenation will be ... WHERE glpi_users.api_token LIKE %a%

At this point, we were able to bypass the authen successfully. If the user creates an API token with the character a , the above query will return that user => we login to that user account.

In summary, our chain from source to sink will look like this:

=> login(no noAUTO parameter and user_token[]=['LIKE','%a%'] parameter)

=> getAlternateAuthSystemsUserLogin($authtype)

=> getFromDBbyToken($_REQUEST[‘user_token’], ‘api_token’)

=> getFromDBByCrit([$this->getTable() . “.$field” => $token])

=> $DB->request($crit)

=> execute($tableorsql, $crit, $debug)

=> buildQuery($table, $crit, $debug)

=> analyzeCrit($crit, $bool = “AND”)

=> analyzeCriterion($value)

POC

java_tCW0KucJ67.gif

Share the news now

Source : Viblo