http://qs321.pair.com?node_id=36121

Take a look at the code in this node. Since it didn't have taint checking (amongst many other problems), I was rather concerned about it. A quick review of this code revealed the following line of code. Where does the filename come from?
open(IN, "$filepath/$siteinfo[1]");
It appears to come from here (near the start of the program):
$query_string = $ENV{'QUERY_STRING'}; @search = split(/&/,$query_string); @info = split(/=/, $search[0]); @siteinfo = split(/-/, $info[1]);
Guess what the following URL does (assuming it's pointing to that CGI script):
http://www.somehost.com/path/to/script/badscript.cgi?name=x-/bin/ls|
If we use that code with that URL, we have /bin/ls| in the filename to be opened. That trailing pipe causes /bin/ls to be executed instead of opened.

Well, that's interesting.

I can now execute any executable on the server that the the script would have rights to run. Needless to say, there is a security issue here and it's a whopper. For all those monks out there who don't take this seriously (and I see a lot of them), pay attention!!!!

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

Replies are listed 'Best First'.
(Ovid) RE: Warning our Fellow Monks
by Ovid (Cardinal) on Oct 11, 2000 at 00:32 UTC
    Update: The above URL doesn't use reverse directory traversal. Stupid me. It should actually read:
    http://www.somehost.com/path/to/script/badscript.cgi?name=x-../../../b +in/ls|
    Update: Adam of course has raised the age-old "Security through Obscurity" question. The problem listed above is a classic on that many sites have. A resourceful hacker can easily write a Perl script that automatically probes for a number of common holes without necessarily knowing the holes are there. Even if you don't mention the hole, just having one can leave it exposed.

    Security through obscurity is simply hoping they don't find the holes. All it takes is one lucky guess to earn the programmer a pink slip. If we plug the holes in the first place, we don't have to sit at home worrying.

    On another note, this demonstrates why we shouldn't allow users to specify files names. Some programmers think the following can help protect:

    $filename =~ tr/\.\.//;
    This fails miserably. We can't disallow periods completely if there is a chance that a legitimate file to be opened is named something like datafile.dat, so we are concerned with two periods in a row. The hacker could simply specify \.\./\.\./\.\./bin/ls| as the path and our tr/// fails. If you don't believe me, hop on your linux box and see what ls \.\./ does.

    Update 2: I noticed that someone downvoted Adam's post. He stated that he was playing the Devil's Advocate "for fun". His intention was clearly to start good discussion, not to defend "security through obscurity." I gave him a ++. Don't downvote him for it!

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

RE: Warning our Fellow Monks
by Adam (Vicar) on Oct 11, 2000 at 00:46 UTC
    Ovid raises an important point, so for fun I am going to play devil's advocate:

    How would any hacker/cracker/punk know to try that parameter with my script? Am I not protected by obscurity?

      You are not protected. That parameter is a standard thing to try, along with various variations of it. You may need to vary the number of ..'s, you may need to fool an RE or two, but there are a finite number of combinations to use, and eventually you will hit one.

      You see there are a limited number of basic attacks the attacker will be trying to get to happen. There are a collection of ways to fool standard code with standard mistakes into falling for something interesting. And your code has a set of input parameters. So the script kiddie is going to list your parameters, and then plug a series of inputs in, until something interesting happens, and this attack will be on the list of obvious things to try.

      Were your code written in C an attacker could similarly pass longer and longer parameters until they got evidence something crashed. Once it crashes then you play around figuring out at what length it crashes. Once you know that then you start putting interesting exploits at that position. Again, it takes virtually no knowledge of the specifics of the code to figure out how to abuse it. Perl is virtually immune to buffer overflows, but they make up the most commonly reported security errors.

      And that is the key. Identify an easy to make mistake, start trying it out in random places. Eventually you find a way in. The key is not to analyze any one target in depth and break in there, rather it is to rattle a lot of doors until one falls apart. And once you break one door, probably lots more have the same flaw...

      Another fun one is default passwords. For instance Microsoft's SQL Server had a default login for the longest time. It has been fixed for a year now, but plenty of sites still have the login. So go, try that key on enough sites, and eventually you will get a database of credit cards.

      It is as easy as that!

      Really. :-(

        Ding Ding Ding. And the always verbose tilly comes in with the answer I was looking for: Dumb Luck

        The malicious hacker does not need to know your code to abuse it. Some kid who knows LWP and has plenty of time on hand can easilly write a simple little script to go through a handful of different attacks on a list of sites that the kid has visited recently. Ooops... there goes your day. ++ for tilly

      How would any hacker/cracker/punk know to try that parameter with my script? Am I not protected by obscurity?

      Not if they realized it came from Matts Script Archive...

      --Chris

      e-mail jcwren
        He he... I liked this answer. Say you foolishly put an ad on your site for the legendary archive. Hacker gets idea... This guy doesn't write secure code... lets find the holes! ++ for jcwren

      Maybe because due to an unrelated security hole the bad guy got your script code to be displayed?

      TIMTOWTDI applies to cracking too, the bad guys can get in through a single gaping hole but also through a series of just slightly insecure features in your code

        Still playing the Devil's Advocate here. (Thank's Ovid for recognizing the usefulness of playing dumb. )

        But no one sees this code...

        Except your co-worker that wants you fired cause your oversized paycheck puts a real damper on the funds available for raises. Or the new site Admin accidentally re-configured Apache to display CGI scripts as text files instead of running them. The list goes on. ++ for mirod

RE: Warning our Fellow Monks
by footpad (Abbot) on Oct 11, 2000 at 23:36 UTC

    A penitent humbly asks:

    After carefully reading the thread (several times), perlsec, the online version of Lincoln Stein's Security FAQ, and a few other sources for most of the morning, I'm afraid I must confess that the solution to the problem continues to elude me.

    Enabling Taint mode would not be entirely sufficient, would it? Would a regular expression around the query string solve the problem? If so, which would be the most effective?

    Perhaps, more clearly: how would you rewrite this to be safer?

    (fwiw, I'll happily pursue links, but ask for ones that show examples....)

      Taint checking would have fatally crapped out when it discovered that you were attempting to use a piece of a foreign string in a critical operation (opening a file). So yes, it would have been quite sufficient in detecting the problem, but it does nothing to help you fix your script so that it will actually run.

      Your best bet is to use tokens in your URL submissions, and then map those tokens to a set of filenames. If that can't be arranged, use a regular expression to "untaint" the data by explicitely declaring permitted characters.

      ($secure) = ($tainted =~ /(\w+)/); open(F, "< $secure") or die "$!"; # read only "../../bin/ls -l /etc|" -> "bin" (no such file)
        Fastolfe: you need to check for failure on your regex. Currently, if it fails and if there was a value already in $1, it will be passed to $secure. That could be disastrous. If a cracker gets your code and figures out how to pass "../../../bin/some_executable" into the previous backreference, you're back to the original problem.

        Also, if the filename has a period delimited extension (and many of them do), your regex won't work (e.g. "somefile.txt").

        Cheers,
        Ovid

        Update: I'm a moron. Fastolfe is right. Read dchetlin's response below. (sniff, sniff)

        That's what I get for reading his code too fast :(

        Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

      Would a regular expression around the query string solve the problem?
      Basically, what a regex does to untaint a variable is to ensure that only valid characters are in the variable. The variable in question, in this example, is $siteinfo[1]. Unfortunately, it's very, very difficult to find an appropriate regex that can perfectly secure user-supplied data that is passed to the shell. What I would probably do in this case is creates a hash that has the user data as the key and the hash value as the actual file to be opened. I say "probably" because if you have hundreds of files that the user could open, you would want to take a different solution.
      my %files = ( colors => 'colors.dat', tests => 'test.txt', names => 'names.dat', bribes => 'politicians.txt' ); open IN, "<$filepath/$files{$siteinfo[1]}" or someErrorRoutine( "Can't open $filepath/$siteinfo[1] for reading: +$!" );
      In the above example, the user data is used to pull a value from a hash. Since you have created those hash values, you know they are safe. If the hash value doesn't exist, then someErrorRoutine() is called. Depending upon how your site is set up, you might want someErrorRoutine to log the details of the failure. The key point to remember here is that user data never gets close to the shell (and that you should always check to see if your open statement failed).

      Also, taint checking should still be used here. The following regex is interesting to me:

      $siteinfo[1] =~ /^[^.]+/(\w+\.\w+)$/ or someErrorRoutine( "Regex failed" ); my $fileToOpen = $1; # $fileToOpen untainted
      In this case, we are making sure that there are NO periods prior to the final slash and only one period in the actual filename. Note that the error subroutine is called on failure. If it's not and that fails, $1 might contain an undesireable value that gets assigned to $fileToOpen.

      The Moral: When untainting data, make sure you check for failure of your regex to match.

      For a little more information about exploits like this, read this article (thanks to tilly for that link).

      Cheers,
      Ovid

      Join the Perlmonks Setiathome Group or just go the the link and check out our stats.

        Yes, check for failure. But regular expressions to perfectly secure data passed to the shell are actually pretty easily come by. You just need to remember the cardinal rule, deny everything that is not explicitly allowed. Trying to trap everything that can go wrong is hard. Allowing very limited input, is not. Try the following:
        /^( (?:\w+\/)* # Directory components? \w+ # Start of filename (?:\.\w+)? # Extension? )$/x
        This will work for most filenames you have a reason to allow, and I guarantee you that on a standard Unix system with a standard directory structure (unless someone has placed poor symlinks, etc), there is no way to name anything that passes this which has shell problems.

        You could test length($1) if you are afraid of buffer overflows. :-)

        What you will find though is that at some point some developer wants to break the rule. That is fine, just become more lenient (eg allow an optional period in a directory) but step by step keep the philosophy the same. Only that which you have guaranteed safe shall pass.

        The line:

        $siteinfo[1] =~ /^[^.]+/(\w+\.\w+)$/ or ...

        in Ovid's example has me confused. The extra slash (/) in the middle is throwing me off. At first glance, it looks like he meant to use the s/// operator. At second glance, it becomes apparent that it's meant as a directory separator (to exclude things like "../../". However, doesn't placing a '/' in a regexp delimited by '/' characters require an escape character (\)?

        (I must be getting a mild case of LTS)