Don’t rely on PHP file upload permissions
If you write your own PHP scripts that allow file uploads, we’ve discovered an unusual issue that might affect you. The “permissions” PHP gives to newly uploaded files aren’t always the same — and a recent change to our servers may have altered the permissions your script sees.
PHP gives programmers a way to accept an uploaded file, store it in a temporary directory, then move it to a specific location using the move_uploaded_file() command. That’s all good — but what file permissions would you expect the resulting file to get? Most PHP documentation says that temporary files created by PHP will not be world-readable, so they’ll look like this:
That’s good security. It makes sure the files can’t be accessed by Web site visitors, for example, unless the programmer takes additional steps to make that happen.
On many servers, mode 0600 is exactly what you get after using move_uploaded_file(). However, if the initial temporary directory used by PHP is on a different “file system” than the final destination, move_uploaded_file() makes a copy of the file instead of moving it. And when it makes a copy, it sets the permissions of the copy to something very different:
Yikes! And since you have no real control over whether there’s going to be more than one file system involved (it’s effectively random from the script author’s point of view), this means you have no idea whether an uploaded file is going to be world-readable after using move_uploaded_file(). If you want to make sure it is — or isn’t! — you have to set that yourself, using something like chmod:
chmod ($filename, 0600);
chmod ($filename, 0644);
There probably aren’t many widely-used scripts that rely on the default permissions of uploaded files (otherwise the scripts would fail on some servers and work on others, and someone would notice and fix it). So this shouldn’t be a problem, right?
Well, until recently, most of our servers had their “temporary directory” on a different file system (it was actually a “tmpfs”). This caused a problem, though: sometimes when a server hadn’t been restarted for several months, the smaller temporary directory would start to fill up (the directories only get cleaned out when a server is restarted). This made us nervous: what if a temporary directory filled up and ran out of space? So we recently decided to stop putting the temporary directory on a separate file system, setting it to use the standard disk file system after the scheduled restart last Saturday night. It shouldn’t really matter where it is, so we didn’t even bother mentioning it.
But this afternoon, a couple of customers contacted us to let us know their PHP file upload scripts had stopped working. Not being familiar with arcane details of how PHP handles uploaded temporary files, we were extremely puzzled… until a perusal of the PHP source code revealed the trouble.
Now, what we originally told the customers who had this trouble still stands: regardless of the cause, everyone’s scripts should explicitly set the mode of any uploaded files if it matters. That’s the only way to guarantee your script works properly on any server.
However, we value consistency a great deal. You should be able to rely on your scripts continuing to work if it’s possible for us to make that happen. So we’ve “patched” our versions of PHP (both PHP 4 and PHP 5) to always make move_uploaded_file() act as if the file was copied (giving mode 0644). This should “fix” any scripts that were broken by this change, even if you can’t actually fix the scripts for some reason.
I should mention that this “solution” is still too random for our taste. There’s no guarantee that this behavior is compatible with what you’d get if you tried another company’s PHP, for example. What would be better is for PHP itself to make sure the mode is always the same, and for that to be fully documented so PHP users could expect the same behavior on all servers. We’ve opened a PHP bug suggesting that. If the PHP folks ever see fit to make that change, we’ll make sure our versions of PHP do what the PHP documentation says, even if it means making our servers start using mode 0600. So again: if the mode matters to your script, make sure your script sets the mode itself to avoid problems.
Followup: the PHP developers changed how PHP works in response to our bug report, forcing PHP to use mode 0644 for files created with move_uploaded_file().