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

I am writing a script that will allow teachers to upload files (called activity sheets) via a Web form. The files are then subsequently written out to a directory on the server which has Web access forbidden to everyone via an .htaccess file. Even I, who have root access, cannot access the file through a browser. The file cannot be viewed by the general public until I have manually examined it and determined that it is exactly what it is supposed to be (word processing documents, images, and spreadsheets. Nothing else is allowed) and has no macrovirii. I think this part is relatively secure.

My humble supplication for information from wiser monks is this: what type of security holes exist with a browse button? The relevant Perl code:

. . . use CGI; my $template = new CGI; . . . sub storeSheet { my ($sheet, $file, $data) = @_; my ($path, $filename); my $storagePath = "/www/htdocs/documents/activity_sheets/"; # Here we extract the filename and file $path = $template->param($sheet); $path =~ m!([^/:\\]*)$!; # capture filename $filename = $1; no strict 'refs'; # $filename is a symbolic reference open (SHEET, ">${storagePath}OSEN${filename}") || die "Can't open +$filename: $!\n"; while (read($path, $data, 1024)) { print SHEET $data; } close SHEET; use strict 'refs'; }
I had a devil of a time just finding information about how to do this. I have two concerns.
  1. What dangers are involved in my using "no strict 'refs';" here and how can I avoid this?
  2. Users can supply their own path information. I'm not sure how security works here. Can they include a path with backticks and execute their own code? Is there anything else I need to know about this>
Any wisdom on this matter would be greatly appreciated. If their are other concerns, I would love to know about them.

Incidentally, I've reduced this to a minimal test case, so my apologies if I missed anything in the code.

Cheers!

Replies are listed 'Best First'.
Re: File Upload Security Question
by Russ (Deacon) on Jun 11, 2000 at 23:08 UTC
    Hmmm....

    I NEVER allow users to set the filename on my server. You are stripping slashes from the user-supplied name, so you have the right idea. But, why open any security hole on your server?

    Provide your own filename for any upload (since you do not allow outside access to these files, you could simply number them sequentially -- we normally create random filenames anyway), and then tie the real filename to the user's intended filename with a database, etc.

    To expand on your actual question ("what type of security holes exist with a browse button?"), the issue is not the browse button. You can be relatively confident when planning for the data from a browse button (file upload). The people about whom you should worry are those who will submit to your server without using the built-in tools. These people will send you something the browse button never will. That's why we have to be paranoid, and never trust anything from userland.

    It's a common misconception in most newbie CGI'ers: just because you provide (or don't provide) a button or field or a certain piece of data, you may never trust it. A user/would-be-hacker can easily submit false information to you. If you send a page with a Client Number in a hidden field, for example, you cannot trust that Client Number when performing any operation. Anyone could have forged it. Is it "The X-Files" with the motto "Trust No One!"? Believe it when doing CGI.

    Russ

      I'm of a similiar opinion. But going one step better, don't use files at all. Use a database. Then you can attach all sorts of extra information that a file has no facility for (comments, user, number of downloads, yada yada yada). And DBs typically give you better flexibility if you have to start splitting machines to store data off from the webservers, etc.

      DBs also support atomic operations more readily. How are you going to handle two users uploading the same filename at the same time, assuming you don't support a mechanism like Russ proposes? The window for error gets wider if users are running on slow links (What?!? Everyone doesn't have a personal OC-12?)

      --Chris
        Okay, I have a slight confession to make. I was going to store the files in a database but I didn't know how to serve them back out to the users (hanging my head in shame). Since I have a very tight deadline (they start using my work the day after I am writing this), I just quickly went with what I knew to get it up and running.

        I'll be revisiting this after a week or so to clean things up (they'll use it for a week and then stop), so if anyone can point me in the right direction for serving files directly out of MySQL using CGI.pm and DBI.pm, that would be great.

        Also, I'm just storing the files directly, I do have a table in my database which stores the other information you mentioned. Wouldn't it be faster have the user link directly to a file rather than a script that will serve the file?

        As a side note, the group I am doing this for (who will remain nameless for legal reasons) is a non-profit group who couldn't afford to hire a really high-end Web development company, so I'm doing it for them for next to nothing. I think that's a mistake I'm not likely to repeat.

      The randomly generated filename works out nicely with what I am doing. The files are indexed with a table in a MYSQL database with autoincrement ids. I'm already adding the unique id to the title, so I suppose it's just a matter of stripping the user-supplied filename from the server filename.

      Thanks for the info!

      While Ovid has already changed this behavior, I'll give an example for anyone with a similar situation who may be reading the thread:

      Using your script (I believe), the user could upload a .htaccess file. The user could also upload a cgi file (for example), that the .htaccess allows to be run, and Poof!, the user now has full access to whatever the webserver id can do (On most systems this is limited, but does include just about everything on the website). In Ovid's example, the user could get access to the database, and twiddle any bits there (grades/scores?)

        I am also interested in giving users the ability to upload files to my website. Is it safe to allow users to upload a file as long as I name the file myself? I have an art business and I usually do drawings from photos. Ideally, I would like visitors of my site to be able to submit an image file to my site so I could give them a price quote for a drawing (or painting, etc). Do I need to take other precautions as well? Thanks, Rad (an humble newbie in Dallas)
      As a follow-up: I am nearing completion of a GPL package to provide secure file trading via web interface. It will offer temporary, expiring "visitor" accounts for outsiders' use, and will handle file clean-up on expiration, etc.

      This was just a sideline project, but based on the interest shown here, I now intend to finish it as quickly as I can.

      /msg me or send email if you're interested...

      Russ

Re: File Upload Security Question
by plaid (Chaplain) on Jun 11, 2000 at 01:03 UTC
    A few thoughts:

    The $file variable which is the second parameter being passed in never gets used anywhere. I don't know if this is because the part of the code using it has been stripped out though.

    The $data variable, third parameter passed in, just gets overwritten in the while loop. Again, maybe something important has been stripped from the code that I'm missing.

    I don't see how $filename is a symbolic reference. A symbolic reference only occurs when you're using one variable's value to get the name for another variable. The code worked fine for me without the 'no strict' lines.

    The danger in this code lies in the open call. open allows some powerful constructs in it, such as piping output to another program, redirecting output, etc. Take a look at perlsec for a detailed description.

      The issues with the $file and $data variables that you pointed out were caused by my stripping this down to a minimal test case and overlooking them.

      You mentioned that you had no problem using the code without the "no strict" lines. Were you using this to upload a file from a Web page and using CGI.pm? That's where I seem to get the symbolic reference problem.

      I'll take a closer look at the perlsec doc and see if it addresses the problem. Thanks.