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);
?>
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
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
-запросом, который выведет все пароли:
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
Выражения UPDATE
и INSERT
также подвержены таким атакам.
Пример #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
...;";
?>
Хотя остаётся очевидным, что для проведения успешной атаки злоумышленник должен обладать хотя бы некоторыми знаниями об архитектуре базы данных, получить эту информацию зачастую очень просто. Например, код может быть частью программного обеспечения с открытым исходным кодом и находиться в открытом доступе. Эта информация также может быть раскрыта закрытым кодом - даже если он закодирован, обфусцирован или скомпилирован, и даже вашим собственным кодом через отображение сообщений об ошибках. Другие методы включают использование типичных имён таблиц и столбцов. Например, форма входа в систему, использующая таблицу '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 будет:
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$result = mssql_query($query);
?>
sa
и служба MSSQLSERVER была запущена с достаточными привилегиями,
у злоумышленника появилась бы учётная запись, с помощью которой
он мог бы получить доступ к этой машине.
Замечание:
Некоторые примеры, приведённые выше, привязаны к конкретному серверу баз данных, но это не означает, что подобная атака невозможна на другие продукты. Ваш сервер баз данных может быть аналогично уязвим и другим способом.
Рекомендуемый способ избежать 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. Первый пример показывает, что такой простой запрос может привести к катастрофе.
Стратегия "защита в глубину" включает в себя несколько эффективных методов написания кода:
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо на уровне базы данных, если она это поддерживает. Очевидно, что логирование не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится. Причём, в большинстве случаев полезно логировать все возможные детали.