Can $_FILES[...]['size'] be forged?

file-upload, php, security

Solution

As suggested by @Dagon in the comments, I checked the PHP source in rfc1867.c.

The lines involved in defining the `[size]` attribute are:

[1042] wlen = write(fd, buff, blen);
       ...
[1056] total_bytes += wlen;
       ....
[1242] ZVAL_LONG(&file_size, total_bytes);
       ...
[1270] snprintf(lbuf, llen, "%s[size]", param);
       ...
[1275] register_http_post_files_variable_ex(lbuf, &file_size, ...

Which I translate as:

- 1042 The temp file is written in `wlen` size chunks

- 1056 In each iteration, `wlen` is added to `total_bytes`

- 1242 `total_bytes` is assigned to the `file_size` zval

- 1270 The target variable name `...[size]` is assigned to `lbuf`

- 1275 `file_size` is registered under the name contained in `lbuf`, `...[size]`

So without doubt, the only variable ever assigned to `$_FILES[...]['size']` is the actual number of bytes written to the temporary file whose path is assigned to `$_FILES[...]['tmp_name']`.

As far as I can see, there is no way to forge the `size` attribute.

Problem

There's a well-known caveat about not trusting the MIME type sent via file upload in PHP (`$_FILES[...]['type']`) as this is sent by the HTTP client and could therefore be forged. There's a similar caveat for the file name (`$_FILES[...]['name']`), which is sent by the HTTP client and could contain potentially dangerous characters. However, I can't see how the file size (`$_FILES[...]['size']`) could be forged, as it does not seem to be part of the request payload, at least I can't see it in the dev tools in Chrome, where the payload looks like: ``` ------WebKitFormBoundarytYAQ3ap4cmAB46Ek Content-Disposition: form-data; name="picture"; filename="picture.jpg" Content-Type: image/jpeg ``` Original file name and MIME type are here as expected, but no sign of a size parameter. Still, I've just stumbled upon Symfony's `UploadedFile` implementation, that considers the file size as client-originated and therefore not trustable: UploadedFile::getClientSize() Returns the file size. It is extracted from the request from which the file has been uploaded. Then is should not be considered as a safe value. Can the file size be part of the request payload, and therefore be forged, or is it always inferred from the actual file pointed to by `$_FILES[...]['tmp_name']`, and therefore always trustable?

Original source