mirror of
https://github.com/php/doc-ru.git
synced 2026-03-26 00:32:15 +01:00
227 lines
10 KiB
XML
227 lines
10 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- EN-Revision: c5d92fd7127e059d448d43ba339f19956f83b05a Maintainer: shein Status: ready -->
|
||
<!-- Reviewed: no -->
|
||
<chapter xml:id="security.filesystem" xmlns="http://docbook.org/ns/docbook">
|
||
<title>Безопасность файловой системы</title>
|
||
<simpara>
|
||
<acronym>PHP</acronym> является одним из важных моментов в вопросе безопасности сервера,
|
||
поскольку PHP-скрипты могут манипулировать файлами и каталогами
|
||
на диске. В связи с этим существуют конфигурационные настройки,
|
||
указывающие, какие файлы могут быть доступны и какие операции с ними можно
|
||
выполнять. Необходимо проявлять осторожность, поскольку любой из файлов,
|
||
с полными правами чтения ("world readable") может быть прочитан
|
||
каждым, кто имеет доступ к файловой системе.
|
||
</simpara>
|
||
<simpara>
|
||
Поскольку в <acronym>PHP</acronym> изначально предполагался полноправный пользовательский
|
||
доступ к файловой системе, можно написать <acronym>PHP</acronym>-скрипт,
|
||
который позволит читать системные файлы, такие как /etc/passwd,
|
||
управлять сетевыми соединениями, отправлять задания принтеру, и
|
||
так далее. Как следствие, вы всегда должны быть уверены в том, что
|
||
файлы, которые вы читаете или модифицируете, являются именно теми,
|
||
которые вы подразумевали.
|
||
</simpara>
|
||
<simpara>
|
||
Рассмотрим следующий пример, в котором пользователь создал скрипт, удаляющий
|
||
файл из его домашней директории. Предполагается ситуация, когда веб-интерфейс,
|
||
написанный на <acronym>PHP</acronym>, регулярно используется для
|
||
работы с файлами, и настройки безопасности позволяют удалять
|
||
файлы в домашнем каталоге.
|
||
</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>
|
||
Поскольку переменные вводятся в пользовательской форме, существует
|
||
возможность удалить файлы, принадлежащие кому-либо другому, введя
|
||
соответствующие значения. В этом случае может понадобиться авторизация.
|
||
Посмотрим, что произойдёт, если будут отправлены значения
|
||
"../etc/" и "passwd". Скрипт выполнит следующие действия:
|
||
<example>
|
||
<title>... атаке на файловую систему</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
// Удаление любого файла, доступного из PHP-скрипта.
|
||
// В случае, если PHP работает с правами пользователя root:
|
||
$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>
|
||
Однако и такая проверка не учитывает все возможные ситуации. Если
|
||
система авторизации позволяет пользователям выбирать произвольные логины,
|
||
взломщик может создать учётную запись вида "../etc/" и система опять
|
||
окажется уязвимой. Исходя из этого, вам может понадобиться более строгая проверка:
|
||
<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("Неправильное имя пользователя или файл");
|
||
}
|
||
|
||
//etc...
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
В зависимости от используемой вами операционной системы необходимо
|
||
предусматривать возможность атаки на разнообразные файлы, включая
|
||
системные файлы устройств (/dev/ или COM1), конфигурационные файлы
|
||
(например /etc/ или файлы с расширением .ini), хорошо известные
|
||
области хранения данных (/home/, My Documents), и так далее.
|
||
Исходя из этого, как правило, легче реализовать такую политику
|
||
безопасности, в которой запрещено все, исключая то, что явно
|
||
разрешено.
|
||
</para>
|
||
<sect1 xml:id="security.filesystem.nullbytes">
|
||
<title>Проблемы безопасности, связанные с нулевым байтом</title>
|
||
<simpara>
|
||
Так как для работы с файловой системой <acronym>PHP</acronym>
|
||
использует нижележащие C-функции, то в этом случае возможна
|
||
крайне неожиданная обработка нулевого байта.
|
||
Так как нулевой байт означает конец строки в C, то строки, содержащие
|
||
такой байт, не будут трактоваться полностью, а только до той
|
||
позиции, в которой находится этот байт.
|
||
|
||
Следующий пример содержит уязвимый код, демонстрирующий эту проблему:
|
||
</simpara>
|
||
<example>
|
||
<title>Скрипт, уязвимый к нулевому байту</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';
|
||
// будет подключён файл /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
|
||
-->
|