PHP Deserialization: Phar RCE через Phar::getMetadata()
Эндпоинт просмотра инвойсов пытается «прочитать структурированные метаданные» и вызывает Phar::loadPhar() + getMetadata() на пути из параметра без проверки расширения. Атакующий загружает PHAR-архив под видом .jpg с объектом InvoiceCache в metadata — getMetadata() делает unserialize(), триггерит __destruct() → system() → RCE.
hardphpPro
Задача
# Атака: Phar Deserialization через file_exists()
## Сценарий
SecureShop добавил функцию «Мои инвойсы». Пользователь может загрузить файл (формально инвойс в формате JPEG/PDF) и просмотреть его на странице `/invoice?file=<имя>`.
Под капотом обработчик просмотра пытается «распознать структурированные метаданные инвойса» — если загруженный файл является PHAR-архивом, приложение вызывает `Phar::loadPhar($path)` и затем `Phar::getMetadata()`, чтобы вывести информацию о компании и дате выставления. В обычном режиме это работает даже на файлах с расширением `.jpg`, потому что `Phar::loadPhar()` распознаёт PHAR по сигнатуре `__HALT_COMPILER()` в теле файла, а не по расширению.
Классика PHP-безопасности: вызов `getMetadata()` (или любая попытка прочитать metadata PHAR-архива) автоматически выполняет `unserialize()` над её содержимым. И это означает вызов magic-методов (`__wakeup`, `__destruct`, `__toString`) у классов, которые есть в приложении.
В коде SecureShop есть класс `InvoiceCache` — внутренняя утилита для чистки устаревшего кэша. Его `__destruct()` вызывает `system($this->cleanupCmd)`. Классическая POP-gadget: если заставить PHP десериализовать объект `InvoiceCache` с контролируемым полем `cleanupCmd` — получим произвольное выполнение команд.
## Цель
1. Создать PHAR-архив, в метаданные которого положить объект `InvoiceCache` со свойством `cleanupCmd`, выполняющим чтение флага.
2. Загрузить его через `/invoice/upload`, замаскировав под `.jpg`.
3. Триггернуть десериализацию, запросив `/invoice?file=<ваш_файл>` — handler сам вызовет `Phar::loadPhar()` + `getMetadata()` на загруженном «изображении» и десериализует gadget.
4. Добиться того, чтобы gadget записал содержимое `/flag.txt` в доступное для чтения место (например, `/tmp/pwn.txt`), и прочитать оттуда флаг через тот же endpoint `/invoice?file=/tmp/pwn.txt`.
## Данные для входа
- Зарегистрируйте нового пользователя на `/register` — роли `user` достаточно.
- Лабораторный флаг лежит в `/flag.txt` (файл присутствует в контейнере).
## Что должно получиться
В каком-либо доступном месте (`/tmp/pwn.txt` и т. п.) появится флаг вида `flag{...}`. Скопируйте его в поле ответа.
## Уязвимый endpoint
- `GET /invoice?file=<path>` — уязвим: вызывает `Phar::loadPhar()`/`getMetadata()` на пути, пришедшем от пользователя, без проверки расширения и происхождения.
- `POST /invoice/upload` — принимает любой бинарь под произвольным расширением.
## Уязвимый файл
Обработчик просмотра инвойсов и gadget-класс кэш-слоя приложения.