Учебник РНР
НазадГлава 4. Безопасность Вперёд

Безопасность баз данных

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

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

PHP сам по себе не может защитить вашу БД. Последующие разделы являются введением в основы доступа и манипулирования БД в PHP-скриптах.

Запомните простое правило: максимальная защита. Необходимо защищать БД как можно сильнее, что уменьшит вероятность успеха взлома и получения, нарушения или уничтожения ценной информации.

Дизайн БД

Первый шаг - это всегда создание БД, если только вы не хотите использовать готовую БД стороннего производителя. Когда БД создаётся, она назначается пользователю, который выполняет оператор создания. Обычно только владелец/owner (или superuser) может выполнять действия с объектами в БД, а чтобы и другие пользователи могли пользоваться этой БД, необходимо дать привилегии доступа.

Приложения никогда не должны соединяться с БД как её owner или superuser, поскольку эти бюджеты могут выполнять любой запрос, модифицировать схему (например, стирая таблицы) или удалять всё содержимое полностью.

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

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

Соединение с БД

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

Модель шифровки при хранении/Encrypted Storage

SSL/SSH защищает передачу данных с клиента на сервер, SSL/SSH не защищает постоянные данные, хранимые в БД. SSL это протокол on-the-wire.

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

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

В случае со скрытыми/hidden данными, если из необработанное представление не нужно (то есть оно не отображается), можно предусмотреть хэширование. Хорошо известный пример хэширования - хранение MD5-хэша пароля в БД, вместо хранения самого пароля. См. также crypt() и md5().

Пример 4-5. Использование хэшированного поля password
 
// хранение хэша пароля
$query  = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

// запросить, еслли пользователь ввёл правильный пароль
$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

if (pg_numrows($result) > 0) {
    echo "Welcome, $username!";
}
else {
    echo "Authentication failed for $username.";
}

Инъекция SQL

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

Direct SQL Command Injection это такая техника, когда взломщик создаёт или изменяет текущие команды SQL для получения доступа к скрытым данным, их переопределения или даже для выполнения опасных команд системного уровня на хосте БД. Это выполняется с помощью приложения, принимающего пользовательский ввод, и сочетания его со static-параметрами для построения SQL-запроса. Следующие примеры (к сожалению...) основаны на реальных фактах.

Благодаря отсутствию проверки ввода и соединения с БД или поведению superuser'а или того, кто может создавать пользователей, взломщик может создать superuser'а в вашей БД.
Пример 4-6. Разделение результата выполнения запроса на страницы...
и создание superuser'ов (PostgreSQL и MySQL)
 
$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);

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

>
// в 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;

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

Примечание: Обычной техникой является форсирование игнорирования SQL-разборщиком остальной части запроса, написанного разработчиком, с помощью -- (знака комментария в SQL).

Возможно получение паролей путём обмана ваших страниц с результатами поиска. Взломщику нужно лишь проверить, имеется ли отправленная переменная, используемая в SQL-операторе, которая не обрабатывается надлежащим образом. Эти фильтры могут быть установлены обычно в предыдущей форме для специализирования вариантов WHERE, ORDER BY, LIMIT и OFFSET в операторах SELECT. Если ваша БД поддерживает конструкцию UNION, взломщик может попытаться присоединить к оригинальному запросу целый запрос на список паролей из произвольной таблицы. Использование шифрованных полей password настоятельно рекомендуется.
Пример 4-7. Листинг статей ... и некоторых паролей (любой сервер БД)
 
$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, запрос чудовищно изменится.

SQL UPDATEs также являются субъектами атаки на ваши БД. Есть угроза их расчленения и присоединения к ним совершенно нового запроса. Взломщик может поработать с SET. В этом случае нужно обладать некоторой схемой информации для успешного манипулирования запросом. Это можно сделать, проверив имена переменных формы, или просто выполнив грубое форсирование. Есть не так уж много соглашений по именованию полей для хранения паролей и имён пользователей.
Пример 4-8. От восстановления значения пароля ...
до получения дополнительных привилегий (любой сервер БД)
 
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";

Но пользователь-злоумышленник отправляет значение ' or uid like'%admin%'; -- в $uid для изменения пароля admin'а или просто устанавливает в $pwd значение "hehehe', admin='yes', trusted=100 " (с ведомым пробелом) для получения дополнительных привилегий. Затем запрос скручивается:

// $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-9. Атака на ОС-хост БД (MSSQL Server)
 
$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 Server выполняет операторы SQL в пакетном режиме, включая и команды добавления нового пользователя в локальную БД бюджетов. Если такое приложение запущено как sa и служба MSSQLSERVER запущена с достаточными привилегиями, хакер сможет получить бюджет для доступа к данной машине.

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

Как этого избежать

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

Этим атакам в основном подвергается код, написанный без учёта требований безопасности. Никогда не доверяйте вводу любого рода, особенно тому, который поступает со стороны клиента, даже если он приходит от select-списка, скрытого/hidden поля или куки/cookie. Первый пример показывает, что такой небезупречный запрос может привести к тяжким последствиям.

  • Никогда не соединяйтесь с БД как superuser или как владелец БД (owner). Всегда используйте специализированных пользователей с максимально ограниченными правами.

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

  • Если приложение ожидает цифровой ввод, предусмотрите проверку данных с помощью is_numeric(), или скрыто измените его тип с использованием settype(), или используйте его числовое представление через sprintf().
    Пример 4-10. Более безопасный способ
    составления запроса для разбивки на страницы
     
    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(). См. первый пример. Как видно из примеров, кавычек, вставленных в статическую часть запроса, недостаточно, и это можно легко взломать.

  • Не выводите специфичную для БД информацию, особенно о схеме. См. также Сообщение об ошибке и Обработка ошибок и функции логинга

  • Вы можете использовать хранимые процедуры и ранее определённые курсоры для абстрактного доступа к данным, чтобы пользователи не имели прямого доступа к таблицам или просмотрам/views, но это решение имеет свою специфику.

Помимо всего прочего, вы можете извлечь пользу из запросов логинга в вашем скрипте или или в самой БД, если она это поддерживает. Очевидно, что логинг не может предотвратить попытку нанесения вреда, но может помочь для трассировки "обманутого" приложения.
log полезен не сам по себе, а содержащейся в нём информацией. Чем больше деталей, тем обычно лучше.


Назад Оглавление Вперёд
Безопасность файловой системыВверх Сообщение об ошибках