Руководство по PHP
Пред. | Глава 27. Безопасность баз данных | След.

# SQL-инъекции

Многие веб-разработчики даже не догадываются, что SQL-запросы могут быть подделаны, и считают, что SQL-запросы всегда достоверны. На самом деле поддельные запросы могут обойти ограничения доступа, стандартную проверку авторизации, а некоторые виды запросов могут дать возможность выполнять команды операционной системы.

Принудительное внедрение вредоносных инструкций в SQL-запросы - методика, в которой взломщик создает или изменяет текущие SQL-запросы для работы со скрытыми данными, их изменения или даже выполнения опасных команд операционной системы на сервере базы данных. Атака выполняется на базе приложения, строящего SQL-запросы из пользовательского ввода и статических переменных. Следующие примеры, к сожалению, построены на реальных фактах.

Благодаря отсутствию проверки пользовательского ввода и соединением с базой данных под учетной записью суперпользователя (или любого другого пользователя, наделенного соответствующими привелегиями), взломщик может создать еще одного пользователя БД с правами суперпользователя.

Пример 27-2. Постраничный вывод результата... и создание суперпользователя в PostgreSQL и MySQL

`<?php $offset = argv[0]; // проверка пользовательских данных отсутствует $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; // используя PostgreSQL $result = pg_exec($conn, $query); // используя MySQL $result = mysql_query($query); ?>`

Обычно пользователи кликают по ссылкам 'вперед' и 'назад', вследствии чего значение переменной $offset заносится в адресную строку. Скрипт ожидает, что $offset - десятиричное число. Однако, взломщик может попытаться взломать систему, присоединив к строке запроса дополнительную подстроку, обработанную функцией urlencode():

    // используя PostgreSQL
    0;
    insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
        select 'crack', usesysid, 't','t','crack'
        from pg_shadow where usename='postgres';
    --

    // используя MySQL
    0;
    UPDATE user SET Password=PASSWORD('crack') WHERE user='root';
    FLUSH PRIVILEGES;

Если это произойдет, скрипт предоставит взломщику доступ к базе с правами суперпользователя. Заметим, что 0; использован для того, чтобы задать правильное значение смещения для первого запроса и корректно его завершить.

> Замечание: Уже привычна технология, когда разработчики указывают принудительное игнорирование парсером SQL оставшейся части запроса при помощи нотации --, означающей комментарий.

Еще один вероятный способ получить пароли учетных записей в БД - атака страниц, предоставляющих поиск по базе. Взломщику нужно лишь проверить, используется ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная. Это может быть один из устанавливаемых на предыдущей странице фильтров, таких как WHERE, ORDER BY, LIMIT и OFFSET, используемых при построении запросов SELECT. В случае, если используемая вами база данных поддерживает конструкцию UNION, взломщик может присоединить к оригинальному запросу еще один дополнительный, для извлечения пользовательских паролей. Настоятельно рекомендуем использовать только зашифрованные пароли.

Пример 27-3. Листинг статей... и некоторых паролей (для любой базы данных)

`<?php $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' ORDER BY $order LIMIT $limit, $offset;"; $result = odbc_exec($conn, $query); ?>`

Статическая часть запроса может комбинироваться с другим SQL-запросом, который откроет все пароли:

    '
    union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
    --

Если этот запрос (использующий ' и --) присоединить к значению одной из переменных, используемых для формирования $query, запрос заметно преобразится.

Команды UPDATE также могут использоваться для атаки. Опять же, есть угроза разделения инструкции на несколько запросов, присоединения дополнительного запроса. Также взломщик может видоизменить выражение SET. В этом случае потенциальному взломщику необходимо обладать некоторой дополнительной информацией для успешного манипулирования запросами. Эту информацию можно получить, проанализировав используемые в форме имена переменных либо просто перебирая все наиболее распространенные варианты названия соответствующих полей (а их не так уж и много).

Пример 27-4. От восстановления пароля... до получения дополнительных привилегий (для любой базы данных)

`<?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 ...;"; ?>`

Пугающий пример того, как на сервере баз данных могут выполняться команды операционной системы.

Пример 27-5. Выполнение команд операционной системы на сервере (для базы MSSQL)

`$query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query);`

Если взломщик введет значениме a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- для переменной $prod, тогда запрос $query будет выглядеть так:

`$query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--"; $result = mssql_query($query);`

MSSQL сервер выполняет SQL-команды в пакетном режиме, в том числе и операции по заведению локальных учетных записей базы данных. В случае, если приложение работает с привилегиями администратора и сервис MSSQL запущен с необходимыми привилегиями, выполнив приведенные выше действия, взломщик получит аккаунт для доступа к серверу.

> Замечание: Некоторые приведенные в этой главе примеры касаются конкретной базы данных. Это не означает, что аналогичные атаки на другие программные продукты невозможны. Работоспособность вашей базы данных может быть нарушена каким-либо другим способом.

## Способы защиты

Вы можете утешать себя тем, что в большинстве случаев, взломщик должен обладать некоторой информацией о структуре базы данных. Вы правы, но вы не знаете, когда и как будет предпринята попытка взлома, в случае если это произойдет ваша БД окажется незащищенной. Если вы используете программный продукт с открытыми исходными кодами или просто общедоступный пакет для работы с базой данных (например контент менеджер или форум), взломщик легко сможет воспроизвести интересующие его участки кода. В случае если они плохо спроектированы, это может являться одной из угроз вашей безопасности.

Большинство успешных атак основывается на коде, написанном без учета соответствующих требований безопасности. Не доверяйте никаким вводим данным, особенно если они поступают со стороны клиента, даже если это списки в форме, скрытые поля или куки. Приведенные примеры показывают, к каким последствиям могут привести подделанные запросы.

- Старайтесь не открывать соединение с базой, используя учетную запись владельца или администратора. Всегда старайтесь использовать специально созданных пользователей с максимально ограниченными правами.

- Всегда проверяйте введенные данные на соответствие ожидаемому типу. В PHP есть множество функций для проверки данных: начиная от простейших Функций для работы с переменными и Функции определения типа символов (такие как is_numeric(), ctype_digit()) и заканчивая Perl-совместимыми регулярными выражениями.

- В случае, если приложение ожидает цифровой ввод, примените функцию is_numeric() для проверки введенных данных, или принудительно укажите их тип при помощи settype(), или просто используйте числовое представление при помощи функции sprintf().

  Пример 27-6. Более безопасная реализация постраничной навигации

  `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);`

- Экранируйте любой нецифровой ввод, используемый в запросах к БД при помощи функций addslashes() или addcslashes(). Обратите внимание на первый пример. Следует помнить, что одного использования кавычек в запросе мало, это легко обойти.

- Не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: Сообщения об ошибках и Функции обработки и логирования ошибок.

- Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.

Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо на уровне базы данных, если она это поддерживает. Очевидно, что логирование не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится. Причем, в большинстве случаев полезно логировать все возможные детали.

Пред. | Начало | След.
Защита хранилища базы данных | Уровень выше | Сообщения об ошибках