Article 4851 of comp.lang.perl:
Xref: feenix.metronet.com comp.lang.perl:4851
Newsgroups: comp.lang.perl
Path: feenix.metronet.com!news.ecn.bgu.edu!wupost!math.ohio-state.edu!sdd.hp.com!decwrl!decwrl!olivea!sgigate!sgiblab!rpal.rockwell.com!headwall.Stanford.EDU!nntp.Stanford.EDU!bir7
From: [email protected] (Ross Biro)
Subject: Replacement Fingerd in Perl
Message-ID: <[email protected]>
Summary: Please check for security holes
Keywords: finger fingerd X chroot security
Sender: [email protected] (Mr News)
Organization: Stanford University, California
Date: Fri, 6 Aug 93 16:25:38 GMT
Lines: 217

       This has probably been done many times before, but I've
written a fingerd in perl which allows arbitrary applications to
be launched via a finger command from a remote computer.
I.E. finger "xarchie -display $DISPLAY"@... will cause xarchie to
be executed.  Since this is an obvious security problem, I would
like other people to examine the script for any security holes
I might have missed.

       I will attempt to summarize any responses I get, so
feel free to follow up via email instead of posting.

       This has been written under a government contract, so it
will probably be in the public domain once it is finished.  However
for now I have put a Berkeley style copyright on it.

Ross Biro [email protected]
Member League for Programming Freedom (LPF)
mail [email protected] to protect your Freedom


#!/usr/local/bin/perl
#fingerd.pl
#
# Copyright 1993 RIACS.
# Permission to use, copy, modify, and distribute this
# software and its documentation for any purpose and without
# fee is hereby granted, provided that this copyright
# notice appears in all copies.  RIACS
# makes no representations about the suitability of this
# software for any purpose.  It is provided "as is" without
# express or implied warranty.
#
# Written by Ross Biro 8/5/93
#
# The problem we had was how to easily launch clients on a computer
# without opening up all sorts of security holes.
# It was decided to use a simple protocol that most computer
# would be able to speak without additional software.  Hence
# fingerd(8) was replaced by this perl script.
#
# This is to be launched from inetd.  So stdin and stdout should
# point to the socket.
#
# Features:
#       1) Chroot is executed so applications do not need to be
#          scrutinized as carefully as they otherwise would.
#          (The ability to run a subshell should still be checked for.)
#
#       2) Executables are added to the system by simply including
#          them in the correct directory.
#
# Problems:
#       1) Must be run as root (More potential damage from security holes).
#
#       2) Currently Only works with X applications.
#

# Set $Debug to True if you want to run in debugging mode.
$Debug = 1;

# The user to run everything as.  Should have limited privildges.  'nobody'
# is a good name to put here.
$User='rosscaptive';

# The log file keeps track of all the connections and can be
# configured to get stdout/stderr of the application.
# Should be modified to use syslog when $Debug is false.
$LogFile='>>/tmp/fingerd.log';

