|
(The PcWeek crack)
By Jfs
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 lemming:~# telnet securelinux.hackpcweek.com 80 HTTP/1.1 400 Bad Request 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: / 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" I asked a friend if he would let me gave a look at his photoad installation 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 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: <!--#include file="..."--> for SSI 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: 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. lemming:~# cat getit.ssi | nc securelinux.hackpcweek.com 80 But unfortunately, the host didn't have SSI nor mod_perl configured, so I 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"); 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. But the other two lines are juicier... In photo.,cgi, line 132: open(ULFD,">$write_file")
|| die show_upload_failed("$write_file $!"); 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: $filename = lc($UPLOAD{'FILE_NAME'}); if ($filename =~ m/gif/) { 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). So, after playing a bit with various approaches and with a bit of help from /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... 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" ) { and in photo.cgi, line 140: if (($PhotoWidth eq
"") || ($PhotoWidth > '700')) { if ($PhotoWidth > $ImgWidth
|| $PhotoHeight > $ImgHeight) { 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 . 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; 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 :( $UPLOAD{'AdNum'}
=~ tr/0-9//cd; 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! :) 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 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 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 :( We modify the overwrite script, and yes, it allows us to overwrite a CGI! :) But then, when you run a shell script as a CGI, you need to specify the #!/bin/sh 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\00\00\00\00n/sh 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... Looking at an ELF (linux default executable type) binary gives us the answer, as it results that those bytes are set to 0x00, yohoo :) 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. So, this little program: lemming:~/pcweek/hack/POST# cat fin.c compiled gives us: lemming:~/pcweek/hack/POST# ls -l fin And stripping the symbols: lemming:~/pcweek/hack/POST# strip fin Then URL-encoding it: lemming:~/pcweek/hack/POST# ./to_url < fin > fin.url Which is TOO large for us to use in our script :( 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: lemming:~/pcweek/hack/POST# joe fin Now, we incorporate this into our exploit, and run it... Check the file called get.sec.find in the files directory for more info. So, we upload the CGI, and access it with our favorite browser, in this case: wget http://securelinux.hackpcweek.com/photoads/cgi-bin/advisory.cgi Which gives us a completed find / of the server :) Unfortunately, either the "top secret" file is not there, or it is not accessible by the nobody user :( We try some more combinations as locate, ls and others, but no traces of the "top secret" file. [ I wonder where it was after all, if it ever existed ] 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 Available on your nearest bugtraq/securityfocus store :) kudos to w00w00 for this 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. We make a CGI that will ls /tmp and yeah, first try and we have the suitroot waiting for us :) We upload a file to /tmp/xx with the modified index.html page. Time to make a program that will run: execlp("/tmp/.bs","ls","-c","cp /tmp/xx /home/httpd/html/index.html",0); And at this point the game is over :) It's been around 20hours since we started, good timing 8) 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 :)
|