The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

[Phrack] Perl CGI problems (security perl cgi)


<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>
Ключевые слова: security, perl, cgi,  (найти похожие документы)
-------[ Phrack Magazine --- Vol. 9 | Issue 55 --- 09.09.99 --- 07 of 19 ] Subject: [Phrack] Perl CGI problems --------[ rain.forest.puppy / [ADM/Wiretrip] <[email protected]> ] ----------------[ Intro I guess I should have an intro as to what this is about. Mostly, I've been coding and auditing various CGIs, and was trying to figure out how to leverage a few problems I thought were holes. So whatever, I'll shutup and get onto the holes. ----------------[ The Beef ----[ Poison NULL byte Note: The name `Poison NULL byte` was originally used by Olaf Kirch in a Bugtraq post. I liked it, and it fit... So I used. Greetings to Olaf. When does "root" != "root", but at the same time, "root" == "root" (Confused yet)? When you co-mingle programming languages. One night I got to wondering, exactly what would Perl allow, and could I get anything to blow up in unexpected ways. So I started piping very weird data out to various system calls and functions. Nothing spectacular, except for one that was quite notable... You see, I wanted to open a particular file, "rfp.db". I used a fake web scenario to get an incoming value "rfp", tacked on a ".db", and then opened the file. In Perl, the functional part of the script was something like: # parse $user_input $database="$user_input.db"; open(FILE "<$database"); Great. I pass 'user_input=rfp', and the script tries to open "rfp.db". Pretty simple (let's ignore the obvious /../ stuff right now). Then it got interesting when I passed 'user_input=rfp%00'. Perl made $database="rfp\0.db", and then tried to open $database. The results? It opened "rfp" (or would have, had it existed). What happened to the ".db"? This is the interesting part. You see, Perl allows NUL characters in its variables as data. Unlike C, NUL is not a string delimiter. So, "root" != "root\0". But, the underlying system/kernel calls are programmed in C, which DOES recognize NUL as a delimiter. So the end result? Perl passes "rfp\0.db", but the underlying libs stop processing when they hit the first (our) NUL. What if we had a script that allowed trusted junior admins to change passwords on anyone's account EXCEPT root? The code could be: $user=$ARGV[1] # user the jr admin wants to change if ($user ne "root"){ # do whatever needs to be done for this user } (**NOTE: this is here in WAY simplistic form & theory just to illustrate the point) So, if the jr. admin tries 'root' as the name, it won't do anything. But, if the jr. admin passes 'root\0', Perl will succeed the test, and execute the block. Now, when systems calls are piped out (unless it's all done in Perl, which is possible, but not likely), that NUL will be effectively dropped, and actions will be happening on root's record. While this is not necessarily a security problem in itself, it is definitely an interesting feature to watch for. I've seen many CGIs that tack on a ".html" to some user-submitted form data for the resulting page. I.e. page.cgi?page=1 winds up showing me 1.html. Semi-secure, because it adds ".html" page, so you'd think, at worst, it'd only show HTML pages. Well, if we send it page.cgi?page=page.cgi%00 (%00 == '\0' escaped) then the script will wind up feeding us a copy of its own source! Even a check with Perl's '-e' will fail: $file="/etc/passwd\0.txt.whatever.we.want"; die("hahaha! Caught you!) if($file eq "/etc/passwd"); if (-e $file){ open (FILE, ">$file");} This will succeed (if there is, in fact, an /etc/passwd), and open it for writing. Solution? Simple! Remove NULs. In Perl, it's as simple as $insecure_data=~s/\0//g; Note: don't escape them with the rest of the shell metacharacters. Completely remove them. ----[ (Back)slash and burn If you take a look at the W3C WWW Security FAQ, you'll see the recommended list of shell metacharacters is: &;`'\"|*?~<>^()[]{}$\n\r What I find the most interesting is everyone seems to forget about the backslash ('\'). Maybe it's just the way you need to write the escape code in Perl: s/([\&;\`'\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g; With all those backslashes escaping [](){}, etc., it gets confusing to make sure that the backslash is also accounted for (here, it's '\\'). Perhaps some people are just regex-dyslexic, and think that by seeing one instance of backslash it's accounted for. So, of course, why is this important? Imagine if you have the following line submitted to your CGI: user data `rm -rf /` You run it through your Perl escape code, which turns it into: user data \`rm -rf /\` Which is now safe to use in shell operations, etc. Now, let's say your forgot to escape out backslashes. The user submits the following line: user data \`rm -rf / \` Your code changes it to: user data \\`rm -rf / \\` The double backslashes will turn into a single 'data' backslash, leaving the backticks unescaped. This will then effectively run `rm -rf / \`. Of course, with this method, you'll always have spurious backslashes to deal with. Leaving the backslash as the last character on the line will cause Perl to error out on system and backtick calls (at least, in my testing it did). You'll have to be sneaky to get around this. ;) (It is possible...) Another interesting backslash side-effect comes from the following code to prevent reverse directory transversals: s/\.\.//g; All it does is remove double dots, effectively squashing reverse transversal of a file. So, /usr/tmp/../../etc/passwd will become /usr/tmp///etc/passwd which doesn't work (Note: multiple slashes are allowed. Try 'ls -l /etc////passwd') Now, enter our friend the backslash. Let's give the line /usr/tmp/.\./.\./etc/passwd the regex expression will not match due to the backslash. Now, go to use that filename in Perl $file="/usr/tmp/.\\./.\\./etc/passwd"; $file=s/\.\.//g; system("ls -l $file"); Note: we need to use double backslashes to get Perl to insert only one 'data' backslash -- otherwise Perl assumes you're just escaping the periods. Datawise, the string is still "/usr/tmp/.\./.\./etc/passwd". However, the above only works on system and backtick calls. Perl's '-e' and open (non-piped) functions do NOT work. Hence: $file="/usr/tmp/.\\./.\\./etc/passwd"; open(FILE, "<$file") or die("No such file"); will die with "No such file". My guess is because the shell is needed to process the '\.' into '.' (as an escaped period is still just a period). Solution? Make sure you escape the backslash. Simple enough. ----[ That pesky pipe In Perl appending a '|' (pipe) onto the end of a filename in a open statement causes Perl to run the file specified, rather than open it. So, open(FILE, "/bin/ls") will get you a lot of binary code, but open(FILE, "/bin/ls|") will actually run /bin/ls. Note that the following regex s/(\|)/\\$1/g will prevent this (Perl dies with a 'unexpected end of file', due to sh wanting the nextline indicated by the trailing '\'. If you find a way around this, let me know). Now we can complex the situation with the other techniques we just learned above. Let's assume $FORM is raw user-submitted input to the CGI. First, we have: open(FILE, "$FORM") which we can set $FORM to "ls|" to get the directory listing. Now, suppose we had: $filename="/safe/dir/to/read/$FORM" open(FILE, $filename) then we need to specifically specify where "ls" is, so we set $FORM to "../../../../bin/ls|", which gives us a directory listing. Since this is a piped open, our backslash technique to get around anti-reverse-traversal regex's may be possibly used, if applicable. Up to this point we can use command line options with command. For example, using the above code snippet, we could set $FORM to "touch /myself|" to create the file /myself (sorry, couldn't resist the filename. :) Next, we have a little harder situation: $filename="/safe/dir/to/read/$FORM" if(!(-e $filename)) die("I don't think so!") open(FILE, $filename) Now we need to fool the '-e'. Problem is that '-e' will come back as not exist if it tries to find 'ls|', because it is looking for the filename with the actual pipe on the end. So, we need to 'remove' the pipe for the '-e' check, but still have Perl see it. Anything come to mind? Poison NULL to the rescue! All we need to do is set $FORM to "ls\0|" (or, in escaped web GET form, "ls%00|"). This causes the '-e' to check for "ls" (it stops processing at our NUL, ignoring the pipe). However, Perl still sees the pipe at the end come time to open our file, so it will run our command. There's one catch, however...when Perl executes the our command, it stops at our NULL -- this means we can't specify command line options. Maybe examples will better illustrate: $filename="/bin/ls /etc|" open(FILE, $filename) This gives as a listing of the /etc directory. $filename="/bin/ls /etc\0|" if(!(-e $filename)) exit; open(FILE, $filename) This will exit because '-e' sees "/bin/ls /etc" doesn't exist. $filename="/bin/ls\0 /etc|" if(!(-e $filename)) exit; open(FILE, $filename) This will work, except we'll only get the listing of our current directory (a plain 'ls')...it will not feed the '/etc' to ls as an argument. <rant> I also want to make a note for you code junkies: if you lazy Perl programmers (not *ALL* Perl programmers; just the lazy ones) would take the extra time to make your mind up and specify a specific file mode, it would render this bug moot. </rant> $bug="ls|" open(FILE, $bug) open(FILE, "$bug") work. But open(FILE, "<$bug") open(FILE, ">$bug") open(FILE, ">>$bug") etc..etc.. won't work. So if you want to read in a file, then open "<$file", not just $file. Inserting that less-then sign (one measly character!) can save you and your server a lot of grief. </rant> Ok, now that we have a few weapons, let's go engage the enemy. ----------------[ Real life (insecure) Perl scripts Our first CGI I snagged off of freecode.com. It's a classified ad manager script. From the CGI file: # First version 1.1 # Dan Bloomquist [email protected] Now the first example...Dan parses all incoming form variables into %DATA. He doesn't strip '..', nor NUL characters. So, let's take a peek at a snippet of code: #This sets the real paths to the html and lock files. #It is done here, after the POST data is read. #of the classified page. $pageurl= $realpath . $DATA{ 'adPath' } . ".html"; $lockfile= $realpath . $DATA{ 'adPath' } . ".lock"; Using 'adPath=/../../../../../etc/passwd%00' we can specify $pageurl to point to the /etc/passwd file. Ditto for the $lockfile. We can't use the appended pipe, because he appends the ".html"/".lock" afterwards (well, you CAN use it, but it's not going to work. ;) #Read in the classified page open( FILE,"$pageurl" ) || die "can't open to read $pageurl: $!\n"; @lines= <FILE>; close( FILE ); Here Dan reads in $pageurl, which is the file we specified. Fortunately for Dan, he then immediately opens $pageurl for write. So whatever we specify to read, we also need rights to write it. This does limit the exploitation potential. But it serves as a great real-life example of this type of problem. Interestingly enough, Dan does go on to: #Send your mail out. # open( MAIL, "|$mailprog $DATA{ 'adEmail' }" ) || die "can't open sendmail: $adEmail: $!\n"; Hmmmmm...this is your standard no-no. And Dan doesn't parse shell metacharacters, so that 'adEmail' gets pretty scary. Sticking around freecode.com, I then got a simple form logger: # flexform.cgi # Written by Leif M. Wright # [email protected] Leif parses form input into %contents, and doesn't escape shell metacharacters. Then he does $output = $basedir . $contents{'file'}; open(RESULTS, ">>$output"); Using our standard reverse directory transversal, we don't even have to NUL out an extension. Whatever file we specify is opened for append, so again, we need to get a little lucky with our permissions. Again, our pipe bug won't work because he specifically set the mode to append (via the '>>'). Next is LWGate, which is a WWW interface to many popular mailing list packages. # lwgate by David W. Baker, [email protected] # # Version 1.16 # Dave puts parsed form variables into %CGI. Then we have # The mail program we pipe data to $temp = $CGI{'email'}; $temp =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; $MAILER = "/usr/sbin/sendmail -t -f$temp" open(MAIL,"| $MAILER") || &ERROR('Error Mailing Data') Hmmmm...Dave seems to have forgotten the backslash in his regex replacement. Not good. Ok, let's switch to one of the many shopping cart applications. This one, again, was yanked from freecode.com, Perlshop. $PerlShop_version = 3.1; # A product of ARPAnet Corp. - [email protected], www.arpanet.com/perlshop The interesting part is: open (MAIL, "|$blat_loc - -t $to -s $subject") || &err_trap("Can't open $blat_loc!\n") $to is obviously the user-defined email. Blat is a NT mail program. Remember that shell metacharacters on NT are <>&|% (maybe more?). Remember the pesky pipe problem I mentioned? (I hope you remember it... It was only a few paragraphs ago!). I admit, it's a very unlikely bug, but I did find it. Let's head over to Matt's Script Archive. # File Download Version 1.0 # Copyright 1996 Matthew M. Wright [email protected] First he parses incoming user data into $Form (not escaping anything). Then he runs the following: $Request_File = $BASE_DIR . $Form{'s'} . '/' . $Form{'f'}; if (!(-e $filename)) { &error('File Does Not Exist'); } elsif (!(-r $filename)) { &error('File Permissions Deny Access'); } open(FILE,"$Request_File"); while (<FILE>) { print; } This fits the criteria for the 'pesky pipe problem' (tm). We do have the '-e' check, so we don't get to use command line args. Since he sticks $BASE_DIR on the front, we'll need to use reverse directory transversal. I'm sure you looking at the above (should) see a much more simpler problem. What if f=../../../../../../etc/passwd? Well, if it exists, and is readable, it'll show it to you. And yes, it does. One other note: all accesses to download.cgi are logged by the following code: open(LOG,">>$LOG_FILE"); print LOG "$Date|$Form{'s'}|$Form{'c'}|$Form{'f'}\n"; close(LOG); So you'll be on candid camera for everything you do. But you shouldn't be doing mean stuff to other people's servers anyways. ;) Let's fly over to BigNoseBird.com. Script I have in mind: bnbform.cgi #(c)1997 BigNoseBird.Com # Version 2.2 Dec. 26, 1998 The code of interest is after the script opens a pipe to sendmail as MAIL: if ($fields{'automessage'} ne "") { open (AM,"< $fields{'automessage'}"); while (<AM>) { chop $_; print MAIL "$_\n"; } This is another simple one. BNB doesn't do any parsing of the user input variables (in $fields), so we can specify any file we want for 'automessage'. Assuming it's readable by the web server context, it will get mailed to whatever address we put (or so the theory goes). ----------------[ Drats...That's the End Sure is. By this time I was a little tired of wading through Perl code. I'll leave it as an exercise for all of you to go find more. And if you do, drop me a line--especially if you find some scripts that you can make use of the 'pesky pipe problem'. Anyways, that's all I wrote for this one, so till next time people. .rain.forest.puppy. [ADM/Wiretrip] [email protected] Greets can be found at http://www.el8.org/~rfp/greets.html ----[ EOF

<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>

 Добавить комментарий
Имя:
E-Mail:
Заголовок:
Текст:




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру