На примере SQL‑запроса с динамической подстановкой параметры объясните, как возникает SQL‑инъекция и предложите безопасные способы защиты на уровне приложения и БД
Что такое SQL‑инъекция (коротко) - SQL‑инъекция возникает, когда приложение формирует SQL‑запрос путём конкатенации/подстановки сырых пользовательских данных в текст запроса. Злоумышленник подставляет специальную строку, которая изменяет синтаксис запроса и выполняет нежелательные операции (чтение, модификация, удаление данных, обход аутентификации). Пример уязвимого кода (динамическая подстановка) - На языке, где строки собирают через конкатенацию: ``` username = get_param("username") password = get_param("password") sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';" db.query(sql) ``` - Если злоумышленник подаст `username = "admin' --"` запрос станет: ``` SELECT * FROM users WHERE username = 'admin' --' AND password = '...'; ``` Всё после `--` игнорируется как комментарий — получен обход проверки пароля. Или атакующий может дать `password = "' OR '1'='1"` и получить: ``` SELECT * FROM users WHERE username = 'someone' AND password = '' OR '1'='1'; ``` Условие `OR '1'='1'` всегда истинно — авторизация пройдена. Почему это работает - SQL‑парсер видит результирующий текст и выполняет его. При динамической подстановке пользовательский ввод становится частью синтаксиса SQL, а не только данных. Безопасные способы защиты 1) На уровне приложения (первый и главный уровень) - Параметризованные запросы / подготовленные выражения: - Пример (Python + psycopg2): ``` cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password)) ``` Параметры передаются отдельно; драйвер экранирует/передаёт их как данные, не как SQL. - Java (PreparedStatement), PHP (PDO::prepare), Node.js (pg with parameter placeholders) — то же самое. - ORM с безопасным формированием запросов (но избегайте raw SQL без параметров). - Белый список и валидация входа: - для id — проверять число, для email — проверять формат, для ограниченного набора значений — выбирать из списка, а не принимать произвольный текст. - Избегать динамического SQL. Если динамический SQL необходим (например, динамические имена колонок), проверять/белить имена через жёсткий список допустимых значений, а не вставлять напрямую. - Использовать безопасное экранирование только как запасной вариант (driver.escape), но не полагаться на него как на основной метод. - Ограничение длины и типов входа, таймауты запросов, лимит возвращаемых строк. - Логирование и мониторинг подозрительной активности (повторные неудачные логины, необычные запросы). 2) На уровне базы данных - Принцип наименьших привилегий: - Дать приложению отдельного DB‑пользователя с минимально необходимыми правами (например, только SELECT/INSERT/UPDATE для конкретных таблиц). - Роли и гранты вместо использования суперпользователя/администратора. - Хранимые процедуры с параметрами (если используются корректно) — параметры тоже передаются как данные, но не все СУБД защищают одинаково: всё ещё используйте привилегии и проверку. - Включить аудит и логирование SQL‑запросов; мониторить аномалии. - Использовать механизм блокировки/фильтрации на уровне БД (Если доступно: SQL Firewall, database activity monitoring). - Ограничения на уровне БД (CHECK, FOREIGN KEY) для целостности данных — снижают полезность некоторых инъекций. - Обновления и патчи СУБД, защита доступа (SSL/TLS, сетевые ACL), смена учётных данных и ограничение доступа по IP. Короткий практический чек‑лист - Никогда не вставляйте сырые данные в текст SQL. - Используйте подготовленные выражения/параметры во всех запросах. - Белый список для имен таблиц/полей и для структурных изменений запроса. - Минимальные привилегии у DB‑пользователя приложения. - Логирование/аудит и WAF/DB firewall по возможности. Если нужно, могу привести конкретный исправленный пример для вашей технологии (PHP/Java/Python/Node.js) и показать разницу vuln → safe.
- SQL‑инъекция возникает, когда приложение формирует SQL‑запрос путём конкатенации/подстановки сырых пользовательских данных в текст запроса. Злоумышленник подставляет специальную строку, которая изменяет синтаксис запроса и выполняет нежелательные операции (чтение, модификация, удаление данных, обход аутентификации).
Пример уязвимого кода (динамическая подстановка)
- На языке, где строки собирают через конкатенацию:
```
username = get_param("username")
password = get_param("password")
sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';"
db.query(sql)
```
- Если злоумышленник подаст `username = "admin' --"` запрос станет:
```
SELECT * FROM users WHERE username = 'admin' --' AND password = '...';
```
Всё после `--` игнорируется как комментарий — получен обход проверки пароля.
Или атакующий может дать `password = "' OR '1'='1"` и получить:
```
SELECT * FROM users WHERE username = 'someone' AND password = '' OR '1'='1';
```
Условие `OR '1'='1'` всегда истинно — авторизация пройдена.
Почему это работает
- SQL‑парсер видит результирующий текст и выполняет его. При динамической подстановке пользовательский ввод становится частью синтаксиса SQL, а не только данных.
Безопасные способы защиты
1) На уровне приложения (первый и главный уровень)
- Параметризованные запросы / подготовленные выражения:
- Пример (Python + psycopg2):
```
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
```
Параметры передаются отдельно; драйвер экранирует/передаёт их как данные, не как SQL.
- Java (PreparedStatement), PHP (PDO::prepare), Node.js (pg with parameter placeholders) — то же самое.
- ORM с безопасным формированием запросов (но избегайте raw SQL без параметров).
- Белый список и валидация входа:
- для id — проверять число, для email — проверять формат, для ограниченного набора значений — выбирать из списка, а не принимать произвольный текст.
- Избегать динамического SQL. Если динамический SQL необходим (например, динамические имена колонок), проверять/белить имена через жёсткий список допустимых значений, а не вставлять напрямую.
- Использовать безопасное экранирование только как запасной вариант (driver.escape), но не полагаться на него как на основной метод.
- Ограничение длины и типов входа, таймауты запросов, лимит возвращаемых строк.
- Логирование и мониторинг подозрительной активности (повторные неудачные логины, необычные запросы).
2) На уровне базы данных
- Принцип наименьших привилегий:
- Дать приложению отдельного DB‑пользователя с минимально необходимыми правами (например, только SELECT/INSERT/UPDATE для конкретных таблиц).
- Роли и гранты вместо использования суперпользователя/администратора.
- Хранимые процедуры с параметрами (если используются корректно) — параметры тоже передаются как данные, но не все СУБД защищают одинаково: всё ещё используйте привилегии и проверку.
- Включить аудит и логирование SQL‑запросов; мониторить аномалии.
- Использовать механизм блокировки/фильтрации на уровне БД (Если доступно: SQL Firewall, database activity monitoring).
- Ограничения на уровне БД (CHECK, FOREIGN KEY) для целостности данных — снижают полезность некоторых инъекций.
- Обновления и патчи СУБД, защита доступа (SSL/TLS, сетевые ACL), смена учётных данных и ограничение доступа по IP.
Короткий практический чек‑лист
- Никогда не вставляйте сырые данные в текст SQL.
- Используйте подготовленные выражения/параметры во всех запросах.
- Белый список для имен таблиц/полей и для структурных изменений запроса.
- Минимальные привилегии у DB‑пользователя приложения.
- Логирование/аудит и WAF/DB firewall по возможности.
Если нужно, могу привести конкретный исправленный пример для вашей технологии (PHP/Java/Python/Node.js) и показать разницу vuln → safe.