Что такое SQL-инъекция и почему это

top 5 comment moderation tools for facebook instagram

Что такое SQL-инъекция и почему это опасно

SQL-инъекция — уязвимость, когда непромытые/некорректно обработанные входные данные попадают в SQL-запрос и позволяют атакующему выполнить произвольные команды в базе данных. Последствия: утечка данных, изменение/удаление данных, обход аутентификации, получение доступа к ОС через расширенные возможности СУБД.

Типы инъекций:

  • In-band (error-based / union-based) — результат виден в ответе приложения.
  • Blind (boolean / time-based) — ответ не даёт прямой информации, но поведение/время ответа зависит от условия.
  • Out-of-band — использование вспомогательных каналов (DNS, HTTP callback).

Этические и юридические принципы тестирования

Перед любым тестированием:

  1. Получите письменное разрешение владельца ресурса (scope, правила, исключения).
  2. Работайте на тестовой среде или staging, идентичной production по стеку, но без реальных данных.
  3. Определите ограничения (часы тестирования, допустимые методы, закреплённый контакт на стороне заказчика).
  4. Логируйте всё тестирование и имейте план действий при случайного повреждения данных (backup/rollback).

Неэтичный/незаконный тест = уголовная/административная ответственность.

Защита: основная идея — недоверие к входным данным

Принцип: все внешние данные недоверенны. Комбинация мер обеспечивает надёжную защиту:

  • Параметризованные запросы / подготовленные выражения (prepared statements).
  • ORM с корректным использованием (без raw SQL с конкатенацией).
  • Белый список валидации (validation by allowlist).
  • Экранирование — вспомогательное, не как основной барьер.
  • Минимизация привилегий у DB-пользователей (least privilege).
  • WAF и RASP как дополнительный уровень защиты.
  • Логирование и мониторинг подозрительных запросов.
  • Регулярный аудит кода и ревью SQL-фрагментов.

5. Конкретные примеры защитного кода (разные языки)

PHP (PDO) — безопасный пример

<?php
// PDO с подготовленными выражениями
$dsn = 'mysql:host=localhost;dbname=appdb;charset=utf8mb4';
$pdo = new PDO($dsn, 'app_user', 'secret', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

// Пример безопасного SELECT
$stmt = $pdo->prepare('SELECT id, email FROM users WHERE username = :username');
$stmt->execute([':username' => $username_input]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Пояснение: ATTR_EMULATE_PREPARES => false заставляет драйвер использовать нативные подготовленные выражения.

Node.js (pg for PostgreSQL) — параметризация

const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.PG_CONN });

const text = 'SELECT id, name FROM customers WHERE email = $1';
const values = [emailInput];

const res = await pool.query(text, values);

Python (psycopg2) — безопасно

import psycopg2
conn = psycopg2.connect(dsn)
with conn.cursor() as cur:
    cur.execute("SELECT id, username FROM users WHERE id = %s", (user_id,))
    row = cur.fetchone()

Java (JDBC) — PreparedStatement

String sql = "SELECT id, name FROM products WHERE sku = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, skuInput);
ResultSet rs = ps.executeQuery();

C# (ADO.NET)

using (var cmd = new SqlCommand("SELECT id FROM Users WHERE login = @login", conn))
{
    cmd.Parameters.AddWithValue("@login", loginInput);
    var reader = cmd.ExecuteReader();
}

Пример на PHP — НИКОГДА не делать (уязвимый код)

// НИКОГДА так не делайте:
$query = "SELECT * FROM users WHERE username = '" . $_GET['user'] . "'";
// Конкатенация строк — прямой путь к инъекции

Валидация входных данных — практики

  • Используйте allowlist: если ожидается целое число — проверяйте is_numeric и приводите к int; если email — используйте строгую проверку формата + при желании дополнительную проверку домена.
  • Ограничивайте длину полей.
  • Для идентификаторов и enum используйте внутренние маппинги (например, если ожидается статус: 0,1,2 — не принимайте произвольное значение).

