SQL-инъекции
Многие веб-разработчики даже не догадываются, что SQL-запросы
могут быть подделаны, и считают, что SQL-запросы всегда достоверны.
На самом деле поддельные запросы могут обойти ограничения доступа,
стандартную проверку авторизации, а некоторые виды запросов могут
дать возможность выполнять команды операционной системы.
Прямое внедрение вредоносных инструкций в SQL-запросы - это
методика, в которой взломщик создаёт или изменяет текущие SQL-запросы
для отображения скрытых данных, их изменения или даже выполнения
опасных команд операционной системы на сервере базы данных.
Атака выполняется на базе приложения, строящего SQL-запросы из пользовательского
ввода и статических параметров. Следующие примеры, к сожалению, построены на
реальных фактах.
Благодаря отсутствию проверки пользовательского ввода и соединению с базой
данных под учётной записью суперпользователя (или любого другого пользователя,
наделённого соответствующими привилегиями), взломщик может создать ещё
одного пользователя БД с правами суперпользователя.
Пример #1
Постраничный вывод результата... и создание суперпользователя в PostgreSQL
<?php
$offset = $argv[0]; // внимание, нет проверки вводимых данных!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
Обычно пользователи кликают по ссылкам 'вперёд' и 'назад', вследствие чего значение
переменной
$offset заносится в
URL.
Скрипт ожидает, что
$offset - десятичное число.
Однако, взломщик может попытаться взломать систему, присоединив к
URL
дополнительную строку, обработанную функцией
urlencode():
Если это произойдёт, скрипт предоставит взломщику доступ к базе
с правами суперпользователя. Заметим, что значение
0;
использовано
для того, чтобы задать правильное смещение для первого запроса и
корректно его завершить.
Замечание:
Часто используемой техникой для игнорирования SQL-парсером
оставшейся части запроса является использование
--
, означающей комментарий.
Ещё один вероятный способ получить пароли учётных записей в БД - атака страниц,
предоставляющих поиск по базе. Взломщику нужно лишь проверить, используется
ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная.
Это может быть один из устанавливаемых на предыдущей странице фильтров,
таких как WHERE, ORDER BY, LIMIT
и OFFSET
,
используемых при построении запросов SELECT
.
В случае, если используемая вами база данных поддерживает
конструкцию UNION
, взломщик может присоединить
к оригинальному запросу ещё один дополнительный, для извлечения пользовательских
паролей. Настоятельно рекомендуем использовать только зашифрованные
пароли.
Пример #2
Листинг статей... и некоторых паролей (для любой базы данных)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
Статическая часть запроса может комбинироваться с другим
SELECT
-запросом, который выведет все пароли:
Если этот запрос (использующий
'
и
--
) присоединить к значению одной из переменных,
используемых для формирования
$query, то
запрос заметно преобразится.
Команды UPDATE также могут использоваться для атаки. Опять же, есть угроза разделения
инструкции на несколько частей и присоединения дополнительного запроса.
Также взломщик может видоизменить выражение SET
.
В этом случае потенциальному взломщику необходимо обладать некоторой дополнительной
информацией о структуре базы данных для успешного манипулирования запросами. Эту информацию можно
получить, проанализировав используемые в форме имена переменных, либо просто перебирая все
наиболее распространённые варианты названия соответствующих полей (а их не так уж и много).
Пример #3
От восстановления пароля... до получения дополнительных привилегий (для любой базы данных)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Но злоумышленник может ввести значение
' or uid like'%admin%'
для переменной
$uid для
изменения пароля администратора или просто присвоить переменной
$pwd значение
hehehe', trusted=100, admin='yes
для получения дополнительных привилегий. При выполнении запросы переплетаются:
Пугающий пример того, как на сервере баз данных могут выполняться команды
операционной системы.
Пример #4 Выполнение команд операционной системы на сервере (для базы MSSQL)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
Если взломщик введёт значение
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
для переменной
$prod, тогда запрос
$query
будет выглядеть так:
MSSQL сервер выполняет SQL-команды в пакетном режиме, в том числе
и операции по заведению локальных учётных записей базы данных. В случае,
если приложение работает с привилегиями администратора
sa
и сервис MSSQL запущен с необходимыми привилегиями, то выполнив
приведённые выше действия, взломщик получит аккаунт для доступа к серверу.
Замечание:
Некоторые приведённые в этой главе примеры касаются конкретной базы данных.
Это не означает, что аналогичные атаки на другие программные продукты невозможны.
Работоспособность вашей базы данных может быть нарушена каким-либо другим способом.
Авторство изображения принадлежит
» xkcd
Способы защиты
Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями
о структуре базы данных чтобы провести успешную атаку, получить эту информацию
зачастую очень просто. Например, если база данных является частью open-source или
другого публично доступного программного пакета с инсталляцией по умолчанию, эта информация
является полностью открытой и доступной. Эти данные также могут быть получены
из закрытого проекта, даже если он закодирован, усложнён, или скомпилирован,
и даже из вашего личного кода через отображение сообщений об ошибках.
К другим методам относится использование распространённых (легко угадываемых) названий
таблиц и столбцов. Например, форма логина, которая использует таблицу 'users' c названиями
столбцов 'id', 'username' и 'password'.
Большинство успешных атак основывается на коде, написанном без учёта соответствующих
требований безопасности. Не доверяйте никаким вводимым данным, особенно если
они поступают со стороны клиента, даже если это списки в форме, скрытые поля или куки.
Первый приведённый пример показывают, как подобные запросы
могут привести к катастрофе.
-
Никогда не соединяйтесь с базой данных, используя учётную запись
владельца базы данных или суперпользователя. Всегда старайтесь
использовать специально созданных пользователей с максимально
ограниченными правами.
-
используйте подготовленные выражения с привязанными переменными.
Эта возможность предоставляется расширениями
PDO,
MySQLi
и другими библиотеками.
-
Всегда проверяйте введённые данные на соответствие ожидаемому типу.
В PHP есть множество функций для проверки данных: начиная от простейших
функций для работы с переменными и
функций определения типа символов
(таких как is_numeric() и ctype_digit()
соответственно) и заканчивая
Perl-совместимыми регулярными выражениями.
-
В случае, если приложение ожидает цифровой ввод, примените функцию
ctype_digit() для проверки введённых данных, или
принудительно укажите их тип при помощи settype(),
или просто используйте числовое представление при помощи функции
sprintf().
Пример #5 Более безопасная реализация постраничной навигации
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// обратите внимание на формат %d, использование %s было бы бессмысленно
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
-
Если на уровне базы данных не поддерживаются привязанные переменные, то
всегда экранируйте любые нечисловые данные, используемый в запросах к БД при помощи
специальных экранирующих функций, специфичных для используемой
вами базы данных (например,
mysql_real_escape_string(),
sqlite_escape_string() и т.д.).
Общие функции такие как addslashes() полезны
только в определённых случаях (например MySQL в однобайтной кодировке
с отключённым NO_BACKSLASH_ESCAPES), поэтому лучше
избегать их использование.
-
Ни в коем случае не выводите никакой информации о БД, особенно о её структуре.
Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
-
Вы можете использовать хранимые процедуры и заранее определённые курсоры для
абстрагированной работы с данными, не предоставляя пользователям
прямого доступа к данным и представлениям, но это решение имеет свои особенности.
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо
на уровне базы данных, если она это поддерживает. Очевидно, что логирование
не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного
приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится.
Причём, в большинстве случаев полезно логировать все возможные детали.