Хранимые процедуры – панацея от SQL-инъекций?

У многих разработчиков сложилось мнение, что хранимые процедуры могут спасти от любой SQL-инъекции. Напомню, что хранимые процедуры (stored procedures, SP), это функции, написанные на языке SQL, с помощью которых выполнются заранее подготовленные запросы к базе данных. Хранимая процедура, как и функция в любом другом языке программирования, имеет входные и возвращаемые данные, позволяет оперировать переменными и, самое главное, облегчает жизнь разработчику, избавляя его от однотипных рутинных работ. Немаловажным является тот факт, что хранимые процедуры позволяют динамически составлять запросы, при этом входящие в хранимую процедуру данные поступают как параметры, обладающие определенным типом и длиной, что исключает возможность SQL-инъекций. Тем не менее внедрить SQL-код можно! Рассмотрим, при каких обстоятельствах возможны SQL-инъекции в хранимых процедурах на примере MySQL.

Прежде всего необходимо разобраться, как составляется простейшая хранимая процедура. Все действия будем проводить в стандартном клиенте MySQL (версия сервера 5.0.20-nt). Итак, имеется следующая процедура:

delimiter //
USE shop_db//
DROP PROCEDURE IF EXISTS get_products//
CREATE PROCEDURE get_products(IN name CHAR(128))
BEGIN
SET @s = CONCAT('SELECT description, price, date FROM products WHERE product_name = \'' , 
              name, 
              '\'');
PREPARE stmt FROM @s;
EXECUTE stmt;
END;
//
delimiter ;

Сперва назначаем // в качестве символа отделения запросов друг от друга для того, чтобы использовать стандартный символ ; внутри процедуры. Далее выбираем базу данных, инициализируем процедуру get_products с входящим параметром name, составляем запрос и записываем его в локальную переменную. Перед выполнением конечное выражение необходимо подготовить, используя оператор PREPARE. Затем выполняем подготовленное выражение и устанавливаем delimiter в перовначальное значение.

Пример работы хранимой процедуры можно посмотреть на скриншоте ниже:

В чем же заключается уязвимость данного кода? Все очень просто: переменная name напрямую передается от пользователя в процедуру и попадает в конечный запрос путем конкатенации с локальной переменной @s; далее запрос подготавливается и успешно выполняется. Вся проблема лежит именно здесь: данные, поступающие со стороны пользователя, стали частью единого запроса. В итоге, мы имеем дело с обычной SQL-инъекцией:

Приведенный выше код аналогичен примеру из этой статьи, размещенной на официальном ресурсе MySQL. Что же, выходит, и на официальных сайтах разработчиков можно получить далеко нелучшую с точки зрения безопасности информацию. Правильный вариант всегда следует искать в документации. Посмотрим, как же на самом деле необходимо составлять безопасные процедуры.

Внутри SQL-выражений необходимо использовать метки, обозначаемые как вопросительный знак, которые показывают в каких местах нужно заменить метку на значение параметра. Далее при вызове оператора EXECUTE необходимо указать какие переменные нужно подставить, используя конструкцию USING. При этом любые данные, попавшие в запрос, будут обрабатываться в соответствии с типом, но не как SQL-код. Вот безопасный вариант процедуры из примера выше:

delimiter //
USE shop_db//
DROP PROCEDURE IF EXISTS get_products//
CREATE PROCEDURE get_products(IN name CHAR(64))
BEGIN
SET @s = 'SELECT description, price, date FROM products WHERE product_name = ?';
PREPARE stmt FROM @s;
EXECUTE stmt USING @name;
END;
//
delimiter ;

Теперь попробуем внедрить SQL-команды:

Судя по реакции MySQL наша процедура работает правильно, угроза SQL-инъекции устранена.

К слову, данная особенность хранимых процедур характерна не только для MySQL, но и других DBMS, в частности MSSQL и Oracle.

Ссылки:


No comments yet.

Write a comment: