Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

RE: Warning our Fellow Monks

by footpad (Abbot)
on Oct 11, 2000 at 23:36 UTC ( #36299=note: print w/replies, xml ) Need Help??

in reply to Warning our Fellow Monks

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....)

Replies are listed 'Best First'.
RE: RE: Warning our Fellow Monks
by Fastolfe (Vicar) on Oct 11, 2000 at 23:46 UTC
    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").


      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.

        Here's the REx Fastolfe posted:

        ($secure) = ($tainted =~ /(\w+)/);

        I certainly agree that the success needs to be checked, as there's an open being called with $secure on the next line, but $secure will not be ending up with a previous value of $1; if the REx fails, it will simply be undefined.


RE: RE: Warning our Fellow Monks
by Ovid (Cardinal) on Oct 12, 2000 at 00:05 UTC
    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).


    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)

        Yes, you need to escape the / (with a \) or change the regex delimiter.

                - tye (but my friends call me "Tye")
        10 Print "I am not worthy" + CHR(10); 20 GOTO 10
        Would be interested to see if the panel thought there's mileage in suggesting that it's unlikely that the user should want to open *any* type of file - and hence we could either assume an extension (and add it on explicitly before passing the filename to the ope() ?) - or offer a list, which would map to extensions (not just add on the field value (which could be faked to blank or a pipe)) Also - could the script not be forceably chrooted ? JAPHN (JAPH Novice)

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://36299]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2021-07-29 03:33 GMT
Find Nodes?
    Voting Booth?

    No recent polls found