PHP 8.1: $_FILES: Новое значение full_path для загрузки каталогов
В PHP 8.1 $_FILES будет содержать новый ключ с именем full_path, который содержит полный путь к файлу, указанный браузером.
$_FILES - суперглобальная переменная PHP. Она содержит имена, размеры и MIME-типы файлов, загруженных в текущем HTTP-запросе.
В полях загрузки HTML-файла можно загрузить весь каталог с атрибутом webkitdirectory. webkitdirectory- это атрибут HTML, который можно использовать в элементах HTML input с расширением type=file. Это не стандарт браузера, но его поддерживают все основные браузеры, включая Chrome 6+, Firefox 50+, Edge 13+ и Safari 11.1+.
<form action="" method="post" enctype="multipart/form-data">
<input name="files[]" type="file" webkitdirectory multiple />
<input type="submit" />
</form>
До PHP 8.1 $_FILES содержали относительные пути в файловой системе пользователя для учета подкаталогов, содержащих файлы с одинаковыми именами. Например, если пользователь загружает каталог foo, значение name массива $_FILES содержит только имя файла без относительного пути:
foo
├── dir1
│ └── file.txt
└── dir2
└── file.txt
Если посмотрим содержимое $_FILES:
var_dump($_FILES);
array(1) {
["files"]=> array(6) {
["name"]=> array(2) {
[0]=> string(8) "file.txt"
[1]=> string(8) "file.txt"
}
["tmp_name"]=> array(2) {
[0]=> string(14) "/tmp/phpK1J4UI"
[1]=> string(14) "/tmp/phpgRqHHs"
}
// ... так же содержит поля error, type, size
}
}
До PHP 8.1 было невозможно принять каталог как файл, загруженный из браузера пользователя, и сохранить их с точной структурой каталогов или получить доступ к относительным путям, потому что PHP не передавал эту информацию в массиве $_FILES.
В PHP 8.1 такая возможность появилась, благодаря новому ключу с именем full_path, который содержит полный путь, указанный браузером.
var_dump($_FILES);
array(1) {
["files"]=> array(6) {
["name"]=> array(2) {
[0]=> string(8) "file.txt"
[1]=> string(8) "file.txt"
}
["full_path"]=> array(2) {
[0]=> string(19) "foo/dir1/file.txt"
[1]=> string(19) "foo/dir2/file.txt"
}
["tmp_name"]=> array(2) {
[0]=> string(14) "/tmp/phpK1J4UI"
[1]=> string(14) "/tmp/phpgRqHHs"
}
// ... + error, type, size
}
}
Имея информацию из full_path, можно сохранить относительные пути или восстановить тот же каталог на сервере.
В приведенном выше примере массив $_FILES['files']['full_path'] содержит путь к загруженному файлу, включая его путь. Это более полезно в отличие от массива $_FILES['files']['name'], который содержит повторяющиеся записи, потому что оба dir1 и dir2 содержат файл с тем же именем file.txt.
Временный путь для загруженных файлов ($_FILES['files']['tmp_name']) остается уникальным и не сохраняется во вложенных каталогах. Можно безопасно продолжать использовать значения tmp_name с функцией move_uploaded_file.
Массив full_path будет всегда существовать для всех загрузок файлов, включая стандартные одиночные или множественные загрузки файлов, и в некоторых случаях будет просто идентичен массиву name.
Повышение безопасности
PHP анализирует только информацию об относительном пути, отправленную браузером, и передает эту информацию в массив $_FILES. Нет гарантии, что значения в массиве full_path содержат реальную структуру каталогов, и приложения PHP не должны доверять этой информации.
Значения в массиве full_path вводятся пользователем, и им нельзя доверять!
Приложения, которые принимают загрузку каталога и хранят файлы в соответствии со значениями full_path, должны обеспечить правильную проверку информации в full_path, перед перемещением загруженных файлов во вложенный каталог.
Некоторые варианты угроз:
- Атаки с обходом каталогов, отправляя путь, который ведет к другому каталогу, например foo/../../../etc/password.
- Атаки на исчерпание ресурсов, отправляя путь с глубоко вложенными путями, например foo/a/a/a/a/a/a/a/a/a/a/a/a/a.jpg, или очень длинное имя, которое приведет к ограничению пути в определенных файловых системах.
- Нарушение целостности из-за предоставления имени файла, которое не может быть создано, например CON, недопустимое имя файла - Файловые системы Windows.
Чтобы предотвратить такие атаки, всегда ограничивайте уровень вложенности, убедитесь, что файл не существует перед записью, и запрещайте пути, которые позволяют обход каталога. Кроме того, ни в коем случае нельзя удалять стандартные проверки содержимого файла (такие как проверка расширения файла и типов MIME).
Влияние на обратную совместимость
Ключ массива full_path в $_FILES - это новая функция в PHP 8.1. Существующие значения массива name не изменяются, и они продолжают содержать только имя файла без пути к файлу.
Использование full_path в более старых версиях PHP
К сожалению, нет простого способа перенести эту функциональность в более старые версии PHP.
Для загрузки файлов требуется наличие в форме HTML атрибута enctype="multipart/form-data". Из-за этого атрибута PHP поток в php://input будет пустой, потому что PHP заполняет суперглобальные переменные $_POST и $_FILES и опустошает php://input поток. Это исключает получение предоставленной браузером информации о пути к файлу при чтении потока php://input.
Однако директива enable_post_data_reading=0 в PHP INI - запрещает PHP заполнять $_POST и $_FILES, оставляя таким образом значения потока php://input нетронутыми. В качестве хак-подхода, можно парсить запрос необработанного HTTP и заполнять $_POST и $_FILES массивы или объектов PSR-7 HTTP запроса. Эта информация содержит предоставленный браузером относительный путь к файлу.
В качестве альтернативы, было бы проще постепенно улучшать взаимодействие с пользователем в веб-браузерах, используя клиентский подход JavaScript для отправки содержимого файлов и путей к файлам в отдельном поле.
Что думаешь?