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: bir7@leland.Stanford.EDU (Ross Biro)
Subject: Replacement Fingerd in Perl
Message-ID: <1993Aug6.162538.27710@leland.Stanford.EDU>
Summary: Please check for security holes
Keywords: finger fingerd X chroot security
Sender: news@leland.Stanford.EDU (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 bir7@leland.stanford.edu 
Member League for Programming Freedom (LPF)
mail lpf@uunet.uu.net 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.");
}




