Sunday, January 17, 2010

You shouldn't swim in TAINTed waters.

Most people know it's not safe to swim in shark infested waters, so why would you do the same thing with your perl code? As we all know there's all types of well meaning and not so well meaning sharks trolling out there in the waters of the world wide web. Add to this, some sources say, that numbers as high 80% of security breaches and hacks are internal in origin, some may begin to think its just best to stay out of the water. There is good news though, for when it comes to perl, you can just ask it to help with plugging up some of its own holes by using perl's taint feature.

So how do you use this really cool feature in perl? Its as simple as adding "-T" switch to your shebang statement in your code or to the interpreter if your running from the command line.
#!/opt/local/bin/perl -T
or
[wm@perlbox ~]$ perl -T really_cool.pl
So you ran out and did this on that brand new shiny piece of code you've been working on; then it burst into flames and came crashing down in a hail of environment or tainting errors, right? This is good it just means that you are indeed swimming in tainted waters and you need to plug the holes in your code.

So your asking yourself, what does this person mean by "Tainted water"? Perl basically looks at certain aspects of your code, first do you use any functions that can compromise the system, next is any external data being used by those functions and also if the environment will allow arbitrary scripts to be executed by your code. So if any of the above condition exists in your code, then your data is considered tainted and perl will exit, complaining vigorously until you fix it. But if you would like a more precise definition of tainting, a good fishing spot is here, Perl Docs - perlsec. But for now lets consider this code snippet:
#!/opt/local/bin/perl -T
# real_cool.pl
my $value = $ARGV[0];
my $out = `cat error.log | mail -s 'error log' $value 2&>1`;
print $out;
First thing since we are using a backtick operation and executing code as if it were on the command line, we now have code that can compromise the system. Also we use a variable passed from an external source via the %ARGV hash that we have no control over. And lastly we are accessing a file in the script directory which could have access to other files with more sensitive data. So what happens when we run the code like this?
IMPORTANT: Don't execute this code unless your sure the taint switch is in place and you understand what it's going to do if the taint switch is not in use!
[wm@perlbox ~]$ perl -T really_cool.pl '; rm -rf /'
Insecure dependency in `` while running with -T switch at real_cool.pl line 5.
Luckily we were using the "-T" switch because this code would have deleted every thing your user has write access too or you would have killed the server if you were root. Also if this had been a cgi it could have deleted most of your website. This should underscore why using tainted data (aka water) can put some very serious security holes in your code.

So now we know there is a hole in our code big enough for any shark to swim through, what do we do next? Like any sailor knows you grab the biggest bucket you can and start baling out the water. What I mean by this is we need to capture the data and look at it before we use it and the taint feature lets us use sub-expressions from perl's regular expression features to do this.
sub un_taint{
    my $tainted_data = shift;
    $tainted_data =~ /(.*)/;
    my $safer_data = $1;
    return $safer_data;
}
I know, your not familiar with regular expression and it makes your brain hurt, if this is the case I recommend you take some time and visit another favorite fishing holes of mine, Perl Docs - perlre. And for the others that are saying this functions does nothing, your partially right, but remember perl considers anything in the sub-expression to be not tainted and this is just our bucket.

We now have a bucket, so how do we start bailing out the water? We don't, we shove the bucket in the hole to plug it up. At this point in time we have an expression that matches anything ( /(.*)/ ) and need to be more specific about looking at our data and start validating what's in it. Lets take a look at the new and improved real_cool.pl:
#!/opt/local/bin/perl -T
# real_cool.pl
my $value = un_taint($ARGV[0]);
my $out = `cat error.log | mail -s 'error log' $value 2&>1`;
print $out;

sub un_taint{
    my $tainted_data = shift;
    my $safer_data = undef;
    if ($tainted_data =~ /(\w{1}[\w-.]*)\@([\w-.]+)/){
        $safer_data = $1,'@',$2;
    }
    return $safer_data;
}
As you can see we are now putting our tainted data in the bucket (aka un_taint()). We are also looking at the tainted data to see if it looks like a real email address ( /(\w{1}[\w-.]*)\@([\w-.]+)/ ) and if it does we return it to be used. The un_taint() function can also be used many times in our scripts by validating data with repeated elsif's. But there's a problem?
[wm@perlbox ~]$ perl -T really_cool.pl '; ls -l /etc/'
Insecure $ENV{PATH} while running with -T switch at test.pl line 6.
If you remember at the beginning of my post I said, the taint feature will also look at %ENV hash to see if our script can view or execute any code on our system and is complaining about $ENV{PATH} specifically. All we need to is fix our environment a bit and we should be good to go.
#!/opt/local/bin/perl -T
# real_cool.pl
$ENV{'PATH'} = '/usr/bin;/usr/loca/bin';
$ENV{'BASH_ENV'} = '';
my $value = un_taint($ARGV[0]);
my $out = `cat error.log | mail -s 'error log' $value 2&>1`;
print $out;

sub un_taint{
    my $tainted_data = shift;
    my $safer_data = undef;
    if ($tainted_data =~ /(\w{1}[\w-.]*)\@([\w-.]+)/){
        $safer_data = $1,'@',$2;
    } else {
        die('VIOLATION: "'.$tainted_data.'" is being passed as a parameter.');
    }
    return $safer_data;
}
So our script can no longer be flooded with tainted data and when it does it tells us about it. We are now using sub-expressions to validate our data before it is used by our system and making sure that our environment is safe from being exploited. These are basically the requirements that taint imposes on our code and our code has addressed them. As I mentioned earlier in this post these are just the basics of the taint features in perl and there is more detailed information about perl's these features and I recommend that you that some time to look at it. But for now this should be a good push it the right direction.

1 comment:

  1. Hey, thanks for the article wiley-man.

    Charlie Brady came on the Mojo list a couple of months ago sounding the alarm that Mojolicious won't play ball with -T. Turned out kraih wasn't particularly bothered about this, but I was kinda curious about this taint mode thing - Charlie made it sound like I might've been driving without a seatbelt in my experience with Perl hitherto.

    Thanks for showing what taint mode means in practice in a nice, succinct, easily-digestable-between-doing-work way :)

    ReplyDelete