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:

0600 (-rw-------)

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:

0644 (-rw-r--r--)

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);

Or:

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().

13 Comments

  1. Thanks Robert, as a php developer find your article usefull for me=)

  2. This is very helpful to me

  3. +Kudos go to you also for that usefull info 😀

    Will bear that in mind for future developments.

  4. Thanx, but couldnt solve the problem, even adding chmod( $uploaded_file, 0644 ); as i want all uploaded files to be accessible…

    Maybe iam putting my chmod command in the wrong place??? any ideas…

  5. Tarek,

    We’re unfortunately not quite understanding your question. It sounds like you need help with a PHP script that isn’t working, but it doesn’t look like you’re one of our customers.

    You should contact your hosting company if their PHP “move_uploaded_file()” doesn’t work correctly. They’ll be able to help. (If we’ve misunderstood and you ARE one of our customers, please contact us using the “Contact” link at the top of this page.)

  6. Hi,

    Nice article! I have a related problem, if you have any solution for it.

    I have WAMP install on my PC. file uploading works fine on it, but when i move the code to any shared web server for final hosting, it stops uploading files and gives the following error

    “failed to open stream: Permission denied in /home/asadccom/public_html/gallery/saveimage.php”

    I also change the permissions of the folder where files are uploading but still the same error,

    Hope anyone have solution for it?

    Regards

    Asad

  7. This might help you Asad.
    I found out just recently that using an FTP client to change permissions on a remote server doesn’t always work. Sometimes you have to login through the control panel or use IIS to change the file permissions. =P

  8. This was exactly what I needed to figure out my permissions issue. Added chmod ($filename, 0755); and everything worked as desired. Many Thanks!!!

  9. nice work useful article thanks

  10. Thanks. I was trying to figure this out at work today.

  11. The part of the chmod function is ok, but the filesystem part really makes no sense.
    are you sure you don’t have an umask setting somewhere?

    for example, suphp can change the default php umask from /etc/suphp.conf

    # grep umask /etc/suphp.conf
    ;umask=0077
    umask=0022

    0022 will generate 644 files, while 0077 will generate 600 files.

  12. wachasaywachasaywa wrote:

    >The part of the chmod function is ok, but the filesystem part really makes no sense. are you sure you don’t have an umask setting somewhere?

    The original problem was that PHP internally changed the umask to 0077 while creating the file on one file system (then restored it), but didn’t change the umask if it needed to copy the file across file systems.

    So using something like “umask=0022” in suPHP didn’t help. PHP used to override the umask to 0077 anyway if a single file system was involved. (It doesn’t do that any more since they fixed the bug.)

  13. Thanks for the article. It helped me resolve my issue when the support team a godaddy was clueless.