views:

464

answers:

4

I am using CGI to allow the user to upload some files. I just want the just to be able to upload .txt or .csv files. If the user uploads file with any other format then I want to be able to put out an error message.

I saw that this can be done by javascript: http://www.codestore.net/store.nsf/unid/DOMM-4Q8H9E

But is there a better way to achieve this? Is there is some functionality in Perl that allows this?

+6  A: 

The disclaimer on the site to you link to is important:

Note: This is not entirely foolproof as people can easily change the extension of a file before uploading it, or do some other trickery, as in the case of the "LoveBug" virus.

If you really want to do this right, let the user upload the file, and then use something like File::MimeInfo::Magic (or file(1), the UNIX utility) to guess the actual file type. If you don't like the file type, delete the file and give the user an error message.

jrockway
on the environment i am given, they do not have Magic. How can the file(1) utility be utilized in perl?
Just include the magic file with your app?
jrockway
+3  A: 

I just want the just to be able to upload .txt or .csv files.

Sounds easy, doesn't it? It's not. And then some.

The simple approach is just to test that the file ends in ‘.txt’ or ‘.csv’ before storing it on the filesystem. This should be part of a much more in-depth validation of what the filename is allowed to contain before you let a user-submitted filename anywhere near the filesystem.

Because the rules about what can go in a filename are complex on some platforms (especially Windows) it's usually best to create your own filename independently with a known-good name and extension.

In any case there is no guarantee that the browser will send you a file with a usable name at all, and even if it does there is no guarantee that name will have ‘.txt’ or ‘.csv’ at the end, even if it is a text or CSV file. (Some platforms simply do not use extensions for file typing.)

Whilst you can try to sniff the contents of the file to see what type it might be, this is highly unreliable. For example:

<html>,<body>,</body>,</html>

could be plain text, CSV, HTML, XML, or a variety of other formats. Better to give the user an explicit control to say what file type they're uploading (or use one file upload field per type).

Now here's where it gets really nasty. Say you've accepted the upload and stored it as /data/mygoodfilename.txt, and the web server is correctly serving it as the Content-Type ‘text/plain’. What do you think the browser interprets it as? Plain text? You should be so lucky.

The problem is that browsers (primarily IE) don't trust your Content-Type header, and instead sniff the contents of the file to see if it looks like something else. Serve the above snippet as plain text, and IE will happily treat it as HTML. This can be a huge problem, because HTML can include client-side scripts that will take over the user's access to the site (a cross-site-scripting attack).

At this point you might be tempted to sniff the file on the server-side, for example using the ‘file’ command, to check it doesn't contain ‘<html>’. But this is doomed to failure. The ‘file’ command does not sniff for all the same HTML tags as IE does, and other browsers sniff differently anyway. It's quite easy to prepare a file that ‘file’ will claim is not HTML, but that IE will nevertheless treat as if it is (with security-disaster implications).

Content-sniffing approaches such as ‘file’ will give you only a false sense of security. This is a convenience tool for loose guessing of filetypes and not an effective security measure.

At this point your last desperate possibilities are things like:

  • serving all user-uploaded files from a separate hostname, so that a script injection attack can't purloin the credentials of your main site;

  • serving all user-uploaded files through a CGI wrapper, adding the header ‘Content-Disposition: attachment’ so that browsers won't attempt to display them directly;

  • only accepting uploads from trusted users.

bobince
What browsers (besides IE) don't respect the Content-Type: text/plain header?
cjm
At least Firefox and Opera (though their sniffing is less wide-ranging). WHATWG are even trying to standardise it: http://www.w3.org/TR/html5/infrastructure.html#content-type-sniffing-0. How ugly and depressing this all is.
bobince
+2  A: 

On unix the easiest way is to do an JRockway suggested. If not on unix then your options are limited. You can examine the file extension and you can examine the contents to verify. I'm assuming for you specific case that you only want "* seperated value" text files. So one of the Text::CSV::* modules may be useful in verifying the file is the type you asked for.

Security for this operation is a whole other ball of wax.

Jeremy Wall
A: 

try this:

$file_name = "file.txt";

$file_cmd  = "file \"$file_name"\";

$file_type = `$file_cmd`;

return 0 unless($file_type =~ /(ASCII|text)/i)