Найдите уязвимость в PHP-фрагменте: $id = $_GET['id']; $q = "SELECT * FROM users WHERE id = $id"; $r = mysqli_query($conn, $q); объясните, как эксплуатируется уязвимость, последствия и как правильно защититься на уровне кода и базы данных
Уязвимость: SQL‑инъекция — переменная $id \$id $id подставляется в запрос напрямую без проверки/экранирования: $q="SELECT∗FROMusersWHEREid=$id"; \$q = "SELECT * FROM users WHERE id = \$id"; $q="SELECT∗FROMusersWHEREid=$id"; Как эксплуатируется (пример): - Запросом типа GET: `?id=1 OR 1=1` (в тексте: `id=111 OR 1=11=11=1`) получим SQL: SELECT * FROM users WHERE id = 1 OR 1=1 — вернутся все строки. - UNION‑инъекция: `id=0 UNION SELECT username,password FROM admin` (в тексте: 000) — можно вытянуть другие таблицы (требует совпадения колонок). - При некоторых настройках СУБД возможны «stacked queries» (несколько команд), что позволяет выполнять DROP/UPDATE/INSERT. Последствия: - Чтение конфиденциальных данных (логины, пароли, PII). - Брутфорс/обход аутентификации (например, получение первой учетной записи). - Изменение/удаление данных, компрометация целостности. - Эскалация до выполнения произвольных команд/выполнения админских операций (в зависимости от прав DB). - Долговременная компрометация (закладки, добавление пользователей). Как защититься (уровень кода): 1. Подготовленные выражения (prepared statements) с биндингом параметров — основной и рекомендованный способ. Пример на mysqli: $stmt = $conn->prepare("SELECT * FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $id = $_GET['id']; // или (int)$_GET['id'] $stmt->execute(); $result = $stmt->get_result(); 2. Использовать PDO с параметризованными запросами: $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id"); $stmt->execute(['id' => $_GET['id']]); 3. Если параметр обязательно целый — приведение типа/в whitelist: $id = (int)$_GET['id']; // простая и быстрая защита для чисел Но приведение не заменяет prepared statements для общих случаев. 4. Не полагаться на mysqli_real_escape_string как основной метод; он пригоден для строковой экранизации, но безопаснее — prepared statements. 5. Валидировать и фильтровать вход (allowlist значений, диапазоны, шаблоны). Как защититься (уровень БД и окружения): - Минимальные привилегии для учетной записи БД (не давать DROP/CREATE/GRANT если не нужно). - Отключить выполнение нескольких запросов в одном вызове, если не требуется (не использовать mysqli::multi_query там, где это не нужно). - Включить логирование подозрительных запросов, мониторинг и оповещения. - Использовать WAF/правила IDS для блокировки очевидных инъекций. - Регулярные бэкапы и аудит привилегий. Краткий вывод: замените конкатенацию переменных на подготовленные запросы с привязкой параметров и применяйте принцип наименьших привилегий в БД — это решает проблему в подавляющем большинстве случаев.
$q="SELECT∗FROMusersWHEREid=$id"; \$q = "SELECT * FROM users WHERE id = \$id"; $q="SELECT∗FROMusersWHEREid=$id";
Как эксплуатируется (пример):
- Запросом типа GET: `?id=1 OR 1=1` (в тексте: `id=111 OR 1=11=11=1`) получим SQL:
SELECT * FROM users WHERE id = 1 OR 1=1 — вернутся все строки.
- UNION‑инъекция: `id=0 UNION SELECT username,password FROM admin` (в тексте: 000) — можно вытянуть другие таблицы (требует совпадения колонок).
- При некоторых настройках СУБД возможны «stacked queries» (несколько команд), что позволяет выполнять DROP/UPDATE/INSERT.
Последствия:
- Чтение конфиденциальных данных (логины, пароли, PII).
- Брутфорс/обход аутентификации (например, получение первой учетной записи).
- Изменение/удаление данных, компрометация целостности.
- Эскалация до выполнения произвольных команд/выполнения админских операций (в зависимости от прав DB).
- Долговременная компрометация (закладки, добавление пользователей).
Как защититься (уровень кода):
1. Подготовленные выражения (prepared statements) с биндингом параметров — основной и рекомендованный способ.
Пример на mysqli:
$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$id = $_GET['id']; // или (int)$_GET['id']
$stmt->execute();
$result = $stmt->get_result();
2. Использовать PDO с параметризованными запросами:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
3. Если параметр обязательно целый — приведение типа/в whitelist:
$id = (int)$_GET['id']; // простая и быстрая защита для чисел
Но приведение не заменяет prepared statements для общих случаев.
4. Не полагаться на mysqli_real_escape_string как основной метод; он пригоден для строковой экранизации, но безопаснее — prepared statements.
5. Валидировать и фильтровать вход (allowlist значений, диапазоны, шаблоны).
Как защититься (уровень БД и окружения):
- Минимальные привилегии для учетной записи БД (не давать DROP/CREATE/GRANT если не нужно).
- Отключить выполнение нескольких запросов в одном вызове, если не требуется (не использовать mysqli::multi_query там, где это не нужно).
- Включить логирование подозрительных запросов, мониторинг и оповещения.
- Использовать WAF/правила IDS для блокировки очевидных инъекций.
- Регулярные бэкапы и аудит привилегий.
Краткий вывод: замените конкатенацию переменных на подготовленные запросы с привязкой параметров и применяйте принцип наименьших привилегий в БД — это решает проблему в подавляющем большинстве случаев.