PHP 8.4.2 Released!

Безопасность файловой системы

Содержание

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

Поскольку PHP разработали для доступа к файловой системе на уровне пользователя, можно написать PHP-скрипт, который разрешит читать системные файлы наподобие /etc/passwd, изменять Ethernet-соединения, отправлять большие задания на печать и т. д. У этого есть ряд последствий, и поэтому нужно убедиться, что не возникла ошибка с выбором файла, который разработчик читает и в который записывает данные.

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

Пример #1 Недостаточная проверка переменных приводит к…

<?php

// Удаление файла из домашней директории пользователя
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

unlink("$homedir/$userfile");

echo
"Скрипт удалил файл!";

?>
Поскольку имя пользователя и название файла приходят из пользовательской формы, не исключается риск подмены и удаления данных, которые принадлежат другому пользователю, даже если у пользователя не было разрешения на удаление данных. Тогда требуется аутентификация. Посмотрим, что произойдёт, если отправить значения "../etc/" и "passwd". Тогда код будет выглядеть вот так:

Пример #2 …атаке на файловую систему

<?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 "Скрипт удалил файл!";

?>
Атаки предотвращают двумя способами.
  • Ограничивают права доступа на двоичный файл веб-пользователя PHP.
  • Проверяют каждую переменную, которую передают пользователи.
Вот улучшенный вариант кода:

Пример #3 Более безопасная проверка имени файла

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

?>
Однако даже такая проверка не лишена недостатков. Если система аутентификации разрешает пользователям создавать произвольные логины, и взломщик выбрал логин "../etc/", система снова становится уязвимой. Поэтому предпочитают более строгую проверку:

Пример #4 Более строгая проверка имени файла

<?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(
"Неправильное имя пользователя или файл");
}

// и т. д.

?>

Набор файлов, за которыми придётся следить разработчику, определяет операционная система, и включает системные файлы устройств /dev/ или COM1, конфигурационные файлы /etc/ и файлы с расширением .ini, хорошо известные области хранения файлов /home/, Мои документы и так далее. Поэтому обычно проще создать политику безопасности, которая запрещает всё, кроме того, что явно разрешили.

Добавить

Примечания пользователей 5 notes

up
99
anonymous
19 years ago
(A) Better not to create files or folders with user-supplied names. If you do not validate enough, you can have trouble. Instead create files and folders with randomly generated names like fg3754jk3h and store the username and this file or folder name in a table named, say, user_objects. This will ensure that whatever the user may type, the command going to the shell will contain values from a specific set only and no mischief can be done.

(B) The same applies to commands executed based on an operation that the user chooses. Better not to allow any part of the user's input to go to the command that you will execute. Instead, keep a fixed set of commands and based on what the user has input, and run those only.

For example,
(A) Keep a table named, say, user_objects with values like:
username|chosen_name |actual_name|file_or_dir
--------|--------------|-----------|-----------
jdoe |trekphotos |m5fg767h67 |D
jdoe |notes.txt |nm4b6jh756 |F
tim1997 |_imp_ folder |45jkh64j56 |D

and always use the actual_name in the filesystem operations rather than the user supplied names.

(B)
<?php
$op
= $_POST['op'];//after a lot of validations
$dir = $_POST['dirname'];//after a lot of validations or maybe you can use technique (A)
switch($op){
case
"cd":
chdir($dir);
break;
case
"rd":
rmdir($dir);
break;
.....
default:
mail("webmaster@example.com", "Mischief", $_SERVER['REMOTE_ADDR']." is probably attempting an attack.");
}
up
25
fmrose at ncsu dot edu
19 years ago
All of the fixes here assume that it is necessary to allow the user to enter system sensitive information to begin with. The proper way to handle this would be to provide something like a numbered list of files to perform an unlink action on and then the chooses the matching number. There is no way for the user to specify a clever attack circumventing whatever pattern matching filename exclusion syntax that you may have.

Anytime you have a security issue, the proper behaviour is to deny all then allow specific instances, not allow all and restrict. For the simple reason that you may not think of every possible restriction.
up
22
devik at cdi dot cz
23 years ago
Well, the fact that all users run under the same UID is a big problem. Userspace security hacks (ala safe_mode) should not be substitution for proper kernel level security checks/accounting.
Good news: Apache 2 allows you to assign UIDs for different vhosts.
devik
up
10
Latchezar Tzvetkoff
15 years ago
A basic filename/directory/symlink checking may be done (and I personally do) via realpath() ...

<?php

if (isset($_GET['file'])) {
$base = '/home/polizei/public_html/'; // it seems this one is good to be realpath too.. meaning not a symlinked path..
if (strpos($file = realpath($base.$_GET['file']), $base) === 0 && is_file($file)) {
unlink($file);
} else {
die(
'blah!');
}
}
?>
up
6
cronos586(AT)caramail(DOT)com
22 years ago
when using Apache you might consider a apache_lookup_uri on the path, to discover the real path, regardless of any directory trickery.
then, look at the prefix, and compare with a list of allowed prefixes.
for example, my source.php for my website includes:
if(isset($doc)) {
$apacheres = apache_lookup_uri($doc);
$really = realpath($apacheres->filename);
if(substr($really, 0, strlen($DOCUMENT_ROOT)) == $DOCUMENT_ROOT) {
if(is_file($really)) {
show_source($really);
}
}
}
hope this helps
regards,
KAT44
To Top