Проблема. Динамическая конкатенация пользовательских данных в SQL даёт риск SQL‑инъекции: злоумышленник может изменить структуру запроса (добавить `OR 1=1`, завернуть дополнительные команды, прочитать/удалить данные и т.д.). Особо опасно, когда конкатенируются идентификаторы (имена таблиц/столбцов), списки (`IN`) и числовые параметры. Рекомендации и защищённые шаблоны 1) Используйте подготовленные выражения (parameterized queries). Пример с PDO: ```php // уязвимый вариант (не делайте так): $sql = "SELECT * FROM users WHERE login = '" . $_POST['login'] . "' AND status = '" . $_GET['status'] . "'"; // безопасный вариант: $stmt = $pdo->prepare("SELECT * FROM users WHERE login = :login AND status = :status"); $stmt->execute([ ':login' => $_POST['login'], ':status' => $_GET['status'] ]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); ``` 2) Для числовых параметров — явно привязывайте тип или кастуйте: ```php $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10; $stmt = $pdo->prepare("SELECT * FROM products LIMIT :lim"); $stmt->bindValue(':lim', $limit, PDO::PARAM_INT); $stmt->execute(); ``` 3) Для списка значений в `IN` — создавайте плейсхолдеры динамически и привязывайте элементы: ```php $ids = array_map('intval', $_POST['ids']); // or validate input array $placeholders = implode(',', array_fill(0, count($ids), '?')); $sql = "SELECT * FROM items WHERE id IN ($placeholders)"; $stmt = $pdo->prepare($sql); $stmt->execute($ids); // элементы привязаны по порядку ``` 4) Для динамических идентификаторов (ORDER BY, имя колонки, имя таблицы) — используйте белый список: ```php $allowed = ['name','created_at','price']; $sort = in_array($_GET['sort'] ?? '', $allowed) ? $_GET['sort'] : 'name'; $dir = (isset($_GET['dir']) && strtoupper($_GET['dir']) === 'DESC') ? 'DESC' : 'ASC'; $sql = "SELECT * FROM products ORDER BY $sort $dir"; // безопасно, т.к. $sort и $dir из whitelist/проверки ``` 5) Общие меры безопасности - Используйте аккаунт БД с минимальными правами (least privilege). - Логи и мониторинг запросов; ограничение соединений/таймауты. - По возможности используйте ORM/Query Builder — они обычно parameterize-ят запросы. - Не полагайтесь на `mysqli_real_escape_string` как единственный способ (лучше prepared statements); escape применим только к строковым литералам и при правильной кодировке. - Тестируйте/сканируйте на уязвимости (Fuzzing, автоматические сканеры). Кратко: замените конкатенацию на подготовленные запросы для всех пользовательских значений, валидируйте/белый‑лист для идентификаторов и явно приводите числовые параметры. Это устраняет большинство рисков SQL‑инъекции.
Рекомендации и защищённые шаблоны
1) Используйте подготовленные выражения (parameterized queries). Пример с PDO:
```php
// уязвимый вариант (не делайте так):
$sql = "SELECT * FROM users WHERE login = '" . $_POST['login'] . "' AND status = '" . $_GET['status'] . "'";
// безопасный вариант:
$stmt = $pdo->prepare("SELECT * FROM users WHERE login = :login AND status = :status");
$stmt->execute([
':login' => $_POST['login'],
':status' => $_GET['status']
]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
```
2) Для числовых параметров — явно привязывайте тип или кастуйте:
```php
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
$stmt = $pdo->prepare("SELECT * FROM products LIMIT :lim");
$stmt->bindValue(':lim', $limit, PDO::PARAM_INT);
$stmt->execute();
```
3) Для списка значений в `IN` — создавайте плейсхолдеры динамически и привязывайте элементы:
```php
$ids = array_map('intval', $_POST['ids']); // or validate input array
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT * FROM items WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids); // элементы привязаны по порядку
```
4) Для динамических идентификаторов (ORDER BY, имя колонки, имя таблицы) — используйте белый список:
```php
$allowed = ['name','created_at','price'];
$sort = in_array($_GET['sort'] ?? '', $allowed) ? $_GET['sort'] : 'name';
$dir = (isset($_GET['dir']) && strtoupper($_GET['dir']) === 'DESC') ? 'DESC' : 'ASC';
$sql = "SELECT * FROM products ORDER BY $sort $dir"; // безопасно, т.к. $sort и $dir из whitelist/проверки
```
5) Общие меры безопасности
- Используйте аккаунт БД с минимальными правами (least privilege).
- Логи и мониторинг запросов; ограничение соединений/таймауты.
- По возможности используйте ORM/Query Builder — они обычно parameterize-ят запросы.
- Не полагайтесь на `mysqli_real_escape_string` как единственный способ (лучше prepared statements); escape применим только к строковым литералам и при правильной кодировке.
- Тестируйте/сканируйте на уязвимости (Fuzzing, автоматические сканеры).
Кратко: замените конкатенацию на подготовленные запросы для всех пользовательских значений, валидируйте/белый‑лист для идентификаторов и явно приводите числовые параметры. Это устраняет большинство рисков SQL‑инъекции.