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-инъекция

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.

Особенности реализации SQL-инъекций

Иногда злоумышленник может провести атаку, но не может видеть более одной колонки результатов. В таком случае можно объединить несколько строк в одну при помощи конкатенации:

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-инъекций

Наиболее общими способами предотвращения SQL-инъекций являются фильтрация, экранирование и проверка всех входных данных. В языке PHP для этого могут применяться такие функции, как addslashes() и mysql_real_escape_string().

addslashes() экранирует специальные символы, добавляя к ним символ \ (обратный слэш). Таким образом, символ ' заменяется на \', " — на \" и т.д. Это позволяет избежать внедрения SQL-кода только в том случае, когда экранируемая строка в запросе заключена в кавычки.

mysql_real_escape_string() работает аналогично addslashes(), но учитывает особенности диалекта MySQL, экранируя некоторые дополнительные символы. Как и addslashes(), она работает только в случае, когда экранируемая строка заключена в кавычки.

Функции, аналогичные mysql_real_escape_string(), реализованы и для других СУБД. За подробной информацией обращайтесь к руководству по PHP.

Кроме того, в языке PHP существует ряд функций, позволяющих привести значение параметра к определённому типу. Например, функции intval() и floatval() позволяют преобразовать переменную к целому числу и числу с плавающей запятой соответственно. Это делает невозможным подстановку произвольных строк в запрос к БД.

Дополнительные материалы

  1. Подборка материалов по SQL Injection
  2. SQL Injection Wiki