SQL-инъекции и XSS остаются самыми частыми дырами в веб-приложениях. Причина проста: обе уязвимости возникают из одной и той же ошибки — слепого доверия к данным, пришедшим от пользователя. Сайт принимает строку из формы или адреса и без проверки подставляет её в запрос к базе или в страницу. Злоумышленнику остаётся лишь подобрать нужные символы.
В этой статье разберём, как работают эти атаки, как выглядит уязвимый код и, главное, как его исправить. Примеры будут на PHP и SQL, но принципы одинаковы для любого языка и платформы.
Как работает SQL-инъекция
Представьте страницу, которая показывает товар по его идентификатору из адреса. Разработчик пишет запрос, склеивая строку:
$id = $_GET['id'];
$sql = "SELECT * FROM products WHERE id = " . $id;
$result = mysqli_query($link, $sql);
Если в адрес подставить обычное число, всё работает. Но что будет, если вместо числа передать фрагмент SQL? Например, добавить условие, которое всегда истинно, либо присоединить второй запрос с выборкой пользователей и паролей. База выполнит всё, что получила, потому что не различает, где код разработчика, а где данные пользователя.
Последствия бывают разными:
- чтение чужих данных: учётные записи, заказы, переписка;
- обход авторизации без знания пароля;
- изменение и удаление записей;
- в тяжёлых случаях запись файлов на сервер и выполнение команд.
Основная защита от SQL-инъекций: подготовленные запросы
Единственный надёжный способ — отделить код запроса от данных. Это делают подготовленные выражения с параметрами. Сначала в базу отправляется шаблон запроса с местами под значения, потом отдельно передаются сами значения. База воспринимает их строго как данные и никогда не исполняет.
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id');
$stmt->execute([':id' => $id]);
$row = $stmt->fetch();
В C# аналог выглядит так:
using var cmd = new SqlCommand(
"SELECT * FROM Products WHERE Id = @id", connection);
cmd.Parameters.AddWithValue("@id", id);
Запомните правило: никогда не вставляйте пользовательские данные в текст запроса конкатенацией. Это относится ко всем источникам: параметрам адреса, формам, куки, заголовкам, данным из файлов и других систем.
Дополнительные меры против SQL-инъекций
- Проверка типов. Если ожидается число, приведите значение к целому и отбросьте всё остальное.
- Белые списки. Имена полей для сортировки или таблиц нельзя передать параметром. Сопоставляйте ввод с заранее заданным списком допустимых значений.
- Минимальные права. Пользователь базы, под которым работает сайт, не должен иметь права удалять таблицы, читать системные данные или писать файлы.
- Скрытие ошибок. Не показывайте посетителям тексты ошибок базы: они подсказывают структуру запроса. Пишите ошибки в журнал.
- Использование ORM. Современные библиотеки по умолчанию формируют параметризованные запросы, но следите за местами, где вы вставляете сырой SQL.
- Хранение паролей в виде хешей. Даже если данные утекут, пароли не прочитаются напрямую. Применяйте современные алгоритмы с солью.
Как работает XSS
Межсайтовый скриптинг происходит, когда сайт выводит в страницу данные пользователя без обработки, и браузер воспринимает их как часть кода. Классический пример — форма комментария:
echo "<div class='comment'>" . $_POST['text'] . "</div>";
Если автор комментария вставит в текст тег скрипта, он выполнится у каждого, кто откроет страницу. Злоумышленник может украсть данные сессии, подменить содержимое страницы, перенаправить на фишинговый сайт, выполнить действия от имени посетителя.
Различают три вида XSS:
- Отражённый. Вредоносный код приходит в параметрах адреса и сразу выводится на страницу. Жертву заманивают по специальной ссылке.
- Хранимый. Код сохраняется на сервере, например в комментарии или профиле, и срабатывает у всех, кто открывает страницу. Самый опасный вид.
- На стороне клиента. Уязвимость в самом скрипте страницы, когда данные из адреса вставляются в документ без обработки.
Основная защита от XSS: экранирование при выводе
Все данные, выводимые в HTML, нужно превращать в безопасный текст: символы, имеющие особый смысл, заменяются сущностями. Тогда браузер покажет их как обычные символы, а не выполнит.
echo htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
Важный принцип: экранирование зависит от контекста вывода. Для HTML-текста, значения атрибута, содержимого скрипта и адреса применяются разные правила. Нельзя защитить все контексты одной функцией. Используйте шаблонизаторы, которые экранируют вывод по умолчанию, и отключайте экранирование только осознанно.
Дополнительные меры против XSS
- Политика безопасности контента. Заголовок, который ограничивает, откуда можно загружать и выполнять скрипты. Сильно снижает ущерб даже при найденной дыре.
- Флаг HttpOnly для кук. Скрипт на странице не сможет прочитать идентификатор сессии.
- Флаги Secure и SameSite. Ограничивают передачу кук и защищают от подделки запросов.
- Очистка разрешённого HTML. Если вы допускаете форматирование в комментариях, пропускайте текст через проверенную библиотеку очистки с белым списком тегов и атрибутов. Не пытайтесь писать фильтры регулярными выражениями.
- Проверка адресов. Не допускайте в ссылках схемы, исполняющие код, разрешайте только безопасные протоколы.
- Безопасное API в браузерном коде. Для вставки текста используйте свойства, работающие с текстом, а не с разметкой.
Типичные ошибки разработчиков
- Фильтрация на стороне клиента вместо серверной. Запрос можно отправить в обход формы.
- Попытка «вычистить» опасные слова. Всегда найдётся обходной вариант написания.
- Экранирование при сохранении, а не при выводе. Данные могут потом пойти в другой контекст.
- Доверие к данным из внутренних источников: файлы, другие сервисы, база, куда писал кто-то ещё.
- Использование устаревших функций для работы с базой без параметров.
- Отображение подробных ошибок на боевом сервере.
Как проверить свой сайт
Проведите хотя бы базовую проверку.
- Пройдитесь по всем формам и параметрам адреса и введите одиночную кавычку. Если появляется ошибка базы, перед вами потенциальная инъекция.
- Вставьте в поля комментариев и поиска безобидный тестовый тег и посмотрите, как он выводится. Если отображается как разметка, а не как текст, есть проблема.
- Используйте автоматические сканеры уязвимостей на тестовой копии сайта, а не на рабочей.
- Проведите ревизию кода: найдите места, где в запросы подставляются переменные конкатенацией, и места, где вывод идёт без экранирования.
- Настройте журналы и оповещения о подозрительных запросах.
Общая гигиена безопасности
Помимо двух главных атак, соблюдайте общие правила: обновляйте систему и библиотеки, удаляйте лишние плагины, делайте резервные копии и проверяйте, что они восстанавливаются, используйте сложные уникальные пароли и двухфакторную защиту для админки, ограничивайте доступ к служебным папкам, включайте защищённое соединение. Безопасность — это не разовая настройка, а привычка.
Заключение
SQL-инъекции и XSS побеждаются дисциплиной, а не хитрыми фокусами. Используйте параметризованные запросы везде, где в запрос попадают внешние данные, экранируйте всё, что выводите в страницу, ограничивайте права и включайте дополнительные защитные заголовки. Эти простые правила закрывают подавляющее большинство атак. Потратьте время сейчас: восстановление после взлома обойдётся намного дороже и по деньгам, и по репутации.