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

Hello, I have a question concerning a simple, but effective, file uploading script. I wish to incorporate the ability to enter a specific "sub directory" of the $basedir to upload files to. I am very new to programming in perl and am trying to learn. Any help would be greatly appreiated. Here is the html and .pl script that I am working with:
#!/usr/bin/perl #$basedir = "/home/sites/web/directory/"; $allowall = "yes"; $theext = ".gif"; $donepage = "up2.html"; ## DO NOT EDIT OR COPY BELOW THIS LINE ## use CGI; $onnum = 1; while ($onnum != 11) { my $req = new CGI; my $file = $req->param("FILE$onnum"); if ($file ne "") { my $fileName = $file; $fileName =~ s!^.*(\\|\/)!!; $newmain = $fileName; if ($allowall ne "yes") { if (lc(substr($newmain,length($newmain) - 4,4)) ne $theext){ $filenotgood = "yes"; } } if ($filenotgood ne "yes") { open (OUTFILE, ">$basedir/$fileName"); print "$basedir/$fileName<br>"; while (my $bytesread = read($file, my $buffer, 1024)) { print OUTFILE $buffer; } close (OUTFILE); } } $onnum++; } print "Content-type: text/html\n"; print "Location:$donepage\n\n"; ################end pl############ <html> <body> <form method="POST" action="upload.cgi" ENCTYPE="multipart/form-data"> File 1: <input type="file" name="FILE1"> <br> File 2: <input type="file" name="FILE2"> <br> <input type="submit" value="Upload!"> </form> </body> </html>

Replies are listed 'Best First'.
(Ovid) Re: File Upload To Selected Directory
by Ovid (Cardinal) on Sep 26, 2000 at 20:18 UTC
    I am not going to downvote your node because you have made it clear that you are new to Perl. However, your script has some serious security problems (amongst other things). It will take a while to work on, so I'll just say that the first two lines of your CGI programs should always be
    #!/usr/bin/perl -wT use strict;
    Please see perlsec for some details about security. One general rule is that you do not allow anyone to name files on your system. Generate a unique filename and use that. NEVER TRUST USER INPUT!!!

    Also, you should check your open statement for success.

    I'll post more later after I've cleaned up your code a bit.

    Cheers,
    Ovid

    Update:Here's a somewhat cleaner version of your script. It has the following features (but is untested!!!):

    • It passes strict and warnings.
    • Random filenames are created, thereby avoiding security a huge security problem in allowing user data near the shell.
    • It has a max file size, so users can't upload 100 meg files.
    • More accurate checking of MIME type.
    • Selected download directories.
    To use the download directory feature, add some HTML like the following:
    <SELECT name="path"> <OPTION value="personal">Personal Images</OPTION> <OPTION value="impersonal">Impersonal Images</OPTION> </SELECT>
    The Perl resembles the following:
    #!/usr/bin/perl -wT use strict; use CGI; use Fcntl; use constant BUFFER_SIZE => 16_384; # Amount of upload file to + read at one time use constant MAX_FILE_SIZE => 1_048_576; # This is the filesize upl +oad limit use constant UPLOAD_DIR => "/home/sites/web/directory/"; $CGI::DISABLE_UPLOADS = 0; # Temporarily reenable upl +oads $CGI::POST_MAX = MAX_FILE_SIZE; # This will stop someone f +rom uploading # a fifty meg file to your + system my $req = CGI->new; my $theext = "gif"; my $donepage = "up2.html"; my %upload_path = {personal => 'personal/', impersonal => 'anotherpath/'}; my $path = $req->param('path'); if (! exists $upload_path{$path}) { # Oops! It's not in our hash. Someone was being naughty! print $req->redirect("some_error_page.html"); exit; } $path = $upload_path{$path}; UPLOAD_FILE: { for my $onnum (1..10) { my $file = $req->param("FILE$onnum") or next UPLOAD_FILE; if ($file) { my $buffer; my $file_handle = $req->upload( $file ); my $format = $req->uploadInfo($file)->{'Content-Type'}; # In the following regex, we're getting the image type of +the MIME type. # This is better than checking the extension because if th +ey upload from # a system that doesn't use extensions - or if the user's +redefined their # extensions, we'd have problems. $format =~ s!^image/([a-zA-Z]+)$!$1!; if ($format !~ /$theext/o) { next UPLOAD_FILE; } my $fileName = ""; # Create a random filename. Keep running the loop if the +filename exists, # or if $fileName is false. while (! $fileName or -e UPLOAD_DIR.$path.$fileName) { $fileName = ""; my @myarray=('a'..'z','A'..'Z','1'..'9'); for (1..8) { $fileName .= $myarray[rand(@myarray)]; } $fileName .= ".$theext"; } # This will create the new file sysopen OUTFILE, UPLOAD_DIR . $path . $fileName, O_CREAT o +r die "Can't open UPLOAD_DIR$path$fileName: $!"; while ( read( $file_handle, $buffer, BUFFER_SIZE ) ) { print OUTFILE $buffer; } close (OUTFILE); } } } # Send them to the confirmation page. print $req->redirect($donepage);
    You should also check your directory size, to make sure someone doesn't upload 1000 1 meg files.

    You'll also need CGI.pm version 2.47 or above to use the upload method.

    I've also added a quick hack to allow users to choose subdirectories from a pre-approved list. It's untested and not great, but again, no user data gets to the shell. Using this method, you'll need to create those download directories in advance and make sure that they are writeable by the same user your script is running under (probably user "nobody").

    Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

