• База знаний
  • /
  • Блог
  • /
  • Wiki
  • /
+380 (44) 364 05 71

Для предотвращения 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
Спасибо, что выбираете FREEhost.UA