Avoiding a GET CGI hack attack

R

Robert Stelmack

I posted this message also at: comp.inforsystems.www.authoring.cgi, but
after one day it did not show up, so I try here since it both a CGI and a
Perl question.

I am trying to make my Perl CGI script secure. Our non-profit web site was
hacked and the index page changed. This in itself is just an annoyance, but
it could have just as easily to alter or delete any file.

The web host system administration was kind enough to look over some logs
and identified the method used to hack the site. This was do to a "bug" in
the script ~/www/cgi-bin/index.cgi This CGI script is something I built and
functionally does what I want it to, but obviously poorly written. I have
blocked the script for the time being with a chmod 000 to disallow any
access.

Here is the way that the system administrator said that the script was
compromised:

xxx.xxx.xxx.xxx - - [09/Jul/2004:06:07:43 -0500] "GET
/cgi-bin/index.cgi?path=|echo%20\"<html><head><title>H@ck3d!</title></head><
body%20bgcolor=black%20text=yellow><h1>Hacked!%20What%20such%20a%20weak%20si
te!%20Please%20fix%20this%20as%20soon%20as%20possible!%20Or%20your%20site%20
will%20be%20more%20vunerable!</h1><center><h3><i>Nowhere%20Man</i></h3></cen
ter></body></html>\"%20>%20../index.htm|

He said that I would need additional input checking to prevent this from
happening again.

Here is the script, pared down to show what it basically does. Yes, and
before anyone says anything: it is a crap script, I didn't RTFM, and there
is a better way to do it ;-)

#!/usr/bin/perl

open(STDERR,'>&STDOUT'); ## Assign error messages to STDOUT to view

$| = 1;

print "Content-Type: text/html\n\n"; #live feed to browser

push(@INC, "../"); ## Location of cgi-lib.pl (located in this directory)

require "cgi-lib.pl" || die "Can't find cgi-bin.pl REASON: $! ";

## Looks up the files in the passed directory name with the suffix ".dbf"

&ReadParse(*input);

$PATH = @input{'path'}; ## The Name of the DIRECTORY

print "<html><head><title>Index Page</title></head><body>";