Re: File Upload To Selected Directory
by Fastolfe (Vicar) on Sep 26, 2000 at 20:26 UTC
    In addition to the items Ovid covered above, which I agree are extremely important and perhaps should take precedence over figuring out this other issue, I have this:

    You basically want to do something like this:

    open (OUTFILE, ">$basedir/$fileName");
    and turn it into this:
    open (OUTFILE, ">$basedir/$subdir/$fileName");
    where $subdir is the subdirectory you wish to place your files under $basedir. If this directory is not guaranteed to exist, you will want to be sure to create it first:
    mkdir("$basedir/$subdir") unless -d "$basedir/$subdir"; open (OUTFILE, ">$basedir/$subdir/$fileName") or die "Could not open $basedir/$subdir/$fileName: $!";
    If you're going multiple levels deep, with subdirectories under subdirectories, you'll need to (perhaps recursively) be sure each parent directory is created first, and only then try to save the file to it. Hope this helps.
Re: File Upload To Selected Directory
by swiftone (Curate) on Sep 26, 2000 at 20:29 UTC
    Security is a huge issue here.

    Many will tell you not to allow the user to select filename/path for their upload. (You decide, and save their choices in a database to let them retrieve the file based on the "vitural" path they selected). In many cases though that's not practical. So make sure the filename is COMPLETELY safe for your system if you can't avoid using the user selections. This means:

    1. Making sure there aren't more "\" in the path.
    2. Making sure the path has only valid characters
    3. Making sure the filename is valid, with only valid characters
    4. Making sure the file doesn't already exist (unless you want to allow overwriting)
    5. Making sure the path doesn't contain ".."
    And these are just the ones I could think of immediately. For example, I could upload \..\..\..\WINDOWS\COMMAND.COM and mess up your system. Or I could upload a IIS.INI (or whatever files IIS uses to control its settings) and get permission to do whatever to your files.

    You can see File Upload Security Question by Ovid for a more intensive analysis of how paranoid you need to be about security.

      Oh there are more, lots more. For instance if you just do:
      open (FILE, $file) or die "Cannot read $file: $!";
      someone can pass a string that turns into a system command. You won't ever catch all of the possible nasties (fun stuff can be done with \0 for instance) which is why you need to make a list of what you explicitly permit and only pass that, rather than plugging the holes individually as you learn them.
        you need to make a list of what you explicitly permit and only pass that,

        There is no way to say it better than that, so I'm just replying to draw extra attention to it. tilly++

      Also, let me point out that the File Upload Security Question was posted by me 3 1/2 months ago. Compare my upload script (and stupid newbie comments) from then as compared to now and you'll see how fast your Perl knowledge can grow on a steady diet of Perlmonks and caffeine :)

      Cheers,
      Ovid

      Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

Re: File Upload To Selected Directory
by koacamper (Acolyte) on Sep 27, 2000 at 02:08 UTC
    One thing that I failed to mention in my original post was that the above perl/html files are part of a program that does the following:
    ~creates a new directory
    ~writes a .htaccess file to the new dir
    ~writes a .htgroup file to the new dir
    ~writes a .htpasswd file to the new dir

    The main thing that I am trying to figure out is how to adjust the script to have the ability to upload to the newly created directories from a browser....once logged in.

    I can also set up the script to write the the "upload perl script" and "upload.html" to the newly created directory which raises another question...how can I set the newly created perl script's $basedir to point to the new dir? create a $subdir variable?

    Thank you for all of your input

    koa

    i want to learn