Защита от SQL иньекций в php

mikelsv

Пишу API, требуется обезопасить его от SQL инъекций. Как лучше и проще защититься от них?

Данные приходят в формате JSON и обрабатываются функцией json_decode(); После чего:

  1. Используются в запросах как числа: "... WHERE id='{$id}' ". Требуется функция, которая проверит, что это $id целое число. Или вернет число, к примеру: $id = real_int($id); В результате должно состоять из знака минуса или символов 0-9.

  2. Используются как строки: "... '{$text}' ... ". По идее тут достаточно заменить ' на \', хотя помнится были варианты обхода такой защиты. В общем тоже нужна функция, которая надежно защитит от инъекций.

  3. Используются как список идентификаторов: "... WHERE id IN({$ids}) ". Полагаю их проще распарсить и каждый id обработать функцией из первого пункта.

1 ответ

mikelsv

Разумеется, вся работа с чистым SQL должна производиться только через плейсхолдеры. Конечно, более предпочтительным вариантом является ORM (идеальный вариант для примитивных запросов, нужных автору) либо прочие квери билдеры - в этом случае пользователь РНР не видит SQL совсем, и следовательно, инъекцию при всем желании устроить не может. Но поскольку новичков эти слова обычно пугают до смерти, то этот вариант подходит только опытным пользователям. При работе же с чистым SQL, повторюсь, любые данные должны попадать в запрос только через плейсхолдеры.

Разумеется, защита от инъекций должна производиться способом, отличным от "мы тут сейчас на жувачку прилепим пару функций, там из гуана и палок сварганим другую, потом наваяем ещё кода - и все полетит!". Защита должна быть единообразной и систематической.

Разумеется, слой работы с БД должен быть отделен от слоя работы с прикладными данными, и предоставлять удобные и безопасные методы для работы с БД, чтобы не приходилось в бизнес-логике видеть какие-то непонятные вложенные по 30 раз друг в друга array_map, implode, intval и пр. ORM здесь опять на первом месте, а сырой SQL, по-хорошему, имеет смысл использовать только для сложных запросов. Но даже и для чистого SQL DB-layer обязан предоставлять безопасные методы, основанные на плейсхолдерах.

Разумеется, в общем случае, использование PDO - это на самом деле, не лишний код, а сокращение кода. Поскольку PDO - единственный из драйверов mysql в РНР, который является высокоуровневой абстракцией, позволяющей сокращать рутинные операции.

Разумеется, "implode(',', $ids) в подготовленных запросах" НИКАК НЕ спасёт ситуацию, а лишь вернёт неверные данные.

Разумеется, все эти прекрасные рассуждения разбиваются о простой случай с WHERE id IN()... Можно, конечно, наваять кода, как предлагается по ссылке в комментариях. Но это, на самом деле, будет профанация. Нам не нужны на самом деле эти плейсхолдеры для каждого значения. Нам нужен способ тупо безопасно поместить массив в запрос, не перемешивая бизнес-логику с логикой защиты от инъекций. Для этого надо просто распространить практику работы с плейсхолдерами на несколько дополнительных типов данных. Этим и должен заниматься DB-layer. поскольку ПДО, увы, не предоставляет нужного функционала, можно воспользоваться другими библиотеками. Такими как DBSimple или Safemysql. В обоих случаях мы сможем решить все поставленные выше задачи. Вот, к примеру, для safemysql:

Нужен запрос с WHERE id=1 с проверкой на int?

$name = $db->getOne("SELECT name FROM users WHERE id=?i", $id);

Будет выброшена ошибка, если в $id - не число.

Нужен запрос со строковой переменной?

$row = $db->getRow("SELECT * FROM users WHERE email=?s", $email);

Переменная $email будет корректно отформатирована, и никакой мусор в ней не приведет к инъекции.

Нужен запрос с пресловутым WHERE id IN()? Ради бога - всего лишь указываем нужный тип:

$data = $db->getAll("SELECT * FROM users WHERE id IN(?a)", $ids);

И опять драйвер сам отформатирует массив так, что инъекция не пройдет. Но сделает это все незаметно для пользователя. В этом и состоит смысл программирования - делегировать различные задачи разным сервисам, чтобы каждый занимался своим делом. Чтобы "лишний код" был инкапсулирован в свой сервис, а не писался в одной большой куче, как это принято в похапе.

Нужно добавить в БД строку прямиком из джейсона? Нет проблем.

$json = '{"name":"John","lastname":"Doe"}';
$db->query("INSERT INTO table SET ?u", json_decode($json,true));

здесь будут правильно отформатированы для запроса не только данные, но и имена полей, про которые обычно вообще не думают, а если и думают, то форматируют все равно неправильно.

licensed under cc by-sa 3.0 with attribution.