Многие веб-разработчики даже не догадываются, что SQL-запросы могут быть подделаны, и считают, что SQL-запросы всегда достоверны. На самом деле поддельные запросы могут обойти ограничения доступа, стандартную проверку авторизации, а некоторые виды запросов могут дать возможность выполнять команды операционной системы.
Прямое внедрение вредоносных инструкций в SQL-запросы - это методика, в которой взломщик создает или изменяет текущие SQL-запросы для отображения скрытых данных, их изменения или даже выполнения опасных команд операционной системы на сервере базы данных. Атака выполняется на базе приложения, строящего SQL-запросы из пользовательского ввода и статических параметров. Следующие примеры, к сожалению, построены на реальных фактах.
Благодаря отсутствию проверки пользовательского ввода и соединению с базой данных под учетной записью суперпользователя (или любого другого пользователя, наделенного соответствующими привелегиями), взломщик может создать еще одного пользователя БД с правами суперпользователя.
Пример #1 Постраничный вывод результата... и создание суперпользователя в PostgreSQL
Для выполнения последующих примеров необходимо:
1. Создать БД в СУБД PostgreSQL;
2. Создать таблицы products, pg_shadow, usertable;
3. Создать соединение с созданной БД (см. PHP соединение с 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;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
Если это произойдет, скрипт предоставит взломщику доступ к базе с правами суперпользователя. Заметим, что значение 0; использовано для того, чтобы задать правильное смещение для первого запроса и корректно его завершить.
Замечание:
Часто используемой техникой для игнорирования SQL-парсером оставшейся части запроса является использование --, означающей комментарий.
Еще один вероятный способ получить пароли учетных записей в БД - атака страниц, предоставляющих поиск по базе. Взломщику нужно лишь проверить, используется ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная. Это может быть один из устанавливаемых на предыдущей странице фильтров, таких как WHERE, ORDER BY, LIMIT и OFFSET, используемых при построении запросов SELECT. В случае, если используемая вами база данных поддерживает конструкцию UNION, взломщик может присоединить к оригинальному запросу еще один дополнительный, для извлечения пользовательских паролей. Настоятельно рекомендуем использовать только зашифрованные пароли.
Пример #2 Листинг статей... и некоторых паролей (для любой базы данных)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
?>
Статическая часть запроса может комбинироваться с другим SELECT-запросом, который выведет все пароли:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
Если этот запрос (использующий ' и --) присоединить к значению одной из переменных, используемых для формирования $query, то запрос заметно преобразится.
Команды UPDATE также могут использоваться для атаки. Опять же, есть угроза разделения инструкции на несколько частей и присоединения дополнительного запроса. Также взломщик может видоизменить выражение SET. В этом случае потенциальному взломщику необходимо обладать некоторой дополнительной информацией о структуре базы данных для успешного манипулирования запросами. Эту информацию можно получить, проанализировав используемые в форме имена переменных, либо просто перебирая все наиболее распространенные варианты названия соответствующих полей (а их не так уж и много).
Пример #3 От восстановления пароля... до получения дополнительных привилегий (для любой базы данных)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Но злоумышленник может ввести значение ' or uid like'%admin%'; -- для переменной $uid для изменения пароля администратора или просто присвоить переменной $pwd значение "hehehe', admin='yes', trusted=100 " (с завершающими пробелами) для получения дополнительных привелегий. При выполнении запросы переплетаются:
<?php
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;";
// … - пробелы
?>
Пугающий пример того, как на сервере баз данных могут выполняться команды операционной системы.
Пример #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 будет выглядеть так:
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>
MSSQL сервер выполняет SQL-команды в пакетном режиме, в том числе и операции по заведению локальных учетных записей базы данных. В случае, если приложение работает с привилегиями администратора sa и сервис MSSQL запущен с необходимыми привилегиями, то выполнив приведенные выше действия, взломщик получит аккаунт для доступа к серверу.
Замечание:
Некоторые приведенные в этой главе примеры касаются конкретной базы данных. Это не означает, что аналогичные атаки на другие программные продукты невозможны. Работоспособность вашей базы данных может быть нарушена каким-либо другим способом.
Способы защиты
Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями о структуре базы данных, чтобы провести успешную атаку, получить эту информацию зачастую очень просто. Например, если база данных является частью open-source или другого публично доступного программного пакета с инсталляцией по умолчанию, эта информация является полностью открытой и доступной. Эти данные также могут быть получены из закрытого проекта, даже если он закодирован, усложнен, или скомпилирован, и даже из вашего личного кода через отображение сообщений об ошибках. К другим методам относится использование распространенных (легко угадываемых) названий таблиц и столбцов. Например, форма логина, которая использует таблицу 'users' c названиями столбцов 'id', 'username' и 'password'.
Большинство успешных атак основывается на коде, написанном без учета соответствующих требований безопасности. Не доверяйте никаким вводимым данным, особенно если они поступают со стороны клиента, даже если это списки в форме, скрытые поля или куки. Первый приведенный пример показывает, как подобные запросы могут привести к катастрофе.
· Никогда не соединяйтесь с базой данных, используя учетную запись владельца базы данных или суперпользователя. Всегда старайтесь использовать специально созданных пользователей с максимально ограниченными правами.
· Всегда проверяйте введенные данные на соответствие ожидаемому типу. В PHP есть множество функций для проверки данных: начиная от простейших функций для работы с переменными и функций определения типа символов (таких как is_numeric() и ctype_digit() соответственно) и заканчивая Perl-совместимыми регулярными выражениями.
· В случае, если приложение ожидает цифровой ввод, примените функцию is_numeric() для проверки введенных данных, или принудительно укажите их тип при помощи 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() и str_replace() смогут оказаться полезными (в зависимости от типа базы данных). Обратите внимание на первый пример. Следует помнить, что одного использования кавычек в запросе мало, это легко обойти.
· Ни в коем случае не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
· Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо на уровне базы данных, если она это поддерживает. Очевидно, что логирование не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится. Причем, в большинстве случаев полезно логировать все возможные детали.