Конфигурация БД и аккаунтов (hardening)

  • Не используйте root/sa для приложения. Создайте отдельного пользователя с минимальными правами (SELECT/INSERT/UPDATE/DELETE только по нужным таблицам; избегайте DROP/ALTER/GRANT).
  • Отключите небезопасные расширения в СУБД, если не используете (например, в MySQL — FILE привилегии для обычных пользователей).
  • Включите шифрование соединений (TLS) между приложением и БД.
  • Ограничьте IP-адреса, которые могут подключаться к БД.
  • Регулярные резервные копии и проверка способности восстановить их.

WAF, RASP и другие инструменты защиты

  • WAF (Web Application Firewall) — блокирует известные сигнатуры/паттерны атаки и аномалии. Используйте как слой защиты, но не полагайтесь только на него.
  • RASP (Runtime Application Self-Protection) — интегрируется в приложение и может обнаруживать/блокировать инъекции на раннем этапе.
  • Настройка WAF: избегайте агрессивных правил, которые ломают легитимные запросы; ставьте learning/monitoring режим прежде чем включать blocking в продакшн.

Логирование и обнаружение инцидентов

Что логировать:

  • Полный текст SQL-запросов (в безопасном и GDPR-compliant виде), где это уместно.
  • Входные параметры и контекст запроса (путь, IP, User-Agent, user_id если есть).
  • Время выполнения запросов — медленные запросы могут указывать на попытки time-based blind SQLi.
  • Ошибки СУБД: stack traces, сообщения об ошибках (error-based SQLi часто вызывает ошибку с содержимым).

