Приведите пример уязвимого кода на PHP с использованием mysqli, демонстрирующий SQL‑инъекцию, и опишите пошагово, какие меры на уровне кода, библиотек и архитектуры (prepared statements, ORM, валидация, разделение прав) нужно принять для устранения угрозы
Уязвимый пример (mysqli, процедурный стиль). Код берёт вход и подставляет его в SQL строку — риск SQL‑инъекции: ```php <?php $mysqli = mysqli_connect('db.example', 'user', 'pass', 'app'); $username = $_GET['username']; // небезопасный вход $sql = "SELECT id, email FROM users WHERE username = '$username'"; // уязвимо $res = mysqli_query($mysqli, $sql); while ($row = mysqli_fetch_assoc($res)) { echo htmlspecialchars($row['email']); } ?>
``` Почему это уязвимо: переменная \($username\) вставляется в SQL как строка; если в неё попадёт специально crafted ввод, можно изменить структуру запроса и получить/изменить данные. Шаги и меры для устранения угрозы Кодовый уровень - Использовать подготовленные выражения (prepared statements). Пример с mysqli (процедурный): ```php <?php $mysqli = mysqli_connect('db.example', 'user', 'pass', 'app'); $username = $_GET['username']; $stmt = mysqli_prepare($mysqli, "SELECT id, email FROM users WHERE username = ?"); mysqli_stmt_bind_param($stmt, 's', $username); mysqli_stmt_execute($stmt); $res = mysqli_stmt_get_result($stmt); while ($row = mysqli_fetch_assoc($res)) { echo htmlspecialchars($row['email'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } mysqli_stmt_close($stmt); ?>
``` - Или PDO (рекомендуется) с отключением эмуляции подготовленных выражений: ```php $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $stmt = $pdo->prepare("SELECT id, email FROM users WHERE username = ?"); $stmt->execute([$username]); ``` Библиотеки и ORM - Используйте ORM/QueryBuilder (например, Eloquent, Doctrine, DBAL). Они по умолчанию используют параметризацию и уменьшают ручную строковую конкатенацию. - При использовании библиотек проверяйте настройки (например, отключать эмуляцию prepared statements в PDO: `PDO::ATTR_EMULATE_PREPARES => false`). Валидация и санитизация - Применяйте белые списки для структурированных данных (например, если ожидается число — явно приводите к типу). - Для строк — проверяйте длину и допустимые символы; не пытайтесь «лечить» SQL через экранирование вместо параметризации. - Экранируйте вывод в HTML (например, `htmlspecialchars`) чтобы предотвратить XSS при отображении данных. Архитектурные меры и права - Принцип наименьших привилегий: учетная запись БД для приложения должна иметь только необходимые права (например, только `SELECT/INSERT/UPDATE` для соответствующих операций, без `DROP/ALTER`). - Разделение ролей: отдельные пользователи/подключения для операций чтения и записи, админские аккаунты для администрирования. - Ограничьте доступ к БД по сети (внутренние сети, VPN), используйте firewall/DB ACL. - Используйте WAF (web application firewall) как дополнительный слой защиты и систему логирования/alerting для аномалий. - Храните чувствительные секреты (пароли, DSN) вне исходного кода, в менеджере секретов/конфигурации окружения. Операционные и защитные практики - Не показывайте подробные SQL‑ошибки пользователю; логируйте их безопасно. - Принудительная кодировка/charset: `mysqli_set_charset($mysqli, 'utf8mb4')`. - Периодические ревью кода и тесты безопасности (SAST, DAST, pentest). - Для динамических идентификаторов (имена таблиц/столбцов) используйте белые списки, а не подстановку строк. Краткая поэтапная инструкция (порядок действий) 111 — заменить все конкатенации SQL на подготовленные выражения; 222 — пройти кодовый аудит на остальные места с динамическим SQL; 333 — ввести валидацию входных данных и экранирование вывода; 444 — ограничить права БД и разделить роли; 555 — включить логирование и мониторинг; 666 — задействовать ORM/QueryBuilder и WAF как дополнительные слои защиты. Если нужно, могу привести другие примеры безопасной/небезопасной практики (PDO, ORM, динамические идентификаторы) или чеклист для аудита.
```php
<?php
$mysqli = mysqli_connect('db.example', 'user', 'pass', 'app');
$username = $_GET['username']; // небезопасный вход
$sql = "SELECT id, email FROM users WHERE username = '$username'"; // уязвимо
$res = mysqli_query($mysqli, $sql);
while ($row = mysqli_fetch_assoc($res)) {
echo htmlspecialchars($row['email']);
}
?> ```
Почему это уязвимо: переменная \($username\) вставляется в SQL как строка; если в неё попадёт специально crafted ввод, можно изменить структуру запроса и получить/изменить данные.
Шаги и меры для устранения угрозы
Кодовый уровень
- Использовать подготовленные выражения (prepared statements). Пример с mysqli (процедурный):
```php
<?php
$mysqli = mysqli_connect('db.example', 'user', 'pass', 'app');
$username = $_GET['username'];
$stmt = mysqli_prepare($mysqli, "SELECT id, email FROM users WHERE username = ?");
mysqli_stmt_bind_param($stmt, 's', $username);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($res)) {
echo htmlspecialchars($row['email'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
mysqli_stmt_close($stmt);
?> ```
- Или PDO (рекомендуется) с отключением эмуляции подготовленных выражений:
```php
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $pdo->prepare("SELECT id, email FROM users WHERE username = ?");
$stmt->execute([$username]);
```
Библиотеки и ORM
- Используйте ORM/QueryBuilder (например, Eloquent, Doctrine, DBAL). Они по умолчанию используют параметризацию и уменьшают ручную строковую конкатенацию.
- При использовании библиотек проверяйте настройки (например, отключать эмуляцию prepared statements в PDO: `PDO::ATTR_EMULATE_PREPARES => false`).
Валидация и санитизация
- Применяйте белые списки для структурированных данных (например, если ожидается число — явно приводите к типу).
- Для строк — проверяйте длину и допустимые символы; не пытайтесь «лечить» SQL через экранирование вместо параметризации.
- Экранируйте вывод в HTML (например, `htmlspecialchars`) чтобы предотвратить XSS при отображении данных.
Архитектурные меры и права
- Принцип наименьших привилегий: учетная запись БД для приложения должна иметь только необходимые права (например, только `SELECT/INSERT/UPDATE` для соответствующих операций, без `DROP/ALTER`).
- Разделение ролей: отдельные пользователи/подключения для операций чтения и записи, админские аккаунты для администрирования.
- Ограничьте доступ к БД по сети (внутренние сети, VPN), используйте firewall/DB ACL.
- Используйте WAF (web application firewall) как дополнительный слой защиты и систему логирования/alerting для аномалий.
- Храните чувствительные секреты (пароли, DSN) вне исходного кода, в менеджере секретов/конфигурации окружения.
Операционные и защитные практики
- Не показывайте подробные SQL‑ошибки пользователю; логируйте их безопасно.
- Принудительная кодировка/charset: `mysqli_set_charset($mysqli, 'utf8mb4')`.
- Периодические ревью кода и тесты безопасности (SAST, DAST, pentest).
- Для динамических идентификаторов (имена таблиц/столбцов) используйте белые списки, а не подстановку строк.
Краткая поэтапная инструкция (порядок действий)
111 — заменить все конкатенации SQL на подготовленные выражения;
222 — пройти кодовый аудит на остальные места с динамическим SQL;
333 — ввести валидацию входных данных и экранирование вывода;
444 — ограничить права БД и разделить роли;
555 — включить логирование и мониторинг;
666 — задействовать ORM/QueryBuilder и WAF как дополнительные слои защиты.
Если нужно, могу привести другие примеры безопасной/небезопасной практики (PDO, ORM, динамические идентификаторы) или чеклист для аудита.