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);
?>
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'";
$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', trusted=100, admin='yes
для получения дополнительных привилегий. При выполнении запросы переплетаются:
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' 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);
?>
sa
и сервис MSSQL запущен с необходимыми привилегиями, то выполнив
приведенные выше действия, взломщик получит аккаунт для доступа к серверу.
Замечание:
Некоторые приведенные в этой главе примеры касаются конкретной базы данных. Это не означает, что аналогичные атаки на другие программные продукты невозможны. Работоспособность вашей базы данных может быть нарушена каким-либо другим способом.
Способы защиты
Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями о структуре базы данных чтобы провести успешную атаку, получить эту информацию зачастую очень просто. Например, если база данных является частью 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), поэтому лучше избегать их использование.
- Ни в коем случае не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
- Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо на уровне базы данных, если она это поддерживает. Очевидно, что логирование не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится. Причем, в большинстве случаев полезно логировать все возможные детали.