Защищают ли подготовленные выражения/переменные полностью от SQL инъекций?

Защищают ли подготовленные выражения/переменные полностью от SQL инъекций?

@phpproglib

Что такое sql-инъекции и как они происходят?

$pdo->query("select * from users where login = '" . $login ."' 
    and pass_md5 = '" . md5($pass) . "'");

Что здесь видит разработчик? Подстановку данных в строку. С точки зрения PHP здесь ничего опасного нет. С точки зрения неопытного PHP-разработчика, проверяющего свой код - код работает, ему ведь не пришло в голову написать что-то странное в логин.

Приключения начинаются, когда кто-то вместо логина вводит admin' or '1'='1. Здесь необходимо напомнить, что SQL - штука изначально текстовая. Что после конкатенации на PHP получит СУБД?

select * from users where login = 'admin' or '1'='1' 
    and pass_md5 = 'какой-тоmd5'

Это одна строка непрерывного текста. Как СУБД должна понять, что этот запрос отличается от задуманного? Это запрос, он синтаксически корректен, его можно выполнить - СУБД его и выполняет. Но запрос уже делает не то, что хотел сказать разработчик.

Опасность и распространённость SQL-инъекции именно в текстовой сущности запроса. Очень просто подставить в нужное место переменную с данными - но это путь к ошибке и так делать нельзя.

Защищают ли подготовленные выражения от sql-инъекций?

Если говорить о числовых и строковых литералах в запросе - то да, защищают.

Этого достаточно и ничего больше не надо делать?

В общем случае - ничего. В случае с ПДО также желательно еще выставлять кодировку соединения в DSN, но это в любом случае нужно делать.

Идея подготовленных выражений не в том, чтобы отправить данные и запрос отдельно, а в том, чтобы добавлением данных в запрос занимался не программист, а драйвер БД. А уж как оно у него там внутри реализовано - дело десятое.

Примечание

Должен добавить, что существует теоретическая возможность так специально настроить свою систему, чтобы она пропускала инъекции в режиме эмуляции. Для этого потребуется две вещи:

  • специальным образом настроить mysql, задав режим NO_BACKSLASH_ESCAPES
  • использовать двойные кавычки вместо одинарных в качестве ограничителей строк

Это был баг в mysql, который был исправлен только недавно. Подробнее можно почитать в этом посте на SO.

Ограничения

Куда интереснее здесь вопрос, что делать, когда подготовленные выражения использовать невозможно. Как говорилось выше, параметры в запросе можно использовать дла замены только строковых или числовых литералов. Но бывают случаи, когда в запрос надо подставить не данные, а имя столбца. Вот пример такой ситуации с разбором неправильных решений (по-английски): An SQL injection against which prepared statements won't help.

Ситуация нечастая, но о ней надо знать и быть к ней готовым.

Удобство

В конце концов, подготовленные выражения просто удобнее. Сравним олд скул

$name = $mysqli->real_escape_sring($_GET['name']);
$price = $mysqli->real_escape_sring($_GET['price']);
$color = $mysqli->real_escape_sring($_GET['color']);

$sql = "SELECT * FROM goods WHERE name='$name' and color='$color' and price > '$price'";
$res = $mysqli->query($sql);

и PDO prepared statements

$stmt = $pdo->prepare("SELECT * FROM goods WHERE name = ? and color = ? and price > ?");
$stmt->execute([$_GET['name'],$_GET['price'],$_GET['color']]);

-- компактно, аккуратно и безопасно.

Мифы про экранирование

Отдельное замечание по поводу "конструкций вида mysqli_real_escape_string". В том-то и штука, что в отличие от подготовленных выражений, эти конструкции никакого отношения к защите от SQL инъекций не имеют. Эта конструкция выполняет строго определенную и очень специализированную синтаксическую функцию. применение же её "для защиты от инъекций" гарантированно к такой инъекции и приведет.

Пример такого нецелевого использования приведен по ссылке выше, но можно привести и другой, совсем уж дурацкий но от этого еще более наглядный:

$id = $mysqli->real_escape_string($_GET['id']);
$sql = "SELECT * FROM table WHERE id=$id";

Если думать, что функция служит "для защиты от инъекций", то этот код логичен. Однако в реальности он дает возможность приписать дальше практически любой запрос через UNION и получить классическую инъекцию.

При этом не надо ударяться и в другую крайность - примененная по назначению, для экранирования спецсимволов в строках, mysqli_real_escape_string прекрасно справляется с инъекциями, просто в качестве побочного эффекта.

Report Page