Мониторинг:

  • Настройте правила оповещений на аномалии: резкий рост числа ошибок 500/500-like DB errors; большое число запросов с подозрительными символами (' OR 1=1 -- и т.п.); необычные запросы к служебным таблицам.
  • Включите EDR/SIEM интеграцию (например, отправка логов в ELK/Graylog/Splunk).
  • Используйте метрики: количество уникальных SQL ошибок по IP, количество long-running запросов, процент 4xx/5xx.

Детекция на примерах (без эксплойтов)

Примеры сигнатур/правил (логический смысл, не конкретные payloads):

  • Проверка на наличие в параметрах последовательностей символов, характерных для SQL (символ одиночной кавычки ', комментарий --, /*, ключевые слова UNION, SELECT и т.п.) в сочетании с подозрительным поведением (ошибки, изменение выдачи).
  • Аномалия: параметры, содержащие большое количество специальных символов или цифр/символов в поле, где ожидается слово/имя (можно применить коэффициент подозрительности).
  • Time-based detection: большое количество долгих запросов от одного IP/aгента.

Частые ошибки разработчиков и как их исправлять

  • Использование строковой конкатенации для SQL. → Исправление: перевести на prepared statements / ORM.
  • Предоставление приложению избыточных DB-привилегий. → Исправление: минимизировать привилегии; использовать отдельного read-only пользователя для операций чтения.
  • Публикация подробных ошибок СУБД клиенту. → Исправление: логировать подробно на сервере, клиенту отдавать дружественное сообщение без деталей.
  • Отсутствие контроля длины и типов входа. → Исправление: валидировать и правильно обрабатывать.

Практические рекомендации для CI/CD и SDLC

  • Включите статический анализ кода (SAST), которые ищут небезопасную конкатенацию SQL.
  • Интегрируйте DAST (динамическое тестирование) в staging, но не в prod.
  • Code review: особое внимание к участкам, формирующим SQL.
  • Обновления зависимостей и СУБД — патчите вовремя.

Понимание уязвимого шаблона (на примере псевдо-кода)

Ниже — псевдо пример уязвимого шаблона запроса (только для понимания механики), и безопасный эквивалент — без эксплойтов:

Уязвимый (псевдо-код, демонстрация опасности конкатенации):

-- ПРЕДСТАВЬТЕ: сервер получает userInput из формы
query = "SELECT id, email FROM users WHERE username = '" + userInput + "';"
execute(query);

Проблемма: конкатенация входных данных напрямую в SQL — вход может изменить структуру запроса.

Безопасный подход — параметризация / подготовленные выражения:

-- ПАРАМЕТРИЗОВАННЫЙ ВАРИАНТ (псевдо)
query = "SELECT id, email FROM users WHERE username = ?;"
execute_prepared(query, [userInput]);

Или в ORM: User.find({ username: userInput }) — где ORM сам параметризует.

Пять реальных примеров уязвимого кода и исправления (без эксплойтов)

Каждый пример: уязвимость (покажу паттерн), почему опасно, исправление.

ВАЖНО: в примерах я не добавляю payloads/паттерны атак — только объясняю механизмы и показываю безопасные варианты.

Пример 1 — PHP + MySQL (PDO) — уязвимость: конкатенация строк

Уязвимый паттерн (псевдо/реальный стиль):

// Уязвимый код (НЕ делайте так)
$username = $_POST['username'];
$query = "SELECT id, email FROM users WHERE username = '" . $username . "'";
$result = $pdo->query($query);

Почему уязвим: пользовательская строка встраивается напрямую — меняет структуру SQL.

Исправление (PDO, prepared statements):

// Безопасный код
$stmt = $pdo->prepare('SELECT id, email FROM users WHERE username = :username');
$stmt->execute([':username' => $username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Пояснение: параметры отделены от синтаксиса запроса — база воспринимает вход как значение.

Пример 2 — Node.js (pg) — уязвимость: шаблонные строки

Уязвимый паттерн:

// Уязвимо
const query = `SELECT * FROM products WHERE sku = '${req.query.sku}'`;
const res = await client.query(query);

Исправление (параметры):

const text = 'SELECT * FROM products WHERE sku = $1';
const values = [req.query.sku];
const res = await client.query(text, values);

Пояснение: placeholders ($1) предотвращают интерпретацию структуры запроса.

Пример 3 — Python (psycopg2) — уязвимость: форматирование строки

Уязвимый паттерн:

# Уязвимо
cur.execute("SELECT * FROM orders WHERE order_id = '%s'" % order_id)

Исправление:

cur.execute("SELECT * FROM orders WHERE order_id = %s", (order_id,))

Пояснение: второй аргумент параметризует значения.

Пример 4 — Java (JDBC) — уязвимость: конкатенация в query

Уязвимый:

String sql = "SELECT * FROM users WHERE email = '" + email + "'";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);

Исправление:

String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, email);
ResultSet rs = ps.executeQuery();

Пример 5 — C# (ADO.NET) — уязвимость: прямое строение запроса

Уязвимый:

string sql = "SELECT * FROM Clients WHERE name = '" + name + "'";
SqlCommand cmd = new SqlCommand(sql, conn);
var reader = cmd.ExecuteReader();

Исправление:

var cmd = new SqlCommand("SELECT * FROM Clients WHERE name = @name", conn);
cmd.Parameters.AddWithValue("@name", name);
var reader = cmd.ExecuteReader();

Для каждого примера: добавьте unit-test, проверки на длину строки и allowlist, если поле имеет ограниченный набор допустимых значений (например, enum или numeric id).

5. Шаблон отчёта об уязвимости (структура, можно копировать)

[Название отчёта]
Дата: YYYY-MM-DD
Исполнитель: <имя, контакт>
Заказчик: <имя организации>, контактное лицо

1. Резюме
Краткое описание проблемы, общий уровень риска (Critical/High/Medium/Low), краткое влияние.

2. Scope тестирования
URLs / приложения / версии / ограничение тестирования / стенд.

3. Методология
Кратко: manual review, SAST, DAST, тестирование в изолированной среде.

4. Найденные уязвимости (по приоритету)
4.1. [Название уязвимости]
- Описание: где обнаружена
- Компонент: файл/маршрут/метод
- Уровень риска:
- Доказательства (PoC в стенде): описание шагов, логи, скриншоты (без нанесения вреда)
- Влияние: какие данные/функции под угрозой
- Рекомендации по исправлению: конкретный код/конфигурация/практика
- Retest steps: что проверять после исправления

(повторять для каждой уязвимости)

5. Общие рекомендации
- Применить parameterized queries во всем проекте
- Минимизировать права БД
- Включить centralized logging и SIEM
- Интегрировать SAST/DAST в CI

6. План исправлений и приоритеты
- Hotfix (срочно), Medium, Long term.

7. Приложения
- Выборка логов, конфигурации, PR-патчи, ссылки на ресурсы по remediation.