3

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>&nbsp;</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>&nbsp;</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?

user1777900
  • 975
  • 3
  • 13
  • 27
  • 1
    The second obvious error is: Why are CGI scripts running as root?!? – ikegami May 15 '14 at 01:28
  • XD Because reasons. Actually, allegedly the assignment says it so that the script can access any of the user's directories, but even I know there are better ways of doing that then SUID – user1777900 May 15 '14 at 01:36

1 Answers1

4

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:

  1. Reusing the values used to populate the popup_menu and comparing.
  2. Use a regular expression to match expected format.

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: $!";
Miller
  • 34,962
  • 4
  • 39
  • 60
  • Ah, I didn't notice that memo wasn't already assigned. I think my brain saw the foreach(@memos) and for some reason substituted that in XD Out of curiosity though, would the method I was looking at have been possible? Since we can create our own memos with any name we want, if I create a memo with the name '/etc/shadow/' or something like that would it be possible to make use of the open call? Or was that completely off? – user1777900 May 15 '14 at 01:29
  • One more question, sorry for the trouble, out of curiosity if I wanted to patch this error, would enforcing a POST HTTP request be enough? – user1777900 May 15 '14 at 01:38
  • I've included some potential steps to validate. Note that just enforcing POST method is not enough, as it's very easy to automate a form submission. – Miller May 15 '14 at 01:56
  • Wow, thanks a bunch. Looks like I get to learn some new stuff too :D I wish I could vote you up more than once – user1777900 May 15 '14 at 02:17
  • 4
    @user1777900, Re "would enforcing a POST HTTP request be enough?" Why would it? The data still come from the user whether they use GET or POST. It just means the attacker would need slightly more than just their browser's address bar (i.e. worthless). **You can't trust anything you get from the user.** – ikegami May 15 '14 at 03:53
  • 1
    One slight complication in obtaining /etc/shadow is that it usually has very restricted file permissions (usually just readable by root), but some of the rest of the CGI script implies that the script *might* be being run as root. – tobyink Nov 23 '14 at 01:06