User Uploads & Unix Folder/File Permissions


#1

I've had a hard time figuring out the best way to handle this issue ... I once set an upload folder to 777 only to have a hacker set-up a bank phishing page in the directory.

GOAL
The functionality I'm looking for is the ability to allow a user to upload an image via an html form <input type="file"> and then have the image displayed on certain public and private web pages. All of this without a security loop-hole.

WHAT I"VE GOT
I've got the form working fine.
I've got the PHP script working fine.
The upload is successful (when the directory is set at 777 or PHP owns the folder).
The image appears where I want it.

UPLOAD FOLDER
/public_html/uploads/

WHAT I TRIED
After the phishing expedition I took the 777 away and made it 755 but the PHP script wouldn't write to the directory. I discovered that this happens because the PHP script doesn't run under my website's uid but under its own. So I deleted the upload folder and had the script create the folder so that the folder would have the PHP uid as its creator - this solved the problem but then I read-up on phpsuexec and get the impression that it's possible for another PHP script on the server to "dive-into" that folder and do what it wants since the uid PHP is using is "nobody".

QUESTIONS

  1. Is it true that on a shared webserver using the PHP/Apache Module environment that a PHP script in a different home directory could actually get into my home directory if my files or directories are set to the PHP uid?[*]Does anyone have a secure upload model that would work in a PHP/Apache Module environment?
  2. Does anybody know if there's a method to change the PHP script uid to the website's uid?

Thanks in advance for your help!
Dan


#2

yes, when apache is running php as a module, php often runs as the same user as the webserver. so all "websites" on that server run php as the same user. this can mean everyone can read/write everyones files if using php. (same with your session files! scandir() your session.save_path if you wanna scare yourself)

php's safe_mode and open_basedir attempt to solve this. but, they only affect php, not other languages.

i dont know of any real solutions other than moving to a webhost that runs php as phpsuexec or suphp, or a vps etc...

maybe its possible to use a cgi script to do the reading and writing to a directory where only your user can. but im not sure how you would execute this cgi script and make it run as your user, ive never looked into it. however, that still will not solve others being able to read your php files, which you must leave world readable at least if you want php to work.

fyi- im definately not an expert on this. take my words with some salt.


#3

Hey clamcrusher thanks for your response. I'll continue with what I've found/done thus far but please don't feel obliged to post a response ...

