PHP 8.1: $_FILES: Новое значение full_path для загрузки каталогов

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 для отправки содержимого файлов и путей к файлам в отдельном поле.

Сергей Мухин

Веб-разработчик со стажем программирования более 9 лет, всегда в процессе учебы и созидания.

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной