Re: PHP Move Uploaded File

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux