mirror of
https://github.com/php/doc-ru.git
synced 2026-03-23 23:32:16 +01:00
236 lines
11 KiB
XML
236 lines
11 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- EN-Revision: 91570644fbbe4d23e79908e1a04c4c4d003fe587 Maintainer: shein Status: ready -->
|
||
<!-- Reviewed: no -->
|
||
<chapter xml:id="security.filesystem" xmlns="http://docbook.org/ns/docbook">
|
||
<title>Безопасность файловой системы</title>
|
||
<simpara>
|
||
<acronym>PHP</acronym> подчиняется правилам безопасности, которые встроены в бо́льшую часть
|
||
серверных систем в отношении разрешений для файлов и каталогов.
|
||
Следование правилам разрешает разработчика управлять тем, какие файлы
|
||
в файловой системе доступны для чтения.
|
||
При настройке файлов, доступ на чтение которых есть у мира,
|
||
соблюдают осторожность, чтобы гарантировать, что файлы безопасны для чтения пользователями
|
||
с доступом к файловой системе.
|
||
</simpara>
|
||
<simpara>
|
||
Поскольку <acronym>PHP</acronym> разработали для доступа к файловой системе на уровне пользователя,
|
||
можно написать <acronym>PHP</acronym>-скрипт,
|
||
который разрешит читать системные файлы наподобие <filename>/etc/passwd</filename>,
|
||
изменять Ethernet-соединения, отправлять большие задания на печать и т. д.
|
||
У этого есть ряд последствий, и поэтому нужно убедиться,
|
||
что не возникла ошибка с выбором файла, который разработчик читает и в который записывает данные.
|
||
</simpara>
|
||
<simpara>
|
||
Рассмотрим следующий скрипт, в котором пользователь указывает,
|
||
что хотел бы удалить файл из пользовательского домашнего каталога.
|
||
Это предполагает, что управление файлами регулярно использует
|
||
веб-интерфейс <acronym>PHP</acronym>,
|
||
поэтому пользователю веб-сервера Apache разрешается удалять файлы
|
||
в домашних каталогах пользователя.
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>Недостаточная проверка переменных приводит к…</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
// Удаление файла из домашней директории пользователя
|
||
$username = $_POST['user_submitted_name'];
|
||
$userfile = $_POST['user_submitted_filename'];
|
||
$homedir = "/home/$username";
|
||
|
||
unlink("$homedir/$userfile");
|
||
|
||
echo "Скрипт удалил файл!";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Поскольку имя пользователя и название файла приходят
|
||
из пользовательской формы, не исключается риск подмены и удаления данных,
|
||
которые принадлежат другому пользователю, даже если у пользователя не было разрешения
|
||
на удаление данных. Тогда требуется аутентификация.
|
||
Посмотрим, что произойдёт, если отправить значения
|
||
<literal>"../etc/"</literal> и <literal>"passwd"</literal>. Тогда код будет выглядеть вот так:
|
||
<example>
|
||
<title>…атаке на файловую систему</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
// Удаляем файл из произвольного места на жестком диске,
|
||
// к которому у пользователя PHP-скрипта есть доступ. Если PHP работает с правами суперпользователя:
|
||
$username = $_POST['user_submitted_name']; // В переменной передали значение "../etc"
|
||
$userfile = $_POST['user_submitted_filename']; // В переменной передали значение "passwd"
|
||
$homedir = "/home/$username"; // "/home/../etc"
|
||
|
||
unlink("$homedir/$userfile"); // "/home/../etc/passwd"
|
||
|
||
echo "Скрипт удалил файл!";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Атаки предотвращают двумя способами.
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>
|
||
Ограничивают права доступа на двоичный файл веб-пользователя <acronym>PHP</acronym>.
|
||
</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>
|
||
Проверяют каждую переменную, которую передают пользователи.
|
||
</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
Вот улучшенный вариант кода:
|
||
<example>
|
||
<title>Более безопасная проверка имени файла</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
// Удаляем файл из произвольного места на жестком диске,
|
||
// к которому у пользователя PHP-скрипта есть доступ.
|
||
$username = $_SERVER['REMOTE_USER']; // Проверяем, прошёл ли пользователь аутентификацию
|
||
$userfile = basename($_POST['user_submitted_filename']);
|
||
$homedir = "/home/$username";
|
||
|
||
$filepath = "$homedir/$userfile";
|
||
|
||
if (file_exists($filepath) && unlink($filepath)) {
|
||
$logstring = "Функция удалила файл $filepath\n";
|
||
} else {
|
||
$logstring = "Не удалось удалить файл $filepath\n";
|
||
}
|
||
|
||
$fp = fopen("/home/logging/filedelete.log", "a");
|
||
fwrite($fp, $logstring);
|
||
fclose($fp);
|
||
|
||
echo htmlentities($logstring, ENT_QUOTES);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
Однако даже такая проверка не лишена недостатков. Если
|
||
система аутентификации разрешает пользователям создавать произвольные логины,
|
||
и взломщик выбрал логин <literal>"../etc/"</literal>, система снова становится уязвимой.
|
||
Поэтому предпочитают более строгую проверку:
|
||
<example>
|
||
<title>Более строгая проверка имени файла</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$username = $_SERVER['REMOTE_USER']; // Проверяем, прошёл ли пользователь аутентификацию
|
||
$userfile = $_POST['user_submitted_filename'];
|
||
$homedir = "/home/$username";
|
||
|
||
$filepath = "$homedir/$userfile";
|
||
|
||
if (!ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) {
|
||
die("Неправильное имя пользователя или файл");
|
||
}
|
||
|
||
// и т. д.
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Набор файлов, за которыми придётся следить разработчику,
|
||
определяет операционная система, и включает
|
||
системные файлы устройств <filename>/dev/</filename> или <filename>COM1</filename>, конфигурационные файлы
|
||
<filename>/etc/</filename> и файлы с расширением <literal>.ini</literal>, хорошо известные
|
||
области хранения файлов <filename>/home/</filename>, <filename>Мои документы</filename> и так далее.
|
||
Поэтому обычно проще создать политику безопасности, которая запрещает
|
||
всё, кроме того, что явно разрешили.
|
||
</para>
|
||
<sect1 xml:id="security.filesystem.nullbytes">
|
||
<title>Нулевые байты и безопасность</title>
|
||
<simpara>
|
||
Поскольку для работы с файловой системой <acronym>PHP</acronym> внутренне
|
||
вызывает функции языка C, он иногда обрабатывает нулевые байты
|
||
неожиданным образом. Поскольку в C нулевые байты обозначают конец строки,
|
||
PHP не принимает всю строку с NUL-байтом, а только до позиции перед нулевым байтом.
|
||
Следующий пример содержит уязвимый код, который показывает проблему:
|
||
</simpara>
|
||
<example>
|
||
<title>Пример уязвимого скрипта с NUL-байтом</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$file = $_GET['file']; // "../../etc/passwd\0"
|
||
|
||
if (file_exists('/home/wwwrun/' . $file . '.php')) {
|
||
// Функция file_exists() вернёт true, поскольку путь /home/wwwrun/../../etc/passwd существует
|
||
include '/home/wwwrun/' . $file . '.php';
|
||
|
||
// PHP подключит файл /etc/passwd
|
||
}
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
<para>
|
||
Поэтому каждую испорченную строку, которая участвует в операциях
|
||
с файловой системой, требуется правильно проверять.
|
||
Вот улучшенная версия предыдущего примера:
|
||
</para>
|
||
<example>
|
||
<title>Корректная проверка входных данных</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$file = $_GET['file'];
|
||
|
||
// Белый список возможных значений
|
||
switch ($file) {
|
||
case 'main':
|
||
case 'foo':
|
||
case 'bar':
|
||
include '/home/wwwrun/include/' . $file . '.php';
|
||
break;
|
||
default:
|
||
include '/home/wwwrun/include/main.php';
|
||
}
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</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
|
||
-->
|