# SQL-инъекция (как защищаться)

> Уязвимость, при которой данные пользователя попадают в текст SQL-запроса как код; главная защита — параметризованные запросы, дополнительная — белые списки, ORM и минимальные права в БД.

Источник: https://llmdocs.ru/wiki/sql-injection/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Тип**: инъекция кода (CWE-89)
- **Категория OWASP**: A03:2021 Injection
- **Причина**: склейка SQL-запроса из строк с пользовательскими данными
- **Главная защита**: параметризованные (подготовленные) запросы
- **Дополнительно**: белые списки для имён, ORM, минимальные права, WAF
- **Инструменты проверки**: sqlmap, Semgrep, Bandit
**SQL-инъекция** (англ. *SQL injection*, SQLi) — уязвимость, при которой данные, пришедшие от пользователя, попадают в текст [SQL](https://llmdocs.ru/wiki/sql.md)-запроса и интерпретируются базой данных как часть команды.[2] Атакующий может прочитать чужие данные, обойти проверку пароля, изменить или удалить таблицы, а в некоторых СУБД — выполнить команды операционной системы. Инъекции десятилетиями входят в [OWASP Top 10](https://llmdocs.ru/wiki/owasp-top-10.md) (категория A03:2021).[3]

## Как возникает

Причина всегда одна — запрос собирается склейкой строк:

```python
# УЯЗВИМО
name = request.args["name"]
cur.execute(f"SELECT * FROM users WHERE name = '{name}'")
```

Если передать `name = ' OR '1'='1`, запрос станет

```sql
SELECT * FROM users WHERE name = '' OR '1'='1'
```

и вернёт всех пользователей. Значение `'; DROP TABLE users; --` в драйверах, допускающих несколько команд в одном вызове, удалит таблицу. На практике чаще встречаются более тонкие варианты:

- **UNION-инъекция** — к исходному запросу дописывается `UNION SELECT` из другой таблицы, например с хешами паролей.
- **Слепая инъекция** — результат не виден, но атакующий задаёт вопросы «да/нет» и наблюдает за разницей в ответе или временем (`pg_sleep(5)`).
- **Инъекция второго порядка** — значение сначала безопасно сохраняется в базу, а потом другой код достаёт его и склеивает в новый запрос.
- Инъекции в `ORDER BY`, имена столбцов и таблиц, `LIMIT` — там, где обычные параметры не работают.

## Главная защита: параметризованные запросы

В **параметризованном** (подготовленном, *prepared*) запросе текст SQL и данные передаются в СУБД раздельно. База сначала разбирает запрос с заполнителями, а значения подставляет как данные — они никогда не станут кодом, какие бы кавычки в них ни были.[1]

```python
# Python, psycopg (PostgreSQL)
cur.execute("SELECT * FROM users WHERE name = %s", (name,))

# sqlite3
cur.execute("SELECT * FROM users WHERE name = ?", (name,))
```

```go
// Go, database/sql
row := db.QueryRowContext(ctx, "SELECT id FROM users WHERE name = $1", name)
```

Стиль заполнителей зависит от драйвера (`?`, `%s`, `$1`, `:name` — их перечисляет PEP 249[4]), но принцип один: **никогда не форматировать SQL строковыми операциями** — ни f-строками, ни `%`, ни `+`, ни `.format()`. Важно: `%s` в psycopg — это заполнитель драйвера, а не форматирование Python; значение передаётся вторым аргументом.[5]

## Когда параметры не помогают

Заполнителем можно заменить только **значение**, но не идентификатор (имя таблицы или столбца) и не ключевое слово (`ASC`/`DESC`). Для таких мест:

- **Белый список**: сопоставить ввод с заранее известными вариантами.

```python
SORT = {"name": "name", "date": "created_at"}
col = SORT.get(request.args.get("sort"), "created_at")
order = "DESC" if request.args.get("dir") == "desc" else "ASC"
cur.execute(f"SELECT * FROM posts ORDER BY {col} {order} LIMIT %s", (limit,))
```

- Если нужен динамический идентификатор, использовать безопасный построитель драйвера, например `psycopg.sql.Identifier`, который корректно экранирует имя.

Ручное экранирование кавычек (`replace("'", "''")`) — ненадёжная и устаревшая защита: легко забыть место, ошибиться с кодировкой или контекстом.

## ORM и построители запросов

[ORM](https://llmdocs.ru/wiki/orm.md) (SQLAlchemy, Django ORM, GORM, Prisma) параметризуют запросы автоматически, поэтому обычный код через них безопасен. Уязвимости возвращаются, когда разработчик спускается к «сырому» SQL: `text(f"...")`, `.raw()`, `.extra()`, `whereRaw` со склейкой строк. Правило то же — сырые запросы только с параметрами.

## Эшелонированная защита

Параметризация устраняет причину; остальные меры уменьшают ущерб, если где-то ошибка всё же осталась:

- **Минимальные права** учётной записи приложения в БД: только нужные таблицы и операции, без `DROP`, без прав суперпользователя и доступа к файловой системе (см. [Принцип наименьших привилегий](https://llmdocs.ru/wiki/least-privilege.md)).
- Запрет нескольких команд в одном вызове на уровне драйвера.
- Скрытие подробных ошибок СУБД от пользователя — они сильно облегчают слепые инъекции.
- Валидация типов на входе (число — это число, UUID — это UUID).
- Статический анализ: Semgrep, Bandit (правило B608), CodeQL находят склейку строк в `execute`.
- Тестирование: sqlmap автоматически ищет и эксплуатирует инъекции — применять только к своим системам.[6]
- WAF отсекает типовые полезные нагрузки, но легко обходится и не заменяет исправления кода.

## Родственные уязвимости

Тот же класс ошибок — смешение кода и данных — встречается в командах ОС (shell injection), шаблонизаторах (SSTI), запросах к NoSQL-базам (операторы `$where`, `$ne` в MongoDB), LDAP и XPath. В приложениях с языковыми моделями аналогом стала [prompt injection](https://llmdocs.ru/wiki/prompt-injection.md), у которой, в отличие от SQL, нет полного решения вроде параметризации.

## См. также

- [OWASP Top 10](https://llmdocs.ru/wiki/owasp-top-10.md)
- [PostgreSQL](https://llmdocs.ru/wiki/postgresql.md)
- [XSS (как защищаться)](https://llmdocs.ru/wiki/xss.md)
- [Транзакция и ACID](https://llmdocs.ru/wiki/transaction-acid.md)

## Источники

1. [OWASP — SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
2. [MITRE CWE-89 — Improper Neutralization of Special Elements used in an SQL Command](https://cwe.mitre.org/data/definitions/89.html)
3. [OWASP Top 10:2021 — A03 Injection](https://owasp.org/Top10/A03_2021-Injection/)
4. [Python DB-API — PEP 249](https://peps.python.org/pep-0249/)
5. [psycopg — Passing parameters to SQL queries](https://www.psycopg.org/psycopg3/docs/basic/params.html)
6. [sqlmap — automatic SQL injection tool](https://sqlmap.org/)
