SQL-инъекция (англ. SQL injection) — распространённая атака на веб-сайты и приложения, работающие с БД. Атака возможна в том случае, когда входные данные, получаемые от пользователя, некорректно или недостаточно фильтруются перед использованием в запросах к БД. Это позволяет внедрять во входные данные произвольный SQL-код, меняющий логику работы запроса.
В зависимости от типа используемой СУБД и особенностей уязвимого приложения, SQL-инъекция может дать возможность атакующему выполнить произвольный запрос к БД, получить возможность чтения и/или записи локальных файлов или даже выполнения произвольных команд на атакуемом сервере.
Атака может быть реализована против любой СУБД, поддерживающей SQL: MySQL, PostgreSQL, Oracle и т.д.
Далее рассмотрим ряд уязвимостей, приводящих к возможности реализации SQL-инъекции.
В случае, когда входные данные некорректно или недостаточно фильтруются, они могут содержать служебные символы и SQL-код. Вследствие этого злоумышленник может передать специально сформированную строку, которая будет подставлена в запрос к БД.
Пример уязвимого кода на PHP:
$username = $_GET['username'];
$result = mysql_query("SELECT * FROM users WHERE username = '$username'");
Этот код получает значение параметра username, переданное пользователем. Затем это значение
непосредственно используется для запроса к БД без какой-либо дополнительной обработки.
Для реализации SQL-инъекции в данном случае достаточно передать в качестве username следующую
строку: ' OR '1' = '1
. При этом выражение в условии запроса примет вид:
username = '' OR '1' = '1'
. Очевидно, что оно всегда верно, поскольку всегда верно
выражение '1' = '1'
. Таким образом, запрос вернёт все строки из таблицы users.
SELECT * FROM users WHERE username = ''
Простейшей проверкой на недостаточную фильтрацию входных параметров служит подстановке символа одинарной (реже двойной или обратной) кавычки. Если входные данные обрабатываются некорректно, то такая подстановка гарантированно приведёт к ошибке, поскольку не будет обнаружено закрывающей кавычки. Аномальным поведением считается такое, при котором страницы, получаемые до и после подстановки кавычек, различаются, если при этом не получена страница с информацией о неверном формате параметров.
Следует отметить, что приложение может получать входные данные не только из пользовательских форм, но и из идентификаторов cookie, заголовков протокола HTTP и других источников. Кроме того, параметры могут быть переданы посредством «невидимых» (type="hidden") полей в веб-формах. Для просмотра данных, передаваемых от браузера на сервер и обратно, можно воспользоваться анализатором трафика или специализированные отладочным средством (например, Firebug).
Язык SQL позволяет объединять результаты нескольких запросов при помощи оператора UNION. Это позволяют злоумышленнику получить несанкционированный доступ к данным в таблицах, не используемых в исходном запросе.
$id = $_GET['id'];
$result = mysql_query("SELECT author, title, text FROM news WHERE id = $id");
Приведённый выше код предназначен для отображения новости с заданным id из таблицы news.
Если передать в качестве id строку -1 UNION ALL SELECT username, password, NULL FROM users
,
то запрос не вернёт ни одной строки из таблицы news, поскольку строки с идентификатором -1 заведомо
не существует. При этом посредством оператора UNION будут выбраны все строки из таблицы users.
SELECT author, title, text FROM news WHERE id =
Для успешного внедрения оператора UNION количество столбцов в выборках должно совпадать. Если количество столбцов в первой (исходной) выборке больше, то вторую необходимо дополнить соответствующим количеством констант (например, NULL).
SQL-инъекция называется слепой (англ. blind SQL injection) в том случае, когда результат выполнения запроса недоступен злоумышленнику. При этом уязвимый веб-сайт по-разному реагирует на различные логические выражения, подставляемые в уязвимый параметр. Таким образом, злоумышленник может подобрать значения некоторых параметров (версия СУБД, текущее имя и права пользователя и т. д.), подставляя в запрос соответствующие логические выражения.
Рассмотрим следующий код:
$id = $_GET['id'];
$result = mysql_query("SELECT * FROM catalog WHERE id = $id");
if (mysql_num_rows($result) != 0)
// Вывод описания товара…
else
echo "Товар не найден!";
Его задача — вывести описание товара из таблицы catalog по указанному id. В случае, если
товара с заданным id не существует, будет выведено соответствующее сообшение.
Если в качестве id передать строку 1 AND 1 = 1
, то условие запроса не изменится,
поскольку выражение 1 = 1
всегда истинно. Если товар с id равным 1 существует, то
в ответ будет получена страница с его описанием. Если затем в качестве id передать строку
1 AND 1 = 2
с заведомо ложным условием, то будет получено сообщение о том, что запрошенный
товар не существует. Таким образом, можно подобрать значения некоторых параметров БД, например, условие
SUBSTR(@@version, 1, 1) = 5
будет верным только если версия СУБД равна 5.
Иногда злоумышленник может провести атаку, но не может видеть более одной колонки результатов. В таком случае можно объединить несколько строк в одну при помощи конкатенации:
SELECT CONCAT(username, ':', password) FROM users
В некоторых случаях запрос, подверженный SQL-инъекции, имеет структуру, усложняющую или препятствующую внедрению операторов SQL. Например, следующий код:
$author_id = $_GET['author_id'];
$result = mysql_query("SELECT author, title, text FROM news WHERE author = $author_id ORDER BY date DESC LIMIT 20");
выбирает строки с указанным идентификатором author_id, сортирует их по дате и выводит 20 первых
записей. Простая подстановка оператора UNION вместо author_id приведёт к ошибке из-за
оставшейся части запроса: ORDER BY date DESC LIMIT 20
. В этом случае часть запроса необходимо
экранировать при помощи символов комментария (--, /* или # в зависимости от СУБД).
Часть строки, отделённая этими символами, будет проигнорирована и запрос успешно выполнится.
При этом важно помнить, что все скобки и кавычки должны быть закрыты, чтобы не вызывать ошибок при выполнении запроса. Если часть запроса экранируется, то соответствующие символы нужно передавать в явном виде.
Для разделения команд в языке SQL используется символ ; (точка с запятой). Внедряя этот символ в запрос, злоумышленник получает возможность выполнить несколько команд в одном запросе. Это позволяет выполнять операции, отличные от применяемых в исходном запросе, например, вставлять, изменять или удалять строки.
$id = $_GET['id'];
$result = mysql_query("SELECT * FROM news WHERE id = $id");
Передав в качестве id строку -1; INSERT INTO users(username, password) VALUES ('foo', 'bar')
,
злоумышленник может несанкционированно вставить новую строку в таблицу users.
Наиболее общими способами предотвращения SQL-инъекций являются фильтрация, экранирование и проверка всех входных данных. В языке PHP для этого могут применяться такие функции, как addslashes() и mysql_real_escape_string().
addslashes() экранирует специальные символы, добавляя к ним символ \ (обратный слэш). Таким образом, символ ' заменяется на \', " — на \" и т.д. Это позволяет избежать внедрения SQL-кода только в том случае, когда экранируемая строка в запросе заключена в кавычки.
mysql_real_escape_string() работает аналогично addslashes(), но учитывает особенности диалекта MySQL, экранируя некоторые дополнительные символы. Как и addslashes(), она работает только в случае, когда экранируемая строка заключена в кавычки.
Кроме того, в языке PHP существует ряд функций, позволяющих привести значение параметра к определённому типу. Например, функции intval() и floatval() позволяют преобразовать переменную к целому числу и числу с плавающей запятой соответственно. Это делает невозможным подстановку произвольных строк в запрос к БД.