SQL-инъекции
SQL-инъекция - это техника, при которой злоумышленник использует недостатки в коде приложения,
отвечающего за построение динамических SQL-запросов.
Злоумышленник может получить доступ к привилегированным разделам приложения,
получить всю информацию из базы данных, подменить существующие данные
или даже выполнить опасные команды системного уровня на узле базы данных.
Уязвимость возникает, когда разработчики конкатенируют или интерполируют
произвольный ввод в SQL-запросах.
Пример #1
Постраничный вывод результата и создание суперпользователя в PostgreSQL
В следующем примере пользовательский ввод напрямую интерполируется в SQL-запрос,
что позволяет злоумышленнику получить учётную запись суперпользователя в базе данных.
<?php
$offset = $_GET['offset']; // осторожно, нет валидации ввода!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
Обычно пользователи нажимают по ссылкам 'вперёд' и 'назад', вследствие чего значение
переменной
$offset заносится в
URL.
Скрипт ожидает, что
$offset - десятичное число.
Однако, взломщик может попытаться взломать систему,
присоединив к
URL следующее значение:
Если это произойдёт, скрипт предоставит злоумышленнику доступ суперпользователя.
Обратите внимание, что значение
0;
использовано
для того, чтобы задать правильное смещение для первого запроса и
корректно его завершить.
Замечание:
Это распространённый приём, чтобы заставить синтаксический анализатор SQL
игнорировать остальную часть запроса, написанного разработчиком
с помощью --
, который является знаком комментария в 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
-запросом, который выведет все пароли:
Выражения UPDATE
и INSERT
также подвержены таким атакам.
Пример #3
От сброса пароля до получения дополнительных привилегий (любой сервер баз данных)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Но злоумышленник может ввести значение
' or uid like'%admin%'
для переменной
$uid для
изменения пароля администратора или просто присвоить переменной
$pwd значение
hehehe', trusted=100, admin='yes
для получения дополнительных привилегий. При выполнении запросы переплетаются:
Хотя остаётся очевидным, что для проведения успешной атаки злоумышленник должен обладать
хотя бы некоторыми знаниями об архитектуре базы данных, получить эту информацию зачастую очень просто.
Например, код может быть частью программного обеспечения с открытым исходным кодом и находиться в открытом доступе.
Эта информация также может быть раскрыта закрытым кодом - даже если он закодирован,
обфусцирован или скомпилирован, и даже вашим собственным кодом через отображение сообщений об ошибках.
Другие методы включают использование типичных имён таблиц и столбцов. Например, форма входа в систему,
использующая таблицу 'users' с именами столбцов 'id', 'username' и 'password'.
Пример #4 Атака на операционную систему сервера базы данных (MSSQL Server)
Пугающий пример того, как команды уровня операционной системы могут
быть доступны на некоторых узлах баз данных.
<?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 Server выполняет SQL запросы в пакете, включая команду добавления
нового пользователя в локальную базу данных учётных записей.
Если бы это приложение было запущено от имени
sa
и служба MSSQLSERVER была запущена с достаточными привилегиями,
у злоумышленника появилась бы учётная запись, с помощью которой
он мог бы получить доступ к этой машине.
Замечание:
Некоторые примеры, приведённые выше, привязаны к конкретному серверу баз данных,
но это не означает, что подобная атака невозможна на другие продукты.
Ваш сервер баз данных может быть аналогично уязвим и другим способом.
Изображение любезно предоставлено
» xkcd
Способы защиты
Рекомендуемый способ избежать SQL-инъекций - связывание всех данных с помощью подготовленных запросов.
Использование подготовленных запросов недостаточно для полного предотвращения SQL-инъекций,
но это самый простой и безопасный способ обеспечить ввод данных в SQL-запросы.
Все динамические литералы данных в выражениях WHERE
, SET
и VALUES
должны быть заменены заполнителями. Фактические данные будут связаны во время выполнения
и отправлены отдельно от команды SQL.
Привязка параметров может использоваться только для данных.
Другие динамические части SQL-запроса должны быть отфильтрованы по известному списку допустимых значений.
Пример #5 Избегание SQL-инъекций с помощью подготовленных операторов PDO
<?php
// Динамическая часть SQL проверяется на соответствие ожидаемым значениям
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// SQL подготавливается с заполнителем
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// Значение предоставляется с подстановочными знаками LIKE
$stmt->execute(["%{$productId}%"]);
?>
Подготовленные операторы предоставляются PDO,
MySQLi,
а также другими библиотеками баз данных.
Атаки SQL-инъекций в основном основаны на использовании кода, написанного без учёта требований безопасности.
Никогда не доверяйте любому вводу, особенно со стороны клиента, даже если он поступает из поля выбора,
скрытого поля ввода или cookie. Первый пример показывает, что такой простой запрос может привести к катастрофе.
Стратегия "защита в глубину" включает в себя несколько эффективных методов написания кода:
-
Никогда не подключайтесь к базе данных как суперпользователь или владелец базы данных.
Всегда используйте настроенных пользователей с минимальными привилегиями.
-
Всегда проверяйте введённые данные на соответствие ожидаемому типу.
В PHP есть множество функций для проверки данных: начиная от простейших
функций для работы с переменными и
функций определения типа символов
(таких как is_numeric() и ctype_digit()
соответственно) и заканчивая
Perl-совместимыми регулярными выражениями.
-
В случае, если приложение ожидает цифровой ввод, примените функцию
ctype_digit() для проверки введённых данных, или
принудительно укажите их тип при помощи settype(),
или просто используйте числовое представление при помощи функции
sprintf().
-
Если на уровне базы данных не поддерживаются привязанные переменные, то
всегда экранируйте любые нечисловые данные, используемый в запросах к БД при помощи
специальных экранирующих функций, специфичных для используемой
вами базы данных (например,
mysql_real_escape_string(),
sqlite_escape_string() и т.д.).
Общие функции такие как addslashes() полезны
только в определённых случаях (например MySQL в однобайтной кодировке
с отключённым NO_BACKSLASH_ESCAPES), поэтому лучше
избегать их использование.
-
Ни в коем случае не выводите никакой информации о БД, особенно о её структуре.
Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо
на уровне базы данных, если она это поддерживает. Очевидно, что логирование
не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного
приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится.
Причём, в большинстве случаев полезно логировать все возможные детали.