Bugtraq mailing list archives
potentially dangerous behaviour in CGI_lite.pm file upload
From: andrew () SQUIZ CO NZ (Andrew McNaughton)
Date: Tue, 12 Aug 1997 20:47:30 +1200
In recent versions of CGI_lite.pm, files uploaded via multipart/form-data MIME encoding is saved using a filename which may be unsafe (ie contain shell commands). While CGI_lite's operations on these files are apparently safe, file upload scripts based on CGI_lite may not be. This problem has been tested and confirmed with version 1.7, and it also appears to affect versions 1.62 and 1.8. If you don't use it, You might want to disable the upload system altogether in this module. ie comment out lines as follows in the parse_form_data() routine if (!$content_type || ($content_type eq 'application/x-www-form-urlencoded')) { local $^W = 0; read (STDIN, $post_data, $content_length); $self->_decode_url_encoded_data (\$post_data, 'form'); return wantarray ? %{ $self->{web_data} } : $self->{web_data}; # } elsif ($content_type =~ /multipart\/form-data/) { # ($boundary) = $content_type =~ /boundary=(\S+)$/; # $self->_parse_multipart_data ($content_length, $boundary); # # return wantarray ? # %{ $self->{web_data} } : $self->{web_data}; } else { $self->_error ('Invalid content type!'); } This might seem a bit heavy handed, but I think there's another bug in there which I have not yet been able to replicate. While testing, I wound up with a zombie process which created new files once a second until killed. I can't guarantee that this was not from changes I made to CGI_lite.pm, but I don't think so. I was mostly just putting in a few print statements to see what was going on. If you do use the file upload system, you should properly escape the file names immediately before opening filehandles. Your programs probably expect url escaping, so this is probably the best format to use. See URI::Escape.pm in libwww for code to do this. Make sure all shell metacharacters are escaped, not just the url-unsafe ones. Don't forget \n. -------------------------------------------------------------------------------- DETAILS: ======== Netscape (the only browser implementing file upload that I have access to) url-encodes filenames before sending them, but it is relatively straightforward to manually construct a http request which does not do this url-encoding. ----------------- start --------------------- POST /cgi-bin/test/upload.pl HTTP/1.0 Referer: file:///Hard_Disk/pub/projects/Utilities/w3mir/upload.html Connection: Keep-Alive User-Agent: Mozilla/3.01 (Macintosh; I; PPC) Host: 127.0.0.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Accept-Language: en Content-type: multipart/form-data; boundary=---------------------------29741200957742 Content-Length: 186 -----------------------------29741200957742 Content-Disposition: form-data; name="readme"; filename=";echo foo>foo|" file contents -----------------------------29741200957742-- ---------------------- end ------------------- the script will create a file whose name consists of a numerical timestamp followed by two underscores and then the user nominated filename. CGI_lite's handling of these files is apparently safe. Scripts which make use of the filenames recieved from CGI_lite.pm may not be. $filename = ";cat /etc/passwd>foo|"; open (FILE, ">$filename"); # safe open (FILE, "<$filename"); # safe open (FILE, ">" . $filename); # safe open (FILE, "<" . $filename); # safe open (FILE, "$filename"); # not safe open (FILE, $filename); # not safe If the last is run by a CGI sript, the contents of /etc/passwd will be written to STDOUT, and thereby the web client (more likely telnet than netscape in the event of an exploit). There are two ways for scripts to access the uploaded file(s). ---------------------- start ------------------- #!/usr/local/bin/perl5 use CGI_Lite; $cgi = new CGI_Lite (); $cgi->set_directory ("/usr/foo/tmp") || die "Directory doesn't exist.\n"; $cgi->set_platform ("UNIX"); $cgi->set_file_type ("handle"); # return handles. $data = $cgi->parse_form_data (); print "Content-type: text/plain", "\n\n"; $filename = $$data{'readme'}; while (<$filename>) { print; } close ($filename); exit (0); ---------------------- end ------------------- Safe so far, although subsequent uses of these files might be dangerously implemented. Actually CGI_lite.pm version 1.7 is broken such that using file handles will only work if the upload directory directory is set to "" which results in files being stored in the current directory. ---------------------- start ------------------- #!/usr/local/bin/perl5 use CGI_Lite; $cgi = new CGI_Lite (); $cgi->set_directory ("/usr/shishir") || die "Directory doesn't exist.\n"; $cgi->set_platform ("UNIX"); $cgi->set_file_type ("name"); # return filenames. default behaviour $data = $cgi->parse_form_data (); print "Content-type: text/plain", "\n\n"; $filename = $$data{'readme'}; open (FILE , $filename); while (<FILE>) { print; } close ($filename); exit (0); ---------------------- end ------------------- this will execute the command embedded in the filename. There is no problem if the file is opened like so: open (FILE , "<$filename"); In this case the pipe on the end of filename does not result in commands in filename being passed to the shell, being overriden by the '<'. I'd reccomend that the preceding > and < characters on the filename should be used when opening files in perl unless you're absolutely sure the filename is safe (and probably then also). Check out the newsroom: http://www.newsroom.co.nz . . . . . . . . . . . . . . . . Andrew McNaughton | I tried to make it idiot proof, Andrew () squiz co nz | but they just developed a http://www.squiz.co.nz | better idiot . . . . . . . . . . . . . . . .
Current thread:
- potentially dangerous behaviour in CGI_lite.pm file upload Andrew McNaughton (Aug 12)