Для предотвращения SQL-инъекций нужно экранировать все данные в sql-запросе.
Выполнить экранирование можно разными способами, в данной статье речь пойдет об экранировании средствами php.
На самом деле все просто. Любые внешние данные внутри sql-запроса нужно экранировать с помощью фукнции mysqli::real_escape_string . В прошлой статье PHP и Mysqli. Примеры запросов мы создавали вспомогательный класс ourMysqli, который является оберткой над стандартным классом mysqli. Воспользуемся этим классом и рассмотрим следующий пример, где экранирование в sql-запросе выполняется с помощью функции real_escape_string:
<?php
// ...
// создание объекта $ourMysqli
$ourMysqli = new ourMysqli(array(
'host' => 'p:localhost',
'username' => 'dbuser',
'passwords' => 'dbpassword',
'dbname' => 'dbname',
));
// экранирование данных в SQL-запросе
$sqlQuery = 'SELECT * FROM someTable WHERE column1 LIKE "%' . $ourMysqli->real_escape_string($param1)
. '%" AND column2="' . $ourMysqli->real_escape_string($someValue) . '"';
// выполняем запрос
$resultQuery = $mysqli->query($sqlQuery);
// ...
?>
Данный способ экранирования надёжный. Единственное, что смущает – в любых запросах нужно везде использовать функцию $ourMysqli->real_escape_string() . Можно пойти дальше и настроить в классе ourMysqli автоматическое экранирование. Весь смысл в автозамене в исходном sql-запросе.
Расширим класс ourMysqli, добавим методы query() и getLastSqlQuery(). Полный код класса получится следующий:
<?php
class ourMysqli extends mysqli
{
private $lastSqlQuery;
public function __construct($connectConfig = array())
{
@parent::__construct(
$connectConfig['host'],
$connectConfig['username'],
$connectConfig['password'],
$connectConfig['dbname']
);
if ($this->connect_error) {
throw new \Exception (
$this->connect_error,
$this->connect_errno
);
}
}
public function getLastSqlQuery() {
return $this->lastSqlQuery;
}
public function query($sqlQuery) {
$params = func_get_args();
if (count($params) > 1) {
for ($i = 1; $i < count($params); $i++) {
$sqlQuery = preg_replace('/(:' . $i . '\b)/', $this->real_escape_string($params[$i]),
$sqlQuery);
}
}
$this->lastSqlQuery = $sqlQuery;
$resultQuery = parent::query($sqlQuery);
if ($resultQuery === false) {
throw new \Exception('SQL Error. Sql: ' . $sqlQuery . ' . Error: ' . $this->error);
}
return $resultQuery;
}
}
?>
Предыдущий пример выполнения sql-запроса преобразовывается в следующий вид:
<?php
$sqlQuery = 'SELECT * FROM someTable WHERE column1 LIKE "%:1%" AND column2 = ":2"';
$resultQuery = $mysqli->query($sqlQuery, $param1, $someValue);
Метод query() ищет в запросе строку :<число> и вместо неё подставляет экранированное значение соответствующего параметра. Размер php кода уменьшается, повышается читаемость и выполняется автоматическое экранирование данных в sql-запросе.
Рассмотрим код метода query() более подробно.
С помощью функции func_get_args() получаем массив входящих параметров:
- первый параметр – sql-запрос;
- второй и последующий параметры – переменные с данными для подстановки в sql-запрос.
Если передано больше одного параметра в метод query() – выполняется безопасная подстановка значений в sql-запросе. Запускается цикл обхода всех параметров функции, в цикле с помощью регулярных выражений выполняется подстановка значений в sql-запрос.
<?php
for ($i = 1; $i < count($params); $i++) {
$sqlQuery = preg_replace('/(:' . $i . '\b)/', $this->real_escape_string($params[$i]), $sqlQuery);
}
Читается регулярное выражение следующим образом. Ищем двоеточие, сразу после которого идет порядковый номер параметра, и если такое совпадение найдено – выполняем автозамену на значение параметра с автоматическим экранированием. \b – указывает на то, что после двоеточия и порядкового номера должна быть граница слова, поскольку если не указывать \b – тогда при поиске 1-го параметра будет заменяться 10 параметр (':10' => $this->real_escape_string($params[1]) . '0'), чего нужно избежать .
Ну а дальше все просто.
В $this->lastSqlQuery записывается последний реальный sql-запрос к базе данных, который можно получить с помощью метода getLastSqlQuery(). Метод $this->getLastSqlQuery() полезен для отладки sql-запроса с ошибкой - можно получить реальный запрос к БД с выполненным экранированием и выполнить анализ ошибки в sql-запросе.
Ну и в самом конце выполняется запрос в базу данных. В случае ошибки выбрасывается Exception. Если же с запросом все нормально – возвращается объект mysqli_result или TRUE.
Дата: 12.05.2016 Автор: Игорь
|
|
Авторам статьи важно Ваше мнение. Будем рады его обсудить с Вами:
comments powered by Disqus