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.");
}