Baz has asked for the wisdom of the Perl Monks concerning the following question:

This is the code ive just managed to get working - it allows a client to upload a file to the . directory on the server
In order to get this to work, I had to chmod the current working directory to 777. This isnt wise is it?. Is there a way of temporarily changing the permissions of a directory for the purpose of allowing a file to upload?, or something of that nature. In otherwords what improvements can I make to the code below in order to make it more secure.
#!/usr/local/bin/perl use strict; use CGI; my $query = new CGI; my $dir = $query->param('dir'); my $file = $query->param('file'); $file =~m/^.*(\\|\/)(.*)/; my $name = $2; my $error; #open(LOCAL,">$dir/$name") or $error = $!; open(UP_FILE,">$name") or $error = $!; while(<$file>) { print UP_FILE $_; } print $query->header(); print $query->start_html("File Upload"); # DEBUG TO SCREEN print $query->br,$error,$query->br,$file,$query->br,$name; print $query->end_html;

Replies are listed 'Best First'.
Re: File upload and directory permissions
by dsheroh (Monsignor) on May 10, 2002 at 20:07 UTC
    The proper way to do it would be to have it saving the files to a directory where the user apache (or whatever web server) runs as has write permission. chmoding the directory to 777 is overkill... Ideally, you could use suexec with apache, but that may not be an option depending on who runs the server. Worst case, I'd suggest you create a new directory solely to receive uploads from your script and set its permissions to 733 (anyone can write to the directory, but only you can read it) which will at least prevent people from seeing what's there. .htaccess can probably also be used to help keep prying eyes at bay.

    Looking over the code, I see one glaring problem: You're too lax when untainting $file. You also need to check that it doesn't start with a / or contain .. unless you want to let people upload files to /bin/bash or ../../../bin/ls. (If the Apache module is available, you can use server_root_relative($file) to clean up incoming paths.) Also note that the regex you're using to untaint $file will happily accept 'path/to/somewhere/deeper/than/you/want', although I suspect you don't want it to. If you want $2 to end up with only the final component of a path, use /\/([^\/\\]+)^/ instead.

    I suppose the big question, though, is "What are you trying to accomplish?" TIMTOWTDI, but the way you've chosen seems a bit awkward. There's probably an easier way to do what you intend.

Re: File upload and directory permissions
by deekoo (Initiate) on May 11, 2002 at 09:49 UTC
    Assuming that you have a normal user account and the webserver runs as someone else, your best bet is to make the directory the script is saving files in sticky (1777 or 1733). You also want this dir to be something other than the current dir, and to be somewhere where CGI/shtml/php/other dynamic content can't be run - you don't want someone uploading arbitrary programs, and I suspect some server-interpreted dynamic formats may well be runnable even when not executable.

    Also, the regexp should constrain filenames to safe characters (something like =~/^.*?(a-zA-Z0-9.+)$/ will work - the current one won't strip MacOS paths (Volume:Folder:File), and will pass various things that could, depending on filesystem and what you use to view the directory, range from mildly annoying to security risks.)

    And open(FILE,">filename"); will follow symlinks and overwrite existing files. sysopen() with O_CREAT|O_EXCL (and O_NOFOLLOW if your OS supports/needs it) is safer.