I did a bit of reading about safe_mode and open_basedir, http://php.net/manual/en/features.safe-mode.php, and checked the web server's PHP settings with phpinfo() to verify the open_basedir limitation - it does exist and is limiting my access to my directory and the user library. My session tmp folder is in my home directory so I'm hoping this limits others' PHP access to session info - I'll look into that one a bit later.

  1. When you say
  2. For now I've created the upload folder using the script's uid and set the chmod to 744 which works well for allowing users to upload their files and then have those files appear elsewhere in the application. The idea here being that it's better to at least limit the world writeability to the directory that 777 would give it but then the directory is still open to PHP on the web server writing to it.
  3. With 744 permissions on a directory on my webserver is it possible for someone to send a PHP script to my website via http that would then run the script on my webserver under the PHP uid my web server uses?
  4. With 777 permissions on a directory on my webserver is it possible for someone to upload a file to that directory with their own PHP script running on their own webserver?
  5. After reading up a bit on .htaccess [http://www.javascriptkit.com/howto/htaccess8.shtml and [URL=http://httpd.apache.org/docs/2.2/mod/core.html#filesmatch]http://httpd.apache.org/docs/2.2/mod/core.html#filesmatch](http://www.javascriptkit.com/howto/htaccess8.shtml) I then placed an .htaccess file in the upload directory to block direct access to the directory but allow certain mime types to be served

Any thoughts?


#4

dan,

Interesting thread - thanks!

The Apache/PHP/MySQL user must be the group assigned to Apache, NOT you! You don't want to be the only person to upload files, obviously, so that's out of the question.

Uploads. They're uploaded to temporary directory and, IF UPLOADED (yes, there is a PHP command that checks that), it is then moved to the directory location you specify. IMHO, you MUST check the extension of the file before doing so. Because "nasties" can be embedded in image files, the truly paranoid among us would also ask GD to open the file in the correct format (GIF, JPG, PNG) and, if there is an error detected, DELETE the file rather than risk "a bad case of the nasties." If you want to allow uploads, that's about the best you can do - except make sure that the uploaded files are not 777 (universally executable) as image files should NOT be executable!

Did I miss anything else in your posts?

Regards,

DK


#5

Thanks for your reply DK.

I am using the PHP is_uploaded_file() to test for an actual uploaded file and then a check on the extension for a valid graphic extension as well as $FILES['uploadedfile_name']['type'] to check the mime type. I haven't used GD to test the image but will look into how to do that - after the phishing trip I became a bit paranoid stuck_out_tongue

I still have a few questions (hope you don't mind) ...
[LIST=1]
[*]How do I upload the file to a directory without 777 set on the directory?[LIST]
[*]As far as I can tell, the only way the server is allowing me to upload is if the upload folder that I created with my uid via FTP is set to 777. This is the scenario I had when the phisherman came by.
[*]My 'workaround', which I now think might be creating another security issue, was to create the folder with PHP using mkdir() in which case I can set the folder to 744 using PHP to issue a chmod().
[/LIST]
[*]Is creating the upload folder with PHP's uid via mkdir() and using it in the way I described a security risk?
[*]Assuming the upload folder is set to 777 with my uid not PHP's uid, do I run chmod() on the uploaded file to make sure the uploaded file is set to 644 (not executable)?
[LIST][*]It looks like the server is already setting uploaded files via FTP and via PHP uploads to 644 but it seems it would be prudent to make sure.[*]I also noticed that PHP can't run a chmod on files that are in folders I created with my uid unless the folder is set to 777.[/LIST]
[*]Is it helpful, from a security standpoint, to then put the .htaccess file in the upload directory[LIST][*]I mention this because without the limitation it looks like anyone could access any file in the directory e.g, if a php file got in there somehow it could then be accessed with the direct url.[*][/LIST]

<Files *.*>
order allow,deny
deny from all
</Files>
<FilesMatch "\\.(gif|jpe?g|png)$">
order allow,deny
allow from all
</FilesMatch>

[*]Is there a better way to write the .htaccess file listed above?[/LIST]


#6

dan,

I'd be paranoid, too! :shifty:

[quote="danNL,post:5,topic:2963"]
I still have a few questions (hope you don't mind) ...
[LIST=1]
[*]How do I upload the file to a directory without 777 set on the directory?

You don't need 777, all you need is 644 or 766. You do NOT want the x77 as that would make the files executable.
[LIST]
[*]As far as I can tell, the only way the server is allowing me to upload is if the upload folder that I created with my uid via FTP is set to 777. This is the scenario I had when the phisherman came by.
Not so! Set the (uploaded images) directory to 766 and see what happens.

[*]My 'workaround', which I now think might be creating another security issue, was to create the folder with PHP using mkdir() in which case I can set the folder to 744 using PHP to issue a chmod().
Definitely! You're giving the uploader their own directory - but the 744 should be okay is you don't want to have anyone "read" the file (for display!)

[/LIST]
[*]Is creating the upload folder with PHP's uid via mkdir() and using it in the way I described a security risk?
Using the mkdir() probably is but the 744/766 should ease that problem.

[*]Assuming the upload folder is set to 777 with my uid not PHP's uid, do I run chmod() on the uploaded file to make sure the uploaded file is set to 644 (not executable)?
Again, 766 is probably what you need and that should be what you get for all the files in that directory.

[LIST][*]It looks like the server is already setting uploaded files via FTP and via PHP uploads to 644 but it seems it would be prudent to make sure.
Amen!
[*]I also noticed that PHP can't run a chmod on files that are in folders I created with my uid unless the folder is set to 777.
Really? Did you TRY 766?
[/LIST]
[*]Is it helpful, from a security standpoint, to then put the .htaccess file in the upload directory
I'd put it as high as possible - meaning DocumentRoot or httpd.conf's VirtualHost section (see, I'm paranoid, too!).
[LIST][*]I mention this because without the limitation it looks like anyone could access any file in the directory e.g, if a php file got in there somehow it could then be accessed with the direct url.[*][/LIST]

<Files *.*>
order allow,deny
deny from all
</Files>
<FilesMatch "\\.(gif|jpe?g|png)$">
order allow,deny
allow from all
</FilesMatch>

[*]Is there a better way to write the .htaccess file listed above?[/LIST]
[INDENT]Yes! I think that the Files would prevent the FilesMatch from ever being allowed to display an image!

# .htaccess in upload directory (for images)

RewriteRule !\.(gif|jpe?g|png)$ - [F]

Fail to provide any file that is not a gif, jpg, jpeg or png.

`
[/INDENT]
[/quote]
Regards,

DK


#7

Hey DK, smile Thanks again for your continued input. smile

For now I'll focus on what I imagine is the 'normal' set-up on a webserver:

  • The upload folder is created by me via FTP using my uid.
  • There's no .htaccess that I've placed above or below the folder that affects its contents (I imagine the web host could have placed some restriction I'm not aware of above my home directory or in apache).
  • I've written a PHP upload script that after several validation steps attempts to move the tmp file to the upload directory.

No matter what chmod() permission I set that is not 777 I get the following error message:

Warning: move_uploaded_file(upload/a.jpg) [function.move-uploaded-file]: failed to open stream: Permission denied in /home/user_name/upload.php on line 145

Warning: move_uploaded_file() [function.move-uploaded-file]: Unable to move '/tmp/phpgZEXmR' to 'upload/a.jpg' in /home/user_name/upload.php on line 145

Any ideas on what I might be able to do?


#8

dan,

What you're telling me seems to be off-beat just a bit. PHP has it's own upload directory which you don't have to bother with (it's set in php.ini and will be beyond your control on a hosted server). All you need to do is use the move_uploaded_file() with two arguements, the $FILES['file']['tmpname'] of the uploaded file and the destination path/filename. For PHP to MOVE the file, all that's required is for it to find the 'tmp_name' of the file in its uploaded files directory and to be able to write ('x4x') to the path/filename (it WILL overwrite an existing file without warning!).

From what you're saying, it can't write to your path unless it's a 777 and that's just not right (the 7 says read, write and EXECUTE which you do NOT want to allow!). I can't explain why you're only successful with 777.

I have a couple of code bits in my sites that allow file uploads and will provide them via PM (if requested).

Regards,

DK


#9

Hey DK,

I set-up the script to work as you mentioned thinking it would work with the x4x permission but it didn't. I e-mailed the web host asking why the folder had to have 777 permission in order for a file upload to work and received this reply:

Please note that when uploading using php scripts the directory permission that is required is 777. It should be set else the files will not get uploaded.

I then e-mailed back asking how I can protect the directory from hackers and received this reply:

For this you will have to modify the coding of your software. To detect if any of the other files that the customer is not supposed to upload is getting uploaded. Also you need to check all the files apart from this directory for the permissions. No other folders or files should be having 777 permissions.
This will ensure protection against hacking . Also take regular backups of your files and databases just to be in a safer side.

From other areas on the Internet I've also seen this permission issue - where the host only allows uploads to a directory with 777 permissions. It looks like they're relying on the server to make sure the file itself only has 644 permissions to prevent execution and the programmer to properly check all uploads.

With this in mind it seems that it would be prudent to do the following after a file has been succesfully moved from the temp folder to the upload folder:
[LIST=1][*]Run a chmod('upload/filename',0644); on the file to make sure its permission is set to 644.[*]Put an .htaccess in the upload folder to limit access to certain extensions (this does not limit MIME Type as far as I understand it). These .htaccess statements do allow the images to be viewed (I think because of the order of the statements) and I like it because it starts out by denying everything :shifty: and then only allows certain extension types.

<Files *.*>
   order allow,deny
   deny from all
</Files>
<FilesMatch "\\.(gif|jpe?g|png)$">
   order allow,deny
   allow from all
</FilesMatch>

Does this one have the same effect?

RewriteRule !\\.(gif|jpe?g|png)$ - [F]
# Fail to provide any file that is not a gif, jpg, jpeg or png.

[*]To be even more paranoid I could use the ForceType but I don't know how to apply it to a directory with multiple MIME types - do you know how I could do that? If not, I could always use PHP to move the files into folders per MIME type and add this to the .htaccess file:

ForceType image/gif

thus the full .htaccess file could look like this per MIME Type directory

<Files *.*>
   order allow,deny
   deny from all
</Files>

<FilesMatch *.gif>
   order allow,deny
   allow from all
</FilesMatch>

ForceType image/gif

[*]With this in place the security loopholes, I think, would be:

  • Could a malicious file sneak by a GD check on it?
  • Could a malicious file sneak by an extension check e.g. even though it has an extension of .jpg it runs as a PHP script or something else?[*]Could a malicious file run even if it's served with the wrong MIME Type?

[*]All of this has been about images. While I know I can check for other file extensions e.g., .pdf, .doc, .avi, .mpg, etc. Is there a similar GD check for these MIME types as there is for images? [/LIST]

Thanks again for sticking with me on this smile
o Dan


#10

dan,

First, it sounds like your host is opening the door to security problems (i.e., a good host is recommended in place of these people).

Remember that the file is uploaded to some random (hidden) directory (with a RANDOM filename) from which you move it to your own directory where you can do your testing then move again to the image directory.

<Files *.*>
   order allow,deny
   deny from all
</Files>
<FilesMatch "\\.(gif|jpe?g|png)$">
   order allow,deny
   allow from all
</FilesMatch>

As I'd stated before, I doubt that FilesMatch would override the Files and allow ANY file, image or not, to be served. The following mod_rewrite will allow images and NOT anything else.

RewriteRule !\\.(gif|jpe?g|png)$ - [F]

Also, I doubt forcing a MIME type on a different type of file would help either. The 644 and mod_rewrite should be more than sufficient. If you're really paranoid, ask GD to open the image as the specified type and, if an error is detected, unlink (DELETE) the file.

Regards,

DK


#11

I had the same problem. Only a dir with 777 would allow php to move the image to its final directory.
(Ask your ISP to) switch on Mod_suphp (thats substitue user). This will substitute the 'nobody' to ur own UID. Now you can set the dir to 755. You need to chmod the image files though to 644.
Hope this helps...


#12