{
   ($name, $passwd,$uid,$gid,$quota,$comment, $Gcos,$dir,$shell) =
       getpwnam($User);

   # abort if we didn't get anything
   die ("Unable to find user $User") if (!$name || !( $name eq $User));

   # Give Up Root privledges, but since UID=0 we can still get
   # them back later to do the chroot.  This is not really
   # necessary, but I feel like being paranoid.
   $> = $uid;

   # Get rid of all group privledges
   ($(, $) ) = ($gid, $gid);

   # Name is used again later, so let's undef it to prevent any interference.
   undef $name;

   # change to the home directory of the captive user.
   chdir ($dir) || die ("Unable to change directory to $dir: $!");

   # read a line from stdin (the remote end)
   $_ = <>;

   # Now we want to kill all the ctrl characters, just to be safe.
   s/[\000-\037]/ /g;

   # open the log file.
   open (LOG, $LogFile) || die ("Unable to open log");

   # get the address of the remote user.
   $addr = getpeername(STDIN);

   # redirect stdin from /dev/null.   We don't need it any more, and
   # this way any application will simply get eof if it tries to
   # read its stdin.
   open (STDIN, '</dev/null'); # We don't need stdin any more.

   # if we got an address write it to the log file.  If we are not
   # debugging, and we did not get an address then we should abort.
   if ($addr) {
       # get the current time.
       $time=time();

       #split the time into something reasonable
       ($sec, $min, $hour, $mday, $mon, $year, @dummy) = localtime ($time);

       # split the address into something reasonable
       ($family, $port, $addr) = unpack ('S n a4 x8', $addr);
       (@addr) = unpack ("C4", $addr);

       # put stuff in the log.
       print LOG "$mon/$mday/$year $hour:$min:$sec Host = $addr[0].$addr[1].$addr[2].$addr[3], Port=$port, Command=$_\n";

     } else {
         die "Did not Get Remote Address\n" if (!$Debug);
     }

   # close the log.  We will reopen it later if we need it.
   close LOG;

   # strip off the first word of the command string
   ($command,@words )=split;

   # Make sure that nothing funny was attempted.
   exit if ($command=~/\//);

   # now if its something we can run, assume it's a command
   if (-x "usr/bin/$command" ) {

       # we don't need STDOUT/STDERR anymore.  Since STDIN has already been
       # closed, this terminates the connection.
       if ($Debug) {
           open (STDOUT, $LogFile);
           open (STDERR, '>&STDOUT');
       } else {
           open (STDOUT, '>/dev/null');
           open (STDERR, '>&STDOUT');
       }

       # set effective uid so that we have root access again.
       $>=0;

       #now do the chroot
       chroot ($dir) || die ("Unable to chroot to $dir");

       # I beleive on some systems the chroot doesn't really
       # take affect until after the next chdir.  So we do one now.
       chdir ('/') || die ('Unable to change dir');

       #now give up all root privileges
       ($<,$>) = ($uid, $uid);

       # make sure it worked. (Does this really do anything? )
       exit if ($< != $uid || $> != $uid);

       # Now we want complete control over the environment, so
       # we trash it.
       undef %ENV;

       # now set the display if it was passed.  This has the
       # advantage of making all reasonable commands take
       # the option -display.
       $ENV{'DISPLAY'} = $1 if (s/-display[\t ]+([^ \t]+)//);

       # set the path.  First to things that they can execute
       # remotely, and then to things which are used in the
       # scripts, but should not be executed remotely.
       $ENV{'PATH'}='/usr/bin:/usr/rbin';

       # Now set the shell to be /bin/rsh.  In the event that
       # they get a shell we might as well attempt some
       # extra damage control by limiting what they can
       # do as much as we can.
       $ENV{'SHELL'}='/bin/rsh';

       exec ("$_") || die ("Unable to exec\n");
   }

   # This section of the code is attempting to be a fingerd.  Currently
   # it only lets you finger by user id.  It would be better if it
   # let you finger any string without funny symbols in it, but
   # for now I feel like being paranoid.

   #now give up all root privileges
   ($<,$>) = ($uid, $uid);

   # make sure it worked.
   exit if ($< != $uid || $> != $uid);

   # clear the IFS, It would only be set if inetd was trying something
   # funny, but we will do it anyway.
   $ENV{'IFS'}=' ' if ($ENV{'IFS'});

   # make sure that we have a valid uid.
   ($name, $passwd,$uid,$gid,$quota,$comment, $Gcos,$dir,$shell) =
       getpwnam($command);

   # check for strange characters.  There shouldn't be any because
   # it's a valid uid.  But let's be a little paranoid.  Are there
   # other things that should be searched for?
   exit if ( $command =~ /[\[\]@^;*!\$&]/);

   exec ("finger $command") if (($name eq $command));
   die ("Unable to exec finger.");
}