Проанализируйте уязвимость SQL-инъекции на примере PHP-кода: $query = "SELECT * FROM users WHERE name = '$name'"; — как правильно защититься при помощи подготовленных выражений, ORM и иных практик?
Кратко: уязвимость в строке $query = "SELECT * FROM users WHERE name = '$name'"; возникает потому, что пользовательский ввод попадает в SQL без разделения кода и данных — злоумышленник может подставить нагрузку вроде "' OR '1'='1" (т.е. ′1′=′1′'1'='1'′1′=′1′) и получить нежелательный результат (например, все строки). Как правильно защититься — практики и примеры. 1) Подготовленные выражения (PDO) - Преимущество: драйвер отделяет код от данных, параметры передаются безопасно. Пример: $pdo = new PDO(...); $stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name"); $stmt->execute([':name' => $name]); $rows = $stmt->fetchAll(); альтернатива с позиционным параметром: $stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?"); $stmt->execute([$name]); Если нужен явный тип: $stmt->bindValue(':name', $name, PDO::PARAM_STR); 2) Подготовленные выражения (mysqli) Пример: $mysqli = new mysqli(...); $stmt = $mysqli->prepare("SELECT * FROM users WHERE name = ?"); $stmt->bind_param("s", $name); // "s" — строка $stmt->execute(); $result = $stmt->get_result(); 3) ORM / Query Builder - ORM автоматически параметризует запросы (если используется корректно), что уменьшает риск SQLi. Laravel (Eloquent / Query Builder): $users = DB::table('users')->where('name', $name)->get(); или $users = User::where('name', $name)->get(); Doctrine QueryBuilder: $qb->select('u')->from('User','u')->where('u.name = :name')->setParameter('name',$name); 4) Особенности и дополнительные меры - Никогда не формируйте SQL конкатенацией с невалидированными входными данными. - Не пытайтесь «экранировать» ввод вручную как основную защиту (addslashes, некорректное mysql_real_escape_string) — используйте подготовленные выражения. - Для числовых идентификаторов приводите к типу: $id = (int)$_GET['id']; (явное приведение типов уменьшает риск). - Для динамических идентификаторов (имена таблиц/столбцов) используйте белые списки (allowlist), т. е. проверяйте, что значение находится в допустимом наборе, т.к. параметры не могут подставлять идентификаторы. - Для IN(...) — формируйте нужное число плейсхолдеров: $in = implode(',', array_fill(0, count($ids), '?')); $stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($in)"); $stmt->execute($ids); 5) Операционные и политики безопасности - Используйте учетную запись БД с минимумом прав (least privilege). - Логируйте подозрительные запросы и включите мониторинг/IDS/WAF. - Не показывайте пользователям подробные SQL-ошибки (скрывайте стектрейсы). - Храните чувствительные данные (пароли) в виде хэшей (bcrypt/argon2), а не в открытом виде. Вывод: главный принцип — никогда не вставлять необработанный ввод прямо в SQL. Используйте подготовленные выражения (PDO/mysqli), ORM или тщательно реализованные белые списки для динамических частей запроса, а также дополнительные практики безопасности (приведение типов, минимальные привилегии, логирование).
$query = "SELECT * FROM users WHERE name = '$name'";
возникает потому, что пользовательский ввод попадает в SQL без разделения кода и данных — злоумышленник может подставить нагрузку вроде "' OR '1'='1" (т.е. ′1′=′1′'1'='1'′1′=′1′) и получить нежелательный результат (например, все строки).
Как правильно защититься — практики и примеры.
1) Подготовленные выражения (PDO)
- Преимущество: драйвер отделяет код от данных, параметры передаются безопасно.
Пример:
$pdo = new PDO(...);
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute([':name' => $name]);
$rows = $stmt->fetchAll();
альтернатива с позиционным параметром:
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$name]);
Если нужен явный тип:
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
2) Подготовленные выражения (mysqli)
Пример:
$mysqli = new mysqli(...);
$stmt = $mysqli->prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $name); // "s" — строка
$stmt->execute();
$result = $stmt->get_result();
3) ORM / Query Builder
- ORM автоматически параметризует запросы (если используется корректно), что уменьшает риск SQLi.
Laravel (Eloquent / Query Builder):
$users = DB::table('users')->where('name', $name)->get();
или
$users = User::where('name', $name)->get();
Doctrine QueryBuilder:
$qb->select('u')->from('User','u')->where('u.name = :name')->setParameter('name',$name);
4) Особенности и дополнительные меры
- Никогда не формируйте SQL конкатенацией с невалидированными входными данными.
- Не пытайтесь «экранировать» ввод вручную как основную защиту (addslashes, некорректное mysql_real_escape_string) — используйте подготовленные выражения.
- Для числовых идентификаторов приводите к типу: $id = (int)$_GET['id']; (явное приведение типов уменьшает риск).
- Для динамических идентификаторов (имена таблиц/столбцов) используйте белые списки (allowlist), т. е. проверяйте, что значение находится в допустимом наборе, т.к. параметры не могут подставлять идентификаторы.
- Для IN(...) — формируйте нужное число плейсхолдеров:
$in = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($in)");
$stmt->execute($ids);
5) Операционные и политики безопасности
- Используйте учетную запись БД с минимумом прав (least privilege).
- Логируйте подозрительные запросы и включите мониторинг/IDS/WAF.
- Не показывайте пользователям подробные SQL-ошибки (скрывайте стектрейсы).
- Храните чувствительные данные (пароли) в виде хэшей (bcrypt/argon2), а не в открытом виде.
Вывод: главный принцип — никогда не вставлять необработанный ввод прямо в SQL. Используйте подготовленные выражения (PDO/mysqli), ORM или тщательно реализованные белые списки для динамических частей запроса, а также дополнительные практики безопасности (приведение типов, минимальные привилегии, логирование).