On Thu, August 25, 2005 10:24 pm, germ wrote: I'm cc-ing php-general since this belongs there, not in direct email. > Hi. I read your comment on php.net about handilng file uploads. You > seem to be one of the few to recognize the security issues regarding > uploaded files. Actually, I'm one of many -- I'm probably just more vocal about it than most. :-) > I was wondering if you could help me with an issue of > mine: how to secure an upload directory on a shared server? > > My shared server gives my owner as "loginname" group as "loginname". > So, far I have not been able to find a way to change ownership and > group names of directories. Only 'root' can change ownership/group on a file. And that's good, because otherwise you could write a nasty script suexec/world-executable, change the owner to somebody else, and then wreak havoc with their data. So you can just stop trying to find a way to chown/chgrp files/directories. If you did manage to make that happen, you've got MUCH bigger problems than the PHP scripts. > The public_html directory is 755. I have the upload.php script there. Fine. > I am uploading all files to public_html/upload. I also need to do some > image editing on the files in public_html/upload. But I can't have the > files write to its child directory, unless i give it that upload > directory 777. So is there a way to pw protect this directory so that > only the script in the parent directory that knows the login info can > write to the child directory? Do *NOT* use public_html/upload You probably currently have something like this: public_html/ (with .htm, .php, and 'upload') cgi-bin/ [some other directories, maybe] Make it like this: public_html/ cgi_bin/ upload/ So now, if somebody manages to upload something nasty with PHP (or Perl or Python, or...), they CANNOT surf to it, so the web-server won't just execute it unless you take MORE steps to move it into your public_html. There may be files, after due consideration, that you DO move into public_html, but that should only be after they've sat in ~/upload for awhile, as a staged process, and, in particular, you're as certain as you can be that the files are not malicious. > Also, if you have any alternatives, please let me know. But remember I > can't change ownership or group names. Your files/directories are owned by 'loginname' Let's assume, for the sake of argument, that the Apache User (which is what runs PHP) is 'www-runner' It's often 'nobody', or 'www', or 'apache', or something similar, but we'll use 'www-runner' as an example here. Here is one thing you can do: Step 1. TEMPORARILY make your new 'upload' directory be 777 chmod 777 ~/upload Step 2. Write a PHP script to create a directory inside ~/upload for your specific application: <?php umask('022'); //So the new directory will be chmod 755 //You may want to use '027' to achieve 750 //This will mean ONLY 'www-runner' can see what's in the directory mkdir("/full/path/to/your/home/directory/upload/foo_images") or die("Can't make foo_images"); ?> Surf to the PHP script above. Because you surfed there, that script will run as the PHP User 'www-runner' (or whatever it is on your server) Step 3. Change your upload directory back to some sane settings. chmod 755 ~/upload Step 4. If you now do: ls -als ~/upload you will see that you have made Apache/PHP create a directory owned by 'www-runner' which is correctly set at 755 You also should do this to be 100% certain nobody snuck something in there during the brief period while it was 777, as unlikely as that is. This does not, of course, COMPLETELY eliminate access to your data -- Most shared hosting services have the same PHP user for all customers. So if other customers write a PHP script to look at your ~/upload/foo_images, they will be able to see what is in there, and even change it. There's really no way to fix that unless your host creates a different PHP User for each customer, and runs a separate pool of HTTP servers for each -- which reduces the number of customers they can cram onto one box, since the sharing of Apache children and other resources is fragmented. So most hosts don't do this, as it cuts into their bottom line, and very very very few people shopping for a webhost have this Feature on their shopping list -- and if they did, even fewer webhosts have this Feature on their bullet-point list where they describe their package. Which is a pity. Maybe we just need a really cool-sounding Marketing bullet-point NAME for this, so the good hosts have something to point to on their bullet-point for potential customers. :-) Anyway, the above process *DOES* keep out the casual user from a command-line (shell) since they are not 'www-runner' ; they are 'loginname2' or somesuch. Now, of course, you have all these images that aren't in the public_html, so you can't make an IMG tag to get to them. You could write a PHP script to display images. There are a few zillion examples out there, but the simplest script would be: <?php $filename = $_GET['filename']; readfile("/full/path/to/your/home/dir/upload/foo_images/$filename"); ?> If you want to have more browser-compatibility with ancient and very broken browsers, you can use the PATH_INFO trick I've posted on php-general a few times. You could also run some quick checks in this script to be certain that the image you are sending out is legitimate. You could, for example, check: 1. Is the image in a list of known uploaded images in your database? This means that somebody else on your shared server would have to hack into your database somehow *AND* get an image into that directory, all without going through your formal upload process, to get something that bypasses your Security check-points. 2. Does the image return reasonable values from http://php.net/getimagesize This would make sure that at least the first few dozen bytes of the file LOOK like an image. That doesn't guarantee that somebody couldn't craft a malicious file whose first few dozen bytes LOOKED like a valid image, but that's much harder than just crafting a malicious file and getting your server to spit it out. If your server is too busy to have PHP as a gate-keeper for images, *AFTER* the upload has completed, and *AFTER* you have thoroughly checked the file uploaded to be certain it is NOT malicious using move_uploaded_file and basic tests/checks above, you can move it with http://php.net/rename from the upload directory to ~/public_html/uploaded_safe_images or something. You might also want to run it through image magick (aka 'convert') to make a thumbnail, just to make sure that the whole image "seems" valid enough that it can be processed -- Even if you just throw away the thumbnail, the fact that Image Magick was able to process the whole image gives you a very good probability that it's not a malicious file. The point of all this is to make sure you examine the images as carefully as you can before those images get into your public website, or get spit out by your server via PHP as if they were valid images. The downside is that images that "seem" to be valid, to the Gimp, to the browser, etc, may very well be "damaged" in some non-obvious way that makes Image Magick or getimagesize "hiccup" -- This usually occurs because the image isn't completely valid, but Gimp and the browser are more forgiving. Or, in an extreme example, less experienced users might take an AOL "ART" format image, change the extension to "jpg" and it "works" in some browsers and for some image editors. Mainly because the "ART" format is nothing more than a thinly-disguised wrapper around a JPEG or a GIF, whichever is smaller. To save a few bytes on their caching servers (ugh!) AOL converts each GIF/JPEG to a JPEG/GIF and takes the smaller file size, and crams it into an "ART" file format they made up out of thin air. The only way to un-package an "ART" file I am aware of is to install AOL (blech!) and then run it, and use the close box to dismiss the "logon" dialog, and then you'll have a Tools menu or somesuch you can use to open, edit, and "Save as..." the "ART" image to a real JPEG (or GIF). I've personally also noticed that EXIF data sometimes seems to confuse PHP, Image Magick, and getimagesize. This may be a versioning issue where newer versions of everything would handle the EXIF data. But I generally am quite happy to just strip out the EXIF data, which rarely contains content I personally find useful in the first place. You may not need to do all of this, of course -- If the only person who uploads images is the site owner or trusted people, you probably don't need to worry about them sabotaging their own site, and can just blithely copy the images around without checking that they are valid, though you still want to avoid 777 permsissions in your public_html, or anywhere else, for that matter. If, however, you have employees who might become former employees, possibly even disgruntled former employees, and they are the ones uploading the images, maybe you should do all this. If you have an e-commerce site, and you are on a shared server, and your PHP User is the same as other customers on that server, maybe you shouldn't even allow uploaded images/files -- Or you should make your tests of valid files be VERY stringent, or perhaps the process should include a human review of each image to be certain it *IS* an image before it gets moved into your public_html. Security is not an on/off switch -- It's a gradient and a frame of mind. You can't do this sort of thing as one-size-fits-all or even JUST following "Standard Practices", though obviously you want to follow the standards to some degree. You have to actually turn your brain on and think. :-) -- Like Music? http://l-i-e.com/artists.htm -- PHP General Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php