mirror of
https://github.com/php/doc-ru.git
synced 2026-03-23 23:32:16 +01:00
489 lines
29 KiB
XML
489 lines
29 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: irker Status: ready -->
|
||
<!-- Reviewed: no -->
|
||
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Безопасность баз данных</title>
|
||
|
||
<simpara>
|
||
Базы данных — главные компоненты большей части веб-приложений, которые
|
||
помогают сайтам создавать динамическое содержание. Нужно серьёзно относиться к защите баз данных,
|
||
поскольку в базах данных хранится конфиденциальная или секретная информация.
|
||
</simpara>
|
||
<simpara>
|
||
Чтобы получить или сохранить информацию, необходимо подключиться к базе данных,
|
||
отправить законный запрос, получить результат и закрыть соединение. Наиболее распространённый
|
||
язык запросов в таком взаимодействии — язык структурированных запросов SQL. Подробнее
|
||
о подделке SQL-запросов рассказывает раздел «<link linkend="security.database.sql-injection">SQL-инъекции</link>».
|
||
</simpara>
|
||
<simpara>
|
||
Нетрудно догадаться, что сам <acronym>PHP</acronym> не защищает базу
|
||
данных. Этот раздел документации вводит основы безопасного доступа и
|
||
управления базами данных в <acronym>PHP</acronym>-скриптах.
|
||
</simpara>
|
||
<simpara>
|
||
Требуется помнить простое правило: глубокая защита. Чем шире меры
|
||
по защите базы данных, тем меньше вероятность того, что
|
||
злоумышленник раскроет или злоупотребит сохранённой информацией.
|
||
Хороший проект структуры базы данных и приложения помогает справиться с опасениями.
|
||
</simpara>
|
||
|
||
<sect1 xml:id="security.database.design">
|
||
<title>Проектирование баз данных</title>
|
||
<simpara>
|
||
Первый шаг — создать базу данных или выбрать готовую БД, которую предоставила третья сторона.
|
||
При этом БД назначается владельцу, который выполнил запрос, который создал базу данных.
|
||
Обычно только владельцу или суперпользователю разрешается выполнять действия с объектами
|
||
в базе данных, а чтобы разрешить эти действия другим пользователям, пользователей наделяют
|
||
привилегиями.
|
||
</simpara>
|
||
<simpara>
|
||
Приложения не должны подключаться к базе данных через учётную запись
|
||
владельца или суперпользователя, потому что у этих пользователей есть права на выполнение произвольного запроса,
|
||
например, на изменение схемы БД (например, удаление таблиц) или удаление всего содержимого структуры БД.
|
||
</simpara>
|
||
<simpara>
|
||
Пользователей БД разрешается создавать для каждой отдельной
|
||
задачи приложения и ограничивать права пользователей на действия
|
||
с объектами базы данных.
|
||
Пользователю назначают только те привилегии, которые требуются для конкретных задач,
|
||
и избегают ситуаций, при которых у одного и того же пользователя есть чрезмерные права
|
||
на взаимодействие с базой данных.
|
||
Тогда, если злоумышленники получат доступ к БД через учётные данные приложения,
|
||
у злоумышленников будут права на изменение только тех данных, которые изменяло приложение.
|
||
</simpara>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="security.database.connection">
|
||
<title>Соединение с базой данных</title>
|
||
<simpara>
|
||
Возможно, для повышения безопасности потребуется устанавливать соединения по SSL-протоколу,
|
||
чтобы зашифровать сообщения между клиентом и сервером,
|
||
или подключаться по SSH-протоколу,
|
||
чтобы зашифровать сетевое соединение между клиентами и сервером базы данных.
|
||
Каждый из этих способов связи затруднит злоумышленнику отслеживание трафика и получение информации
|
||
о базе данных.
|
||
</simpara>
|
||
<!--simpara>
|
||
If your database server has native SSL support, consider using <link
|
||
linkend="ref.openssl">OpenSSL functions</link> in communication between
|
||
<acronym>PHP</acronym> and database via SSL.
|
||
</simpara-->
|
||
</sect1>
|
||
|
||
<sect1 xml:id="security.database.storage">
|
||
<title>Шифрование хранилища базы данных</title>
|
||
<simpara>
|
||
Протоколы SSL и SSH защищают данные, которые передаются от клиента к серверу:
|
||
эти протоколы не защищают постоянные данные, которые хранятся в базе данных.
|
||
Протокол SSL — протокол шифрования на уровне сеанса передачи данных.
|
||
</simpara>
|
||
<simpara>
|
||
Злоумышленник сможет раскрыть или использовать конфиденциальные данные не по назначению,
|
||
как только получит прямой (в обход веб-сервера) доступ к базе данных,
|
||
если только база данных не защищает информацию. Шифрование данных снижает риск угрозы,
|
||
но немногие базы данных предлагают такой тип шифрования данных.
|
||
</simpara>
|
||
<simpara>
|
||
Простейший способ защитить информацию — вначале установить пакет
|
||
для шифрования данных, а затем использовать этот пакет в <acronym>PHP</acronym>-скриптах.
|
||
<acronym>PHP</acronym> помогает в этом и предлагает модули вроде <link linkend="book.openssl">OpenSSL</link>
|
||
и <link linkend="book.sodium">Sodium</link>, которые покрывают широкий круг алгоритмов
|
||
шифрования. Скрипт шифрует данные перед вставкой в базу данных и расшифровывает данные
|
||
при извлечении. В следующих параграфах документация приводит примеры шифрования.
|
||
</simpara>
|
||
|
||
<sect2 xml:id="security.database.storage.hashing">
|
||
<title>Хеширование</title>
|
||
<simpara>
|
||
При работе со скрытыми данными, которые требуют повышенного внимания,
|
||
данные хешируют, если не требуется необработанное представление данных, —
|
||
то, которое не требуется показывать.
|
||
Популярный пример данных, которые требуют хеширования, — хранение криптографического
|
||
хеша пароля в БД вместо хранения самого пароля.
|
||
</simpara>
|
||
<simpara>
|
||
Функции, которые работают <link linkend="ref.password">с паролями</link>,
|
||
помогают удобно хешировать конфиденциальные данные и работать с этими хешами.
|
||
</simpara>
|
||
<simpara>
|
||
Функция <function>password_hash</function> хеширует заданную строку самым
|
||
стойким из доступных алгоритмом, а функция <function>password_verify</function>
|
||
проверяет, совпадает ли заданный пароль с хешем, который хранится в базе данных.
|
||
</simpara>
|
||
<example>
|
||
<title>Хеширование полей с паролями</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
// Сохраняем хеш пароля
|
||
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
|
||
pg_escape_string($username),
|
||
password_hash($password, PASSWORD_DEFAULT));
|
||
$result = pg_query($connection, $query);
|
||
|
||
// Проверяем правильность пароля, который ввёл пользователь
|
||
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
|
||
pg_escape_string($username));
|
||
$row = pg_fetch_assoc(pg_query($connection, $query));
|
||
|
||
if ($row && password_verify($password, $row['pwd'])) {
|
||
echo 'Добро пожаловать, ' . htmlspecialchars($username) . '!';
|
||
} else {
|
||
echo 'Ошибка авторизации, ' . htmlspecialchars($username) . '.';
|
||
}
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="security.database.sql-injection">
|
||
<title>SQL-инъекции</title>
|
||
<simpara>
|
||
SQL-инъекция — техника, при которой злоумышленник пользуется недостатками в коде приложения,
|
||
который отвечает за построение динамических SQL-запросов.
|
||
Злоумышленник получает доступ к привилегированным разделам приложения,
|
||
получает информацию из базы данных, подменяет данные
|
||
или даже выполняет опасные команды системного уровня на узле базы данных.
|
||
Уязвимость возникает, когда разработчики конкатенируют или интерполируют
|
||
произвольные входные данные в SQL-запросах.
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>
|
||
Постраничный вывод результата и… создание суперпользователя в СУБД PostgreSQL
|
||
</title>
|
||
<simpara>
|
||
В следующем примере пользовательский ввод интерполируется в SQL-запрос,
|
||
что помогает злоумышленнику получить учётную запись суперпользователя в базе данных.
|
||
</simpara>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$offset = $_GET['offset']; // Осторожно, нет валидации ввода!
|
||
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
|
||
$result = pg_query($conn, $query);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
В стандартном сценарии пользователи нажимают на ссылки «Вперёд» и «Назад»,
|
||
в <acronym>URL</acronym>-адресах которых закодировано смещение,
|
||
значение которого получает переменная <varname>$offset</varname>.
|
||
Скрипт ожидает, что входящее значение для переменной <varname>$offset</varname> — число.
|
||
Однако, что если взломщик выполнит попытку взломать систему
|
||
и добавит к <acronym>URL</acronym>-адресу следующее значение:
|
||
<informalexample>
|
||
<programlisting role="sql">
|
||
<![CDATA[
|
||
0;
|
||
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
|
||
select 'crack', usesysid, 't','t','crack'
|
||
from pg_shadow where usename='postgres';
|
||
--
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
Если бы это случилось, скрипт предоставил бы злоумышленнику доступ суперпользователя.
|
||
Обратите внимание, что значение <literal>0;</literal> записали,
|
||
чтобы передать правильное смещение для исходного запроса
|
||
и завершить запрос.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
Распространённый приём, который заставляет SQL-парсер
|
||
игнорировать остальную часть запроса разработчика, —
|
||
последовательность символов <literal>--</literal>, которая играет в SQL роль синтаксиса комментариев.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
Ещё один способ раскрыть пароли учётных записей в БД — атаковать страницы
|
||
с результатами поиска. Злоумышленнику требуется только проверить,
|
||
есть ли в запросе к серверу переменные, которые попадут в SQL-запрос и которые
|
||
не обрабатываются правильно. Эти фильтры обычно устанавливают в форме перед выполнением поиска,
|
||
чтобы изменить поведение условий <literal>WHERE, ORDER BY, LIMIT</literal> и <literal>OFFSET</literal>
|
||
в запросе <literal>SELECT</literal>.
|
||
Если база данных поддерживает конструкцию <literal>UNION</literal>, злоумышленник попробует
|
||
добавить к оригинальному запросу ещё один, чтобы вывести список паролей из произвольной таблицы.
|
||
Настоятельно рекомендуется хранить только зашифрованные пароли.
|
||
<example>
|
||
<title>
|
||
Вывод списка статей… и ряда паролей (любой сервер базы данных)
|
||
</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$query = "SELECT id, name, inserted, size FROM products
|
||
WHERE size = '$size'";
|
||
$result = odbc_exec($conn, $query);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Статическую часть запроса комбинируют с другим <literal>SELECT</literal>-запросом,
|
||
который раскроет все пароли:
|
||
<informalexample>
|
||
<programlisting role="sql">
|
||
<![CDATA[
|
||
'
|
||
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
|
||
--
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</para>
|
||
<para>
|
||
Инструкции <literal>UPDATE</literal> и <literal>INSERT</literal> также подвержены таким атакам.
|
||
<example>
|
||
<title>
|
||
От сброса пароля до… получения дополнительных привилегий (любой сервер баз данных)
|
||
</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Если злоумышленник отправляет значение
|
||
<literal>' or uid like'%admin%'</literal> для переменной <varname>$uid</varname>,
|
||
чтобы изменить пароль администратора, или просто присваивает переменной <varname>$pwd</varname>
|
||
значение <literal>hehehe', trusted=100, admin='yes</literal>,
|
||
чтобы получить дополнительные привилегии, тогда запросы переплетаются:
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?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
|
||
...;";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</para>
|
||
<simpara>
|
||
Хотя остаётся ясным, что для успешной атаки злоумышленнику нужны
|
||
хотя бы частичные знания об архитектуре базы данных, эту информацию часто получают легко.
|
||
Например, когда код — часть открытого программного обеспечения и лежит в открытом доступе.
|
||
Информация также раскрывается и закрытым исходным кодом, даже если код закодировали,
|
||
обфусцировали или скомпилировали, и даже собственным кодом через отображение сообщений об ошибках.
|
||
Другие методы включают подстановку типичных имён таблиц и столбцов: форма входа в систему,
|
||
которая использует таблицу users с именами столбцов id, username и password.
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>Атака на операционную систему сервера базы данных (MSSQL Server)</title>
|
||
<simpara>
|
||
Страшный пример получения доступа к командам уровня операционной системы
|
||
на отдельных хостах баз данных.
|
||
</simpara>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
|
||
$result = mssql_query($query);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Если злоумышленник отправит значение
|
||
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
|
||
для переменной <varname>$prod</varname>, то переменная <varname>$query</varname> будет равна:
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$query = "SELECT * FROM products
|
||
WHERE id LIKE '%a%'
|
||
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
|
||
$result = mssql_query($query);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
MSSQL Server выполняет пакетные SQL-запросы, включая команду для добавления
|
||
нового пользователя в базу данных локальных учётных записей.
|
||
Если бы это приложение запустили от имени суперадминистратора <literal>sa</literal>,
|
||
а службу MSSQLSERVER запустили бы с достаточными привилегиями,
|
||
у злоумышленника появилась бы учётная запись с доступом к локальной машине.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
Часть приведённых примеров привязана к конкретному серверу базы данных,
|
||
но это не говорит о невозможности похожей атаки на другие продукты.
|
||
Сервер базы данных подвержен и другим уязвимостям.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
<mediaobject>
|
||
<alt>Забавный пример проблем, связанных с SQL-инъекциями</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
|
||
</imageobject>
|
||
<caption>
|
||
<simpara>
|
||
Изображение любезно предоставил сайт веб-комиксов <link xlink:href="&url.xkcd;327">xkcd</link>
|
||
</simpara>
|
||
</caption>
|
||
</mediaobject>
|
||
</para>
|
||
|
||
<sect2 xml:id="security.database.avoiding">
|
||
<title>Техники защиты</title>
|
||
<para>
|
||
Рекомендуемый способ избежать SQL-инъекций — связать данные подготовленными инструкциями.
|
||
Параметризованных запросов недостаточно, чтобы на 100 процентов избежать внедрения SQL-инъекций,
|
||
но это простейший и безопасный способ передать входные данные SQL-инструкциям.
|
||
Литералы динамических данных в условиях <literal>WHERE</literal>, <literal>SET</literal>
|
||
и <literal>VALUES</literal> требуется заменить заполнителями. Сервер базы данных свяжет
|
||
фактические данные при выполнении и отправит отдельно от SQL-команды.
|
||
</para>
|
||
<para>
|
||
Сервер применяет связывание параметров только для данных.
|
||
Другие динамические части SQL-запроса требуется отфильтровать по известному списку разрешённых значений.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>
|
||
Избегание SQL-инъекций средствами подготовленных инструкций модуля PDO
|
||
</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?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}%"]);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
|
||
<simpara>
|
||
Подготовленные инструкции предоставляют модули <link linkend="pdo.prepared-statements">PDO</link>,
|
||
<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link>
|
||
и другие библиотеки баз данных.
|
||
</simpara>
|
||
|
||
<simpara>
|
||
Атаки с внедрением SQL-инъекций обычно основаны на коде,
|
||
который написали без учёта требований безопасности.
|
||
Разработчик не должен доверять никаким входным данным, особенно со стороны клиента,
|
||
даже если входные значения поступают из HTML-блока SELECT,
|
||
скрытого поля ввода или из cookie. Первый пример показывает, что такой простой запрос
|
||
легко приводит к катастрофе.
|
||
</simpara>
|
||
|
||
<para>
|
||
Стратегия глубокой защиты включает набор практик хорошего кода:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>
|
||
Не подключайтесь к базе данных как суперпользователь или владелец базы данных.
|
||
Используйте только пользователей с минимальными привилегиями.
|
||
</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>
|
||
Проверяйте входные данные на соответствие типу, который ожидался.
|
||
В <acronym>PHP</acronym> содержится много функции для проверки входных данных,
|
||
от простейших
|
||
<link linkend="ref.var">функций для работы с переменными</link>
|
||
наподобие функции <function>is_numeric</function>
|
||
и <link linkend="ref.ctype">функций проверки типа символов</link>
|
||
наподобие функции <function>ctype_digit</function> и далее к поддержке
|
||
<link linkend="ref.pcre">Perl-совместимых регулярных выражений</link>.
|
||
</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>
|
||
Если приложение ожидает числовые входные данные,
|
||
рассмотрите применимость проверки данных функцией <function>ctype_digit</function>,
|
||
незаметно измените тип входных данных функцией <function>settype</function>
|
||
или получите числовое представление входных данных функцией <function>sprintf</function>.
|
||
</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>
|
||
Если на уровне базы данных не поддерживается связывание переменных,
|
||
возьмите в кавычки каждое пользовательское нечисловое значение, которое передаётся в БД
|
||
через характерную для конкретной базы данных функцию экранирования строки:
|
||
<function>mysql_real_escape_string</function>,
|
||
<function>sqlite_escape_string</function> и т. д.
|
||
Общие функции наподобие <function>addslashes</function> полезны
|
||
только в конкретных средах (например, СУБД MySQL в однобайтовой кодировке
|
||
с отключённым режимом <varname>NO_BACKSLASH_ESCAPES</varname>), поэтому при работе с БД
|
||
лучше избегать таких функций.
|
||
</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>
|
||
Ни в каком случае не выводите информации о БД, особенно о структуре.
|
||
Дополнительную информацию дают разделы «<link
|
||
linkend="security.errors">Сообщения об ошибках</link>»
|
||
и «<link linkend="ref.errorfunc">Функции обработки и логирования ошибок</link>».
|
||
</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<simpara>
|
||
Кроме сказанного, выгоду получают от логирования запросов либо внутри скрипта,
|
||
либо на уровне базы данных, если БД поддерживает логирование. Очевидно, логирование
|
||
не в состоянии предотвратить попытки навредить, но полезно при трассировке приложения,
|
||
защиту которого получилось обойти. Польза файла журнала заключается не в самом файле,
|
||
а в информации, которую содержит лог.
|
||
Недостатку информации лучше предпочесть избыточное логирование.
|
||
</simpara>
|
||
</sect2>
|
||
</sect1>
|
||
</chapter>
|
||
<!-- Keep this comment at the end of the file
|
||
Local variables:
|
||
mode: sgml
|
||
sgml-omittag:t
|
||
sgml-shorttag:t
|
||
sgml-minimize-attributes:nil
|
||
sgml-always-quote-attributes:t
|
||
sgml-indent-step:1
|
||
sgml-indent-data:t
|
||
indent-tabs-mode:nil
|
||
sgml-parent-document:nil
|
||
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
||
sgml-exposed-tags:nil
|
||
sgml-local-catalogs:nil
|
||
sgml-local-ecat-files:nil
|
||
End:
|
||
vim600: syn=xml fen fdm=syntax fdl=2 si
|
||
vim: et tw=78 syn=sgml
|
||
vi: ts=1 sw=1
|
||
-->
|