LFI через PHP stream wrappers (php://filter)
Наивная фильтрация ../ не блокирует php://filter — атакующий читает исходники PHP-файлов в base64.
mediumphpPro
Задача
# Лаба: LFI через PHP stream wrappers
## Сценарий
Команда SecureShop добавила раздел «Документация» по адресу `/doc?name=...`. Разработчик прочитал пару статей про path traversal, добавил проверку, что параметр не содержит `..` и заканчивается на `.php`, и посчитал задачу закрытой. Однако `include()` в PHP понимает не только пути в файловой системе — он работает и со **stream wrappers**, которые позволяют читать файлы через встроенные фильтры PHP.
Stream wrappers — это специальные URI-схемы (`php://`, `phar://`, `data://`, `zip://`, `compress.zlib://`), которые PHP интерпретирует как источники данных. Самый опасный из них — `php://filter`. Он позволяет прогнать содержимое любого читаемого файла через встроенные конвертеры (например, `convert.base64-encode`) перед тем, как `include()` попытается выполнить результат. Поскольку base64-закодированный PHP-код не является валидным синтаксисом, интерпретатор не выполняет его — но он попадает в буфер вывода, и атакующий читает исходники.
## Уязвимый паттерн
Обработчик `/doc?name=...` принимает имя статьи из GET-параметра и пропускает его через две поверхностные проверки: блокирует подстроку `..` и требует, чтобы значение оканчивалось на `.php`. После этого функция конструирует `include_path` (включающий корень приложения) и вызывает `include()` с пользовательским значением.
Проверка ищет литеральные `..` и `/`, но никак не препятствует префиксу `php://`. Параметр вида `name=php://filter/convert.base64-encode/resource=...` не содержит `..`, оканчивается на `.php` и проходит обе проверки — а PHP интерпретирует его как stream wrapper и прогоняет произвольный читаемый файл через base64-кодировщик.
## Цели
1. Подтвердить, что наивная защита (`..` blacklist + суффикс `.php`) пропускает stream wrappers.
2. Получить base64-закодированные исходники серверных файлов (`internal/handlers/handlers.php`, `internal/env/env.php`, `cmd/main.php`) и убедиться, что в ответе действительно лежит PHP-исходник.
3. По прочитанному исходному коду понять, что флаг лабы хранится в переменной окружения `LAB_FLAG`.
4. Через тот же stream wrapper прочитать `/proc/self/environ` и извлечь значение `LAB_FLAG=flag{...}`.
## Точка входа
* **Эндпоинт:** `GET /doc?name=<article>`
* **Легитимные значения:** `index.php`, `api.php`, `faq.php`
* **Учётные данные обычного пользователя:** `demo` / `demo` (логин не требуется для эксплуатации — эндпоинт `/doc` публичный)
* **Маркер успешного чтения:** base64-блок длиной 200+ символов в ответе
* **Источник флага:** переменная окружения `LAB_FLAG` процесса Apache (читается функцией `getFlag()` в `internal/env/env.php`)
## Почему это важно
LFI через stream wrappers — реальный класс уязвимостей, регулярно встречающийся в production-приложениях на PHP. CWE-98 («Improper Control of Filename for Include/Require Statement») и CVE-базы содержат сотни записей именно про эту технику. Сюда же относятся знаменитые атаки на phpMyAdmin, Drupal и другие CMS.