Дан фрагмент SQL-запроса, уязвимый к SQL-инъекции: "SELECT * FROM users WHERE login = '" + login + "';" — опишите различные способы эксплуатации этой уязвимости и как надёжно её устранить в разных СУБД и ORM
Коротко: уязвимость возникает из-за конкатенации строки запроса с небезопасным вводом. Это позволяет атакующему внедрить произвольную SQL-инструкцию. Ниже — основные способы эксплуатации (с кратким примером/эффектом) и надёжные способы устранения в разных СУБД и ORM. Способы эксплуатации (классы атак) - Тавтология (auth bypass). Вставка логического выражения, которое всегда истинно, например строка-поломка типа `' OR '1'='1'`. Приводит к выборке всех строк (авторизация обходится). Здесь числовое выражение: ‘1=1‘`1=1`‘1=1‘. - UNION-based (добыча данных). Добавление `UNION SELECT ...` для вытягивания данных из других таблиц; требует совпадающего числа/типов столбцов. - Error-based (инфо через ошибки). Провокация ошибок СУБД, возвращающих содержимое (инфо-утечка). - Boolean blind (побитовый/логический). Меняя условие и наблюдая ответы (true/false), извлекают данные побитово/построчно. - Time-based blind. Вставка тяжёлых выражений типа `IF(condition, SLEEP(5), 0)` и замер времени ответа для определения истинности условия. - Stacked queries (многоинструментные запросы). Добавление `; DROP TABLE users;` и т.п. (работает в СУБД/драйверах, поддерживающих несколько выражений в одном запросе). - Out-of-band (OOB). Использование функций, вызывающих внешние вызовы (например, DNS) для передачи данных на контролируемые серверы. - Second-order injection. Ввод сохраняется в БД, затем используется в другом контексте и выполнен позже. Особенности по СУБД - MySQL: часто поддерживает множественные выражения при включённом параметре `multiStatements`; `SLEEP()` для time-based; `UNION`/error-based работают. Рекомендуется отключать multiStatements в драйвере. - PostgreSQL: чаще работает через подготовленные выражения; не все драйверы поддерживают stacked queries; есть `pg_sleep()` для time-based; мощные error- и boolean-подходы. - MSSQL: поддерживает stacked queries; есть расширения типа `xp_cmdshell` (если включены) — риск привилегированного выполнения команд. - SQLite: обычно single-statement; но UNION/boolean/time-based всё ещё применимы. Профиль риска зависит от настроек драйвера, прав учётной записи и включённых расширений. Как надёжно устранить (общие принципы) 1. Параметризованные запросы / подготовленные выражения (bind parameters) — основной и рекомендуемый метод. 2. ORM-safe API: использовать методы, которые принимают параметры, а не строковую интерполяцию. 3. Ограничение прав БД (least privilege): аккаунт приложения только с нужными правами. 4. Отключение многовыраженных запросов в драйвере (если не нужно). 5. Валидация/white‑list входных данных (особенно для идентификаторов, имён таблиц, чисел). 6. Экранирование как крайняя мера; для значений предпочтительнее bind-параметры. 7. Логирование, мониторинг, WAF для дополнительной защиты и обнаружения. Примеры безопасной реализации (коротко) - PHP PDO (рекомендуемый): - Небезопасно: `"...WHERE login = '$login'"`. - Безопасно: - `$stmt = $pdo->prepare("SELECT * FROM users WHERE login = ?");` - `$stmt->execute([$login]);` - Java (JDBC): - `PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE login = ?");` - `ps.setString(1, login);` - Python (psycopg2 / SQLAlchemy): - psycopg2: `cur.execute("SELECT * FROM users WHERE login = %s", (login,))` - SQLAlchemy ORM: `session.query(User).filter(User.login == login).all()` - SQLAlchemy Core: `select(...).where(users.c.login == bindparam('login'))` - C# (ADO.NET): - `var cmd = new SqlCommand("SELECT * FROM users WHERE login = @login", conn);` - `cmd.Parameters.AddWithValue("@login", login);` ORM‑конкретика - Rails (ActiveRecord): безопасно `User.where(login: login)`; опасно `"User.where(\"login = '#{login}'\")"`. - Django ORM: безопасно `User.objects.filter(login=login)`; при raw — использовать параметры: `raw("SELECT * FROM users WHERE login = %s", [login])`. - Hibernate/JPA: использовать именованные параметры: `query.setParameter("login", login)`. - Entity Framework: LINQ/parameterized queries; избегать `SqlQuery` с конкатенацией. - SQLAlchemy: всегда пользоваться bind-параметрами или ORM-фильтрами; избегать `text()` с конкатенацией. Дополнительные рекомендации - Для LIKE: экранировать `%` и `_` и использовать bind-параметр (пример: `WHERE name LIKE ? ESCAPE '\'`). - Для динамических идентификаторов (имена таблиц/колонок): не принимать их напрямую от пользователя; если нужно — белый список. - Для ограничений привилегий: запретить DROP/ALTER/EXECUTE для учётной записи приложения. - Тестирование: внедрять автоматические тесты и сканирование на SQLi (SAST/DAST), проводить ревью запросов. Краткий вывод: перестать конкатенировать строки запроса с пользовательским вводом и перейти на параметризированные запросы/ORM-safe API — это надёжное и переносимое решение для всех СУБД. Дополнительно — минимальные права, валидация, отключение multi-statement и мониторинг.
Способы эксплуатации (классы атак)
- Тавтология (auth bypass). Вставка логического выражения, которое всегда истинно, например строка-поломка типа `' OR '1'='1'`. Приводит к выборке всех строк (авторизация обходится). Здесь числовое выражение: ‘1=1‘`1=1`‘1=1‘.
- UNION-based (добыча данных). Добавление `UNION SELECT ...` для вытягивания данных из других таблиц; требует совпадающего числа/типов столбцов.
- Error-based (инфо через ошибки). Провокация ошибок СУБД, возвращающих содержимое (инфо-утечка).
- Boolean blind (побитовый/логический). Меняя условие и наблюдая ответы (true/false), извлекают данные побитово/построчно.
- Time-based blind. Вставка тяжёлых выражений типа `IF(condition, SLEEP(5), 0)` и замер времени ответа для определения истинности условия.
- Stacked queries (многоинструментные запросы). Добавление `; DROP TABLE users;` и т.п. (работает в СУБД/драйверах, поддерживающих несколько выражений в одном запросе).
- Out-of-band (OOB). Использование функций, вызывающих внешние вызовы (например, DNS) для передачи данных на контролируемые серверы.
- Second-order injection. Ввод сохраняется в БД, затем используется в другом контексте и выполнен позже.
Особенности по СУБД
- MySQL: часто поддерживает множественные выражения при включённом параметре `multiStatements`; `SLEEP()` для time-based; `UNION`/error-based работают. Рекомендуется отключать multiStatements в драйвере.
- PostgreSQL: чаще работает через подготовленные выражения; не все драйверы поддерживают stacked queries; есть `pg_sleep()` для time-based; мощные error- и boolean-подходы.
- MSSQL: поддерживает stacked queries; есть расширения типа `xp_cmdshell` (если включены) — риск привилегированного выполнения команд.
- SQLite: обычно single-statement; но UNION/boolean/time-based всё ещё применимы.
Профиль риска зависит от настроек драйвера, прав учётной записи и включённых расширений.
Как надёжно устранить (общие принципы)
1. Параметризованные запросы / подготовленные выражения (bind parameters) — основной и рекомендуемый метод.
2. ORM-safe API: использовать методы, которые принимают параметры, а не строковую интерполяцию.
3. Ограничение прав БД (least privilege): аккаунт приложения только с нужными правами.
4. Отключение многовыраженных запросов в драйвере (если не нужно).
5. Валидация/white‑list входных данных (особенно для идентификаторов, имён таблиц, чисел).
6. Экранирование как крайняя мера; для значений предпочтительнее bind-параметры.
7. Логирование, мониторинг, WAF для дополнительной защиты и обнаружения.
Примеры безопасной реализации (коротко)
- PHP PDO (рекомендуемый):
- Небезопасно: `"...WHERE login = '$login'"`.
- Безопасно:
- `$stmt = $pdo->prepare("SELECT * FROM users WHERE login = ?");`
- `$stmt->execute([$login]);`
- Java (JDBC):
- `PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE login = ?");`
- `ps.setString(1, login);`
- Python (psycopg2 / SQLAlchemy):
- psycopg2: `cur.execute("SELECT * FROM users WHERE login = %s", (login,))`
- SQLAlchemy ORM: `session.query(User).filter(User.login == login).all()`
- SQLAlchemy Core: `select(...).where(users.c.login == bindparam('login'))`
- C# (ADO.NET):
- `var cmd = new SqlCommand("SELECT * FROM users WHERE login = @login", conn);`
- `cmd.Parameters.AddWithValue("@login", login);`
ORM‑конкретика
- Rails (ActiveRecord): безопасно `User.where(login: login)`; опасно `"User.where(\"login = '#{login}'\")"`.
- Django ORM: безопасно `User.objects.filter(login=login)`; при raw — использовать параметры: `raw("SELECT * FROM users WHERE login = %s", [login])`.
- Hibernate/JPA: использовать именованные параметры: `query.setParameter("login", login)`.
- Entity Framework: LINQ/parameterized queries; избегать `SqlQuery` с конкатенацией.
- SQLAlchemy: всегда пользоваться bind-параметрами или ORM-фильтрами; избегать `text()` с конкатенацией.
Дополнительные рекомендации
- Для LIKE: экранировать `%` и `_` и использовать bind-параметр (пример: `WHERE name LIKE ? ESCAPE '\'`).
- Для динамических идентификаторов (имена таблиц/колонок): не принимать их напрямую от пользователя; если нужно — белый список.
- Для ограничений привилегий: запретить DROP/ALTER/EXECUTE для учётной записи приложения.
- Тестирование: внедрять автоматические тесты и сканирование на SQLi (SAST/DAST), проводить ревью запросов.
Краткий вывод: перестать конкатенировать строки запроса с пользовательским вводом и перейти на параметризированные запросы/ORM-safe API — это надёжное и переносимое решение для всех СУБД. Дополнительно — минимальные права, валидация, отключение multi-statement и мониторинг.