Deserialisation_vulnerabilites_exploitation

Lorsqu’on développe un jeu, on peut avoir besoin de sauvegarder la partie d’un joueur dans un fichier pour ne pas perdre sa progression afin qu’il puisse revenir là où il en était. De la même manière, quand on développe un éditeur de texte en ligne, on peut vouloir préserver le contenu que l’utilisateur a écrit.

En effet, il y a beaucoup de cas où l’on souhaite sauvegarder l’état de notre application pour le rétablir dans le futur. Deux termes sont utilisés pour définir ce processus : la sérialisation et la désérialisation.

Qu’est-ce la désérialisation (et la sérialisation) ?

La sérialisation est le processus qui consiste à convertir l’état d’une application dans un format approprié pour un transfert ou du stockage. La désérialisation est le processus inverse qui permet donc de restituer l’état de l’application.

Par ailleurs, il se peut que l’on souhaite sérialiser des données spécifiques comme des instances de classes par exemple. Il faut alors que le format dans lequel on sérialise la donnée le supporte. Cela se fait en spécifiant par exemple le nom de la classe en question à côté des données. Cependant, une question peut se poser : qu’est-ce qui arriverait lors de la désérialisation si l’utilisateur met un autre nom de classe que celui attendu ?

Les classes peuvent avoir des méthodes qui seront appelées lors de leur désérialisation, de la lecture ou écriture de leurs membres ou encore à leur destruction. Par exemple, une classe pourrait mettre dans une liste tous les fichiers temporaires qu’elle a créé pour les supprimer quand elle sera détruite. C’est le cas par exemple de cette classe XLSXWriter en PHP.

De fait, si un utilisateur est capable de contrôler ce que le serveur désérialisera, alors il pourrait utiliser des classes dangereuses pour s’octroyer des droits, supprimer des fichiers ou encore exécuter du code arbitraire.

Désérialisation en PHP : fonctionnement et exploitations possibles

En PHP, la fonction serialize est utilisée pour sérialiser une structure de données et son type. La fonction unserialize permet de revenir en arrière.

Suite à la sérialisation des données, elles sont représentées différemment en fonction de leur type.

  • Pour une chaîne de caractère s:13:"Hello, world!";, le nombre correspond à sa longueur.
  • Pour un nombre entier, i:42;.
  • Pour un booléen, b:0; ou b:1;.
  • Pour la valeur null, N;.
  • Pour une liste a:1:{s:5:"hello";s:5:"world";}, le premier nombre correspond au nombre d’entrées de la liste et, entre les accolades, une suite de clés/valeurs.
  • Pour un objet O:5:"Hello":1:{s:7:"to_whom";s:5:"World";}, le premier nombre correspond à la longueur du nom de la classe et, la suite c’est la même chose que pour une liste.

Un attaquant sera sûrement intéressé par la représentation d’un objet car il pourra contrôler le nom de la classe et ainsi construire ce qu’il souhaite. Prenons le code ci-dessous par exemple.

php
class TemporaryStorage {
	public $files = array();

	function __destruct() {
		foreach ($this->files as $temp_file) {
			echo "Deleting $temp_file...\n";
			@unlink($temp_file);
		}
	}
};

unserialize($_GET["user_data"]);

Si cette classe existe dans le code, alors un attaquant pourrait abuser de la déconstruction de TemporaryStorage pour supprimer arbitrairement des fichiers sur le système. Pour ce faire, il devra forger un objet ayant comme nom de classe TemporaryStorage avec un champ files et comme valeur une liste contenant les noms des fichiers à supprimer. Puis de passer tout ça à la fonction unserialize.


O:16:"TemporaryStorage":1:{s:5:"files";a:2:{i:0;s:20:"/app/application.php";i:1;s:16:"/data/datbase.db";}}

Si on transmet la chaîne de caractères ci-dessus, alors nous obtiendrons la réponse suivante du serveur.


Deleting /app/application.php...
Deleting /data/datbase.db...

Des classes faites maison qui permettraient à un attaquant d’augmenter l’impact peuvent nécessiter l’accès au code source pour être découvertes. Cependant, certaines bibliothèques contenant des classes vulnérables peuvent être utilisées, ce qui augmente les possibilités pour un attaquant. Sur ce point, le projet phpggc regroupe une liste de classes pouvant être exploitées dans des bibliothèques et Framework souvent utilisés.

Si une autre vulnérabilité est présente sur votre application qui permet d’éditer arbitrairement des sessions PHP, alors un attaquant pourrait l’utiliser pour faire de la désérialisation dangereuse car les sessions ne sont rien d’autre que des objets sérialisés.

Désérialisation en C# avec Json.NET : fonctionnement et exploitations possibles

La bibliothèque Json.NET permet de sérialiser et désérialiser des données dans un format considéré comme sûr, car simple : le JSON. Malheureusement, lors de la désérialisation, la bibliothèque utilisera le champ $type dans un objet pour savoir quelle classe instancier. Si l’option TypeNameHandling.Objects est utilisée, alors lorsqu’on essaiera de désérialiser un élément de type object on pourra spécifier n’importe quel type.

Prenons le cas suivant par exemple :

csharp
using System.Diagnostics;
using Newtonsoft.Json;

public class CommandManager {
	public string? output { get; set; }

	private string? _command;

	public string? command {
		get { return _command; }

		set {
			_command = value;

			Process p = new Process();
			p.StartInfo.RedirectStandardOutput = true;
			p.StartInfo.FileName = "/usr/bin/env";
			p.StartInfo.Arguments = _command;
			p.Start();

			output = p.StandardOutput.ReadToEnd();
			p.WaitForExit();

			Console.WriteLine(output);
		}
	}
};

Cette classe possède un setter personnalisé pour le champ command, il exécutera la commande passée et mettra la sortie dans le champ output. Comme expliqué plus haut, si nous utilisons la bibliothèque Json.NET pour désérialiser, alors nous pourrons spécifier le nom de cette structure dans le champ $type et appeler le setter command.

csharp
var result = JsonConvert.DeserializeObject<object>(
	user_input,
	new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }
);

Si nous utilisons le code ci-dessus pour désérialiser la donnée ci-dessous.

json
{"$type":"CommandManager, myproject","command":"id"}

Alors le setter de CommandManager sera appelé et notre commande id exécutée. De la même manière que pour l’exploitation PHP, il existe des listes de classes vulnérables sur des projets comme ysoserial.net.

Désérialisation dans d’autres langages et bonnes pratiques sécurité

De manière générale, le procédé est le même dans les langages qui permettent de charger dynamiquement du code ou des classes. On cherche une fonction de désérialisation puis des classes qui ont des méthodes intéressantes sans toujours tenir compte des considérations en termes de sécurité.

Nous proposons deux façons de remédier à ce problème. Si vous devez vraiment représenter des données complexes venant du serveur et renvoyées par le client, il est possible d’utiliser une signature cryptographique avec la donnée sérialisée par le serveur et de vérifier la signature lorsque l’utilisateur renvoie la donnée. De cette façon, un attaquant ne pourra pas modifier la donnée pour charger arbitrairement du code sans invalider la signature.

L’autre façon serait d’utiliser un format plus basique comme le JSON tout en utilisant une bibliothèque qui ne permet pas (ou en tout cas par défaut) de charger arbitrairement des classes.

Auteur : Arnaud PASCAL – Pentester @Vaadata