{foreach $file (<$PATH/*.dbf>)

{

$_ = $file;

print "PWD for files found: $_<br>\n";

}

}

print "</body></html>";

I have read several Perl and security references and I would like to see if
this is all I need to do:

&ReadParse(*input);

$PATH = @input{'path'}; ## The Name of the DIRECTORY

if ( $PATH =~ /[`\$\\"';&>]/ ) {die} ## Die if special characters are used

Not sure if the reg expression is correct, but is this the input checking
the system administrator was referring to? Or, do you have a good generic
suggestion to prevent this type of hacking that a simple user could
implement?

There were other ways to secure the script, but many of then required system
administrative access that I don't have.

I will add the -w -T for the perl execution and the "use strict; use
warnings;" once I clean up the script and use some of the OOP in the CGI.pm
module. And I will try to understand the scope (ie. "my variable") and
properly code the variables.

I will also add this, as suggested by perlsec:

delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe

----------------------------------------

References used:

http://www.oreilly.com/catalog/cgi2/chapter/ch08.html

http://search.cpan.org/~jhi/perl-5.8.0/lib/CGI.pm

perlsec
 
G

Gunnar Hjalmarsson

Robert said:
I posted this message also at: comp.inforsystems.www.authoring.cgi,
but after one day it did not show up,

Please see http://www.thinkspot.net/ciwac/howtopost.html for info
about a first time post to ciwac.

Here is the script, pared down to show what it basically does.
Yes, and before anyone says anything: it is a crap script, I didn't
RTFM, and there is a better way to do it ;-)

Since you realize that, you posted here prematurely. You should have
made an effort to improve the script first.
&ReadParse(*input);

$PATH = @input{'path'}; ## The Name of the DIRECTORY

At this point, $PATH may contain basically anything.
{foreach $file (<$PATH/*.dbf>)

And here you pass $PATH to a construct that invokes system commands.

This is a good example of what tainted mode is good for. If -T had
been enabled, Perl would have complained.
I have read several Perl and security references and I would like
to see if this is all I need to do:

&ReadParse(*input);

$PATH = @input{'path'}; ## The Name of the DIRECTORY

if ( $PATH =~ /[`\$\\"';&>]/ ) {die} ## Die if special characters
are used

Not sure if the reg expression is correct, but is this the input
checking the system administrator was referring to? Or, do you
have a good generic suggestion to prevent this type of hacking that
a simple user could implement?

Well, you missed one of the most important advices in "perldoc
perlsec": "It's better to verify that the variable has only good
characters (for certain values of ``good'') rather than checking
whether it has any bad characters. That's because it's far too easy to
miss bad characters that you never thought of."

Also, the above does not untaint $PATH.

What you should have done is ensuring that $PATH only contains 'safe'
characters, for instance like this:

if ( $PATH =~ /^([-/\w])$/ ) {
$PATH = $1;
} else {
die "Unsafe character(s) in \"path\"";
}

Then you would have been able to run the script in taint mode, and the
hack you let us know about would not have been possible.
I will add the -w -T for the perl execution and the "use strict;
use warnings;" once I clean up the script and use some of the OOP
in the CGI.pm module. And I will try to understand the scope (ie.
"my variable") and properly code the variables.

I will also add this, as suggested by perlsec:

delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe

That sounds good. But why didn't you do that before posting? :)
 
J

Joe Smith

Gunnar said:
What you should have done is ensuring that $PATH only contains 'safe'
characters, for instance like this:

if ( $PATH =~ /^([-\/\w])$/ ) {
$PATH = $1;
} else {
die "Unsafe character(s) in \"path\"";
}

I'd like to point out to Robert that he should be aware of attacks
that use "../", as in "../../../../../etc/passwd". Since the
above regex (corrected) does not accept periods, it foils that
particular attack.
-Joe
 
G

Gunnar Hjalmarsson

Joe said:
Gunnar said:
What you should have done is ensuring that $PATH only contains
'safe' characters, for instance like this:

if ( $PATH =~ /^([-\/\w])$/ ) {
$PATH = $1;
} else {
die "Unsafe character(s) in \"path\"";
}

I'd like to point out to Robert that he should be aware of attacks
that use "../", as in "../../../../../etc/passwd". Since the above
regex (corrected) does not accept periods, it foils that particular
attack.

True, but note that the OP has

foreach $file (<$PATH/*.dbf>)

, so nothing prevents you from simply stating the path '/etc'. I
suppose that the best protection in this case against that kind of
illegitimate access is the file extension '.dbf'.
 
K

krakle

Robert Stelmack said:
#!/usr/bin/perl

Always run perl scripts under the 'T' and 'w' switches using the
strict pragma. Warnigns help too.. It forces you to write "clean" perl
by scoping variables and prevents you from certain dangerious coding
methods.

#!/usr/bin/perl -Tw
use strict;
use warnings;

Ofcourse your script will have to be re-written.. But let's say you
decide not to do that.. You can patch up that bug with the
following...
{foreach $file (<$PATH/*.dbf>)

{

$_ = $file;

print "PWD for files found: $_<br>\n";

}

}

Firstly I would of thought the first line would of caused an error
since it doesn't exactly go by coding standards... I assume each .dbf
(DataBase File) located at the path ($path) contain a plaintext
password and this script loops through the path and displays all
passwords... IF all the .dbf files are in a single location cut out
the parameter passed to the script and just use a non-user defined
variable in the script. IF, the .dbf files are in multiple directories
(but a set number) just store the paths in a HASH (path as value.. a
keyname as the name) and pass a parameter through the script wiht just
the keyname and fetch the path that way. IF the .dbf files are
dynamically made in random locations you may want to use the "open"
command and do some regex's on the parameter passed...
$PATH = @input{'path'}; ## The Name of the DIRECTORY

if ( $PATH =~ /[`\$\\"';&>]/ ) {die} ## Die if special characters are used

Your script allowes for anyone to execute unix commands.. so it
seems.. Sure you can STRIP all non A-Z 0-9 characters all you want BUT
that doesn't stop anyone from using unix commands to delete files...or
do anything such as 'cat' dbf files to get passwords...
 
G

Gunnar Hjalmarsson

krakle said:
Your script allowes for anyone to execute unix commands.. so it
seems.. Sure you can STRIP all non A-Z 0-9 characters all you want
BUT that doesn't stop anyone from using unix commands to delete
files...or do anything such as 'cat' dbf files to get passwords...

Sounds scaring. Could you please show us how you delete a file through

foreach $file (<$PATH/*.dbf>)

if $PATH only may contain letters and digits.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top