PHP 8.1: $_FILES
: New full_path
value for directory-uploads
$_FILES
is a PHP super-global and variable. It contains names, sizes, and mime types of files uploaded in the current HTTP request.
In HTML file upload fields, it is possible to upload an entire directory with the webkitdirectory
attribute. This feature is supported in most modern browsers.
<form action="" method="post" enctype="multipart/form-data">
<input name="myupload[]" type="file" webkitdirectory multiple />
<input type="submit" />
</form>
Prior to PHP 8.1, the $_FILES
did contain the relative paths in the user file-system to account for sub directories containing files with the same name.
For example, if the user uploads the foo
directory, the $_FILES
array name
value only contains the file name without the relative path.
Example directory structure:
foo
├── dir1
│ └── file.txt
└── dir2
└── file.txt
$_FILES
array of the POST request:
var_dump($_FILES);
array(1) {
["myupload"]=> array(6) {
["name"]=> array(2) {
[0]=> string(8) "file.txt"
[1]=> string(8) "file.txt"
}
["tmp_name"]=> array(2) {
[0]=> string(14) "/tmp/phpV1J3EM"
[1]=> string(14) "/tmp/phpzBmAkT"
}
// ... + error, type, size
}
}
Prior to PHP 8.1, it was not possible accept a directory as a file upload from the browser/user-agent, and store them with exact directory structure, or access the relative paths because PHP did not pass that information to the $_FILES
array.
In PHP 8.1, the $_FILES
also contains a new key named full_path
, that contains the full path as submitted by the browser.
var_dump($_FILES);
array(1) {
["myupload"]=> array(6) {
["name"]=> array(2) {
[0]=> string(8) "file.txt"
[1]=> string(8) "file.txt"
}
+ ["full_path"]=> array(2) {
+ [0]=> string(19) "foo/test1/file.txt"
+ [1]=> string(19) "foo/test2/file.txt"
+ }
["tmp_name"]=> array(2) {
[0]=> string(14) "/tmp/phpV1J3EM"
[1]=> string(14) "/tmp/phpzBmAkT"
}
// ... + error, type, size
}
}
With the full_path
information, it is possible to store the relative paths, or reconstruct the same directory in the server.
In the example above, $_FILES['myupload']['full_path']
array contains the path to the uploaded file, including its path. This is useful in contrast to the $_FILES['myupload']['name']
array that contains duplicate entries because both dir1
and dir2
contain a file with the same name file.txt
.
- The temporary path for uploaded files (
$_FILES['myupload']['tmp_name']
) continue to be unique, and is not stored in nested directories. It is safe to continue to use thetmp_name
values with themove_uploaded_file
function.full_path
array will be for all file uploads, including standard individual/multiple file uploads. It will be identical to thename
array in those cases.
Security Hardening
PHP only parses the relative path information submitted by the browser/user-agent, and passes that information to the $_FILES
array. There is no guarantee that the values in the full_path
array contains a real directory structure, and the PHP applications must not trust this information.
Values in
full_path
array are user-input, and must never be trusted.
Applications that accept a directory upload, and store the files as per the full_path
values must ensure the full_path
information is properly validated prior to moving the uploaded files to a nested directory.
Some of the threat models include:
- Directory traversal attacks, by submitting a path that traverses to a different directory. e.g.
foo/../../../etc/password
. - Resource exhaustion attacks, by submitting a path with deeply nested paths. e.g.
foo/a/a/a/a/a/a/a/a/a/a/a/a/a.jpg
, or a very long name that would trigger path limits on certain file systems. - Integrity corruption by submitting a file name that may not be created otherwise. e.g.
CON
is not an allowed file name Windows file systems.
To prevent such attacks, always make sure to limit the nesting level, ensure the file does not exist before writing, and disallow paths that allow directory traversal. Further, standard validations against the file contents (such as inspecting the file extension and mime types) must not be removed.
Backwards Compatibility Impact
full_path
array key in the $_FILES
super-global is a new feature in PHP 8.1. The existing name
array values are not modified, and they continue to only contain the file name without the file path.
Using full_path
in older PHP versions
Unfortunately, there is no easy way to bring that functionality to older PHP versions.
HTML file uploads require enctype="multipart/form-data"
attribute present in the form. With this attribute present, PHP's built-in php://input
stream will be empty because PHP itself populates the $_POST
and $_FILES
super-globals and empties the php://input
stream. This rules out fetching the browser-provided file path information by reading the php://input
stream.
However, the PHP INI directive enable_post_data_reading=0
disables PHP from populating $_POST
and $_FILES
super-globals, thus leaving the php://input
stream values untouched. As a far-fetched and strong discouraged approach, it is possible to parser the raw HTTP request and populate the $_POST
and $_FILES
array or PSR-7 HTTP Request objects. This information contains the browser-provided relative path to the file.
Some of the multi-part parsers include:
Alternately, it might be more straight-forward to progressively enhance user experience on web browsers by using a client-side JavaScript approach to submit the file contents and file paths in a separate field.