this is an assignment for my computer security class, so I don't need specific answers, but I'd like some advice or at least general direction since I've been going in circles for a while.
Basically, we have an assignment where we've been given a simple cgi application (written in perl) that has a vulnerability somewhere that allows users to view private files, like /etc/shadow, that they aren't supposed to. We basically have to show that we can attack it and view the /etc/shadow file. The application is a memo viewing program, that lets users write and read memos.
Now, I'm very new to Perl. I did use it once or twice for very simple web stuff a long time ago, but that was literally the basics.
The code in question
#!/usr/bin/perl -w
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use strict;
my %labels; # global of pretty labels for memo pathnames
# glob through the homedirs for an array of paths to memos sorted by date
sub list_memo_selector {
my @memos = </home/*/memo/*>; # all regular users
push (@memos, </root/memo/*>); # special memos from root
foreach (@memos) {
$_ =~ m#^.+/([^/]+)$#; # regex extract filename
my $temp = $1;
$temp =~ s/_/ /g; # convert _ to " "
$labels{$_} = $temp; # assign pretty label name
}
print popup_menu(-name=>'memo', -values=>\@memos, -labels=>\%labels);
print submit("Read memo");
}
print header();
print "<html><head><title></title></head><body>\n";
print h1("FrobozzCo Memo Distribution Website");
print h4("Got Memo?");
print hr();
print p('Select a memo from the popup menu below and click the "Read memo" button.');
print p("<form method='post' name='main'>\n");
if (!param('memo')) {
list_memo_selector();
} else { # there is a memo selected
list_memo_selector();
my $memo = param('memo');
my $author = "root";
my @stat = stat $memo;
my $date = localtime $stat[9];
if ($memo =~ m#^/home/([^/]+)/.*$#) {
$author = $1;
}
print "<hr>\n";
print "<blockquote>";
print '<table border=1><tr><td>';
print "<center><b>$labels{$memo}</b></center>";
print '</td></tr>';
print "<tr><td>\n<p>";
print "<b>Author:</b> $author<br />\n";
print "<b>Subject:</b> $labels{$memo}<br />";
print "<b>Date:</b> $date<br />\n";
print "\n</p></td></tr>\n";
print "<tr><td><p> </p>\n";
print "<blockquote><p>\n";
open (MEMO, $memo);
foreach (<MEMO>) {
$_ =~ s#\n$#</p><p>#; # HTMLize newlines for formatting
print "$_\n";
}
print "</p></blockquote>\n";
print '<p> </p></td></tr></table>';
print "</blockquote>";
print "<hr>\n";
}
print h2("To publish a memo:");
print <<TEXT;
<ol>
<li>Create a directory named 'memo' in your home directory.</li>
<li>Edit text files in that directory.</li>
<li>Save the file using underscores (_) for spaces, e.g. "free_lunch".</li>
</ol>
TEXT
print p('To remove your memo from publication, simply delete the file from tme memo directory.');
print "</form>\n";
print "</body></html>";
I think the key is that the program calls open(MEMO, $memo) without checking the user's input, so if you could point it to /etc/shadow the program would just print out the shadow file.
The problem is, currently it only lists the files from home/*/memo or /root/memo. I've been trying to figure out how to to point somewhere else. I think it has to do with the fact that we can create our own memos (by creating a directory called memo in our homedir). But I can't quite figure out what I need to put in the memo to get the program to open /etc/shadow instead.
Does anyone know if I'm on the right track or completely missing another obvious error?
You're missing the obvious error.
A popup menu is created to specify a memo
value, but there is nothing enforcing the user to only specify one of the prepopulated values. They could specify anything.
There isn't even any enforcement of a POST request method, so editing the form parameters in the URL would be sufficient for specifying a value:
http://www.yourdomain.com/form.cgi?memo=/etc/naughty/boy
Validation
To avoid the attack, one must validate that the data is within our expected range of values by either:
The least likely to introduce a new bug would be to reuse the original values. This is because it's very easy to not make a regex restrictive enough. For example, allowing the updir ..
to be included in the path somewhere.
Additionally, the open
call should use the 3 parameter form along with a lexical filehandle while we're at it. We do not want to allow the user to specify the mode of opening the file.
open my $fh, '<', $memo or die "Can't open $memo: $!";