#! /usr/bin/perl # ========================================================================= # sudo-copy - perl script to allow semi-secure file copying ability for # sudo. Giving pretty much any copy rights to an sudoer makes it trivial # for the sudoer to escalate to full root permissions. # # This script attempts to make it difficult to carry out a copy operation # exploit to full root access. # ========================================================================= # Task Description: # De-obfuscate the source and destination files # No recursive or directory copies # No symbolic links allowed in the source or destination # Check /etc/sudo-copy.conf file for valid target list # If all rules pass, allow the copy to happen. # ========================================================================= # Written by Scott L. Miller # # Born on date: 05/08/2008 use strict; use warnings; use File::Basename; use IO::Tee; use Getopt::Std; use vars qw($opt_h $LOG); getopts('h'); sub usage { print < Allow restricted copy operations to suduoers. Why? Unrestricted copy operations make it trivial for any sudoer to escalate to full root permissions. The more restricted the copy operations are, the harder it is to exploit. Output goes both to STDOUT and /var/log/sudo-copy.log file. Options: -h -h or no parameters will print this message. ENDUSAGE exit 1; } my $argc = scalar(@ARGV); usage() if $opt_h || not $argc; die "Need one source and one destination\n" if ($argc != 2); my $logfile = "/var/log/sudo-copy.log"; $|=1; my ($starttime, $startdate) = GetTime(); $LOG = IO::Tee->new(">> $logfile", \*STDOUT) or die "Error during IO::Tee->new\n $!\n"; print $LOG "=================================================================\n"; print $LOG "Started @ $startdate $starttime\n"; sub get_actual_path { my ($path) = @_; if(chdir "$path") { # CDing into a directory and then calling /bin/pwd should normalize whatever # strange input might be given to us by the user. my $actual_dir = `/bin/pwd`; chomp $actual_dir; return $actual_dir; } else { print $LOG "Can't cd into <$path>: $!\n"; print $LOG "Copy failed.\n"; die; } } my ($src, $dst) = @ARGV; my ($srcFileName, $srcPath, $srcFileExt, $dstFileName, $dstPath, $dstFileExt); ($srcFileName, $srcPath, $srcFileExt)=fileparse($src); ($dstFileName, $dstPath, $dstFileExt)=fileparse($dst); $srcPath = get_actual_path($srcPath); $dstPath = get_actual_path($dstPath); $src = $srcPath.'/'.$srcFileName.$srcFileExt; $dst = $dstPath.'/'.$dstFileName.$dstFileExt; if($src =~ /\$/) { $src =~ s/\$/\\\$/g; #escape the dollar signs } if($dst =~ /\$/) { $dst =~ s/\$/\\\$/g; #escape the dollar signs } foreach ($src,$dst) { lstat($_); if ( -l _ ) { print $LOG "Error: symbolic links not allowed\n"; print $LOG "Copy failed.\n"; die; } if ( -d _ ) { print $LOG "Error: directory copies not allowed\n"; print $LOG "Copy failed.\n"; die; } if ( -e _ && ! -f _ ) { print $LOG "Error: file is not a plain file\n"; print $LOG "Copy failed.\n"; die; } } if(check_valid_destination($dst)) { my ($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($dst); dosystem("cp -f \"$src\" \"$dst\""); if($uid ne '') { dosystem("/bin/chown $uid.$gid \"$dst\""); chmod $mode, $dst; } print $LOG "Copy complete.\n"; } else { print $LOG "Copy not allowed\n"; } my ($endtime, $enddate) = GetTime(); print $LOG "Finished @ $enddate $endtime\n"; exit; sub check_valid_destination { my $fname = $_[0]; my $cfgfile = '/etc/sudo-copy.conf'; open (CFGFILE, $cfgfile) || die "Couldn't open \"$cfgfile\"\n $!\n"; while() { s/(^\s+)//; #remove indentation if any if(/^$/) { next; } #ignore blank lines if(/^#/) { next; } #ignore comments chomp; if (eval "\'$fname\' =~ $_") { close CFGFILE; return 1; } } close CFGFILE; return 0; } sub dosystem { my $rc = system(@_) & 0xFFFF; if ($rc == 0) { return; } if ($rc == 0xff00 ) { print $LOG "Command [@_] failed: $!\n"; exit 0; } if ($rc > 0x80) { $rc >>= 8; print $LOG "Command [@_] ran with exit code $rc\n"; return; } if ($rc & 0x80) { printf $LOG "Core dumped from signal %d\n", $rc & 0x7F; exit 0; } else { printf $LOG "Interrupted by signal %d\n", $rc & 0x7F; exit 0; } print $LOG "Should never be able to get here\n"; } sub numerically { no warnings; $a <=> $b; } sub GetTime { my ($sec,$min,$hr,$mday,$mon,$yr,$wday,$yday,$isdst) = localtime(time()); my $date = sprintf("%02d/%02d/%04d",$mday,$mon+1,$yr+1900); my $time = sprintf("%02d:%02d:%02d",$hr,$min,$sec); return ($date, $time); }