home *** CD-ROM | disk | FTP | other *** search
- From: df@sei.cmu.edu (Dan Farmer)
- Newsgroups: alt.sources
- Subject: alpha perl-cops, 3 of 3
- Message-ID: <25552@as0c.sei.cmu.edu>
- Date: 17 May 91 04:47:32 GMT
-
- Submitted-by: df@death.cert.sei.cmu.edu
- Archive-name: alpha p-cops/part03
-
- #!/bin/sh
- # this is apcops.03 (part 3 of alpha p-cops)
- # do not concatenate these parts, unpack them in order with /bin/sh
- # file p-cops.alpha/user.chk continued
- #
- if test ! -r _shar_seq_.tmp; then
- echo 'Please unpack part 1 first!'
- exit 1
- fi
- (read Scheck
- if test "$Scheck" != 3; then
- echo Please unpack part "$Scheck" next!
- exit 1
- else
- exit 0
- fi
- ) < _shar_seq_.tmp || exit 1
- if test ! -f _shar_wnt_.tmp; then
- echo 'x - still skipping p-cops.alpha/user.chk'
- else
- echo 'x - continuing file p-cops.alpha/user.chk'
- sed 's/^X//' << 'SHAR_EOF' >> 'p-cops.alpha/user.chk' &&
- # even if invoked as an sh or csh or foosh script.
- # notice we don't use full path cause we don't
- # know where the user has perl on their system.
- #
- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
- & eval 'exec perl -S $0 $argv:q'
- X if $running_under_some_stupid_shell_instead_of_perl;
- X
- #
- # check for writable files in all user's homes
- #
- require "pass.cache.pl";
- require "is_able.pl";
- X
- # files checked for:
- @ftable = ("rhosts", "profile", "login", "logout", "cshrc",
- X "bashrc", "kshrc", "tcshrc", "netrc", "forward", "dbxinit",
- X "distfile", "exrc", "emacsrc", "remote", "mh_profile",
- X "xinitrc", "xsession", "Xdefaults", "Xresources", "rninit");
- X
- @readables = ("netrc", "rhosts");
- X
- local(%done);
- X
- for $i (keys %uname2dir) {
- X $dir = $uname2dir{$i};
- X # I don't want to hear about every file in their home dir, if
- X # is WW, but still need to check the .netrc file for readability...
- X next if $done{$dir}++;
- X if (-e $dir) {
- X if (&is_able($dir, "w", "w")) {
- X for $r (@readables) {
- X if (-s "$dir/.$r") {
- X &is_able("$dir/.$r", "w", "r");
- X }
- X }
- X next;
- X }
- X for $file (@ftable) {
- X $foo_file = $dir . "/.$file";
- X if (-e $foo_file) {
- X &is_able($foo_file, "w", "w");
- X for $r (@readables) {
- X if ($file eq $r && -s $foo_file) {
- X &is_able($foo_file, "w", "r");
- X }
- X }
- X }
- X }
- X }
- }
- X
- 1;
- SHAR_EOF
- echo 'File p-cops.alpha/user.chk is complete' &&
- chmod 0700 p-cops.alpha/user.chk ||
- echo 'restore of p-cops.alpha/user.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/user.chk'`"
- test 2048 -eq "$Wc_c" ||
- echo 'p-cops.alpha/user.chk: original size 2048, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/stat.pl ==============
- if test -f 'p-cops.alpha/stat.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/stat.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/stat.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/stat.pl' &&
- ;# $Header: stat.pl,v 3.0.1.1 90/08/09 04:01:34 lwall Locked $
- ;# Usage:
- ;# require 'stat.pl';
- ;# @ary = stat(foo);
- ;# $st_dev = @ary[$ST_DEV];
- ;#
- $ST_DEV = 0 + $[;
- $ST_INO = 1 + $[;
- $ST_MODE = 2 + $[;
- $ST_NLINK = 3 + $[;
- $ST_UID = 4 + $[;
- $ST_GID = 5 + $[;
- $ST_RDEV = 6 + $[;
- $ST_SIZE = 7 + $[;
- $ST_ATIME = 8 + $[;
- $ST_MTIME = 9 + $[;
- $ST_CTIME = 10 + $[;
- $ST_BLKSIZE = 11 + $[;
- $ST_BLOCKS = 12 + $[;
- X
- ;# Usage:
- ;# require 'stat.pl';
- ;# do Stat('foo'); # sets st_* as a side effect
- ;#
- sub Stat {
- X ($st_dev,$st_ino,$st_mode,$st_nlink,$st_uid,$st_gid,$st_rdev,$st_size,
- X $st_atime,$st_mtime,$st_ctime,$st_blksize,$st_blocks) = stat(shift(@_));
- }
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/stat.pl ||
- echo 'restore of p-cops.alpha/stat.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/stat.pl'`"
- test 653 -eq "$Wc_c" ||
- echo 'p-cops.alpha/stat.pl: original size 653, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/suckline.pl ==============
- if test -f 'p-cops.alpha/suckline.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/suckline.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/suckline.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suckline.pl' &&
- #
- # As title implies... :-)
- #
- sub main'suckline {
- X local($file, $_) = @_;
- # local($package) = caller;
- X
- # $file =~ s/^([^']+)$/$package'$1/;
- X {
- X if (s/\\\n?$//) {
- X $_ .= <$file>;
- X redo;
- X }
- X }
- X $_;
- }
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/suckline.pl ||
- echo 'restore of p-cops.alpha/suckline.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/suckline.pl'`"
- test 229 -eq "$Wc_c" ||
- echo 'p-cops.alpha/suckline.pl: original size 229, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/suid.chk ==============
- if test -f 'p-cops.alpha/suid.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/suid.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/suid.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suid.chk' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: suid.chk [-n] [-s secure_dir] [search_starting_directory]
- #
- # Shell script intended to be run periodically by cron in order
- # to spot changes in files with the suid or sgid bits set.
- #
- # suid.chk 840919 Prentiss Riddle
- #
- # This changes into the $SECURE directory first, then
- # uses find(1) to search the directories in $SEARCH for all
- # files with the 4000 or 2000 permission bits set. $STOP is a file
- # containing "ls -gildsa" output for known setuid or setgid programs.
- # Any additions or changes to this list represent potential security
- # problems, so they are reported.
- #
- # Modified 8/15/89, Dan Farmer:
- # Just changed the program/doc names and some of the temp
- # files to make it fit in with the rest of the programs....
- # Modified 12/26/90, df
- # Now flags SUID shell scripts and world writeable SUID files, too.
- #
- # Rewritten in perl, 1/17/91, df
- # Major hacks by tchrist 5/14/91
- #
- # NOTE:
- # If you know where perl is and your system groks #!, put its
- # pathname at the top to make this a tad faster.
- #
- # the following magic is from the perl man page
- # and should work to get us to run with perl
- # even if invoked as an sh or csh or foosh script.
- # notice we don't use full path cause we don't
- # know where the user has perl on their system.
- #
- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
- & eval 'exec perl -S $0 $argv:q'
- X if $running_under_some_stupid_shell_instead_of_perl;
- X
- require "hostname.pl";
- require "is_able.pl";
- require "file_owner.pl";
- require "pathconf.pl";
- require "chk_strings.pl";
- require "pass.cache.pl";
- package suid_chk; # name space protection
- $debug=0;
- X
- #
- # Getopts stuff
- $usage = "Usage: $0 [-n] [-s secure_dir] [starting_directory]\n";
- require 'getopts.pl';
- # Process the command args; Either specify verbose or an alternate config file:
- die $usage unless &Getopts('ns:');
- X
- $suid_dir = $'SECURE || '.';
- if (defined($opt_s)) { $suid_dir = $opt_s; }
- X
- # Do NFS stuff? Yes unless opt:
- if (defined($opt_n)) { $skip_nfs = $opt_n; }
- else { $skip_nfs = 0; }
- X
- $STOP="$suid_dir/suid.stop";
- $TEMPOLD="$suid_dir/fsold$$";
- $TEMPCUR="$suid_dir/fscur$$";
- $TEMPNEW="$suid_dir/fsnew$$";
- $TEMPGON="$suid_dir/fsgon$$";
- $TEMPM="$suid_dir/fsm$$";
- X
- if (@ARGV > 1) { die $usage; }
- elsif (@ARGV == 1) { $start_dir = shift; }
- X
- # these may be terribly rash assumptions....
- $start_dir="/" unless defined $start_dir;
- $find_can_ls = 1 unless defined $find_can_ls;
- X
- $NONFS = '\( -type f -fstype nfs -prune \) -o' if $skip_nfs;
- $find_ls = $find_can_ls ? '-ls' : "-exec $'LS -gilds {} \\;";
- X
- die "Error -- Security directory $suid_dir doesn't exist\n" unless -d $suid_dir;
- unless (-d $suid_dir) {
- X mkdir($suid_dir, 0700) || die "can't mkdir $suid_dir: $!";
- X }
- chdir $suid_dir || die "can't chdir $suid_dir: $!";
- X
- # find the setuid programs and sort
- &run("$'FIND $start_dir $NONFS -type f \\( -perm -4000 -o -perm -2000 \\) $find_ls | $'SORT > $TEMPCUR");
- X
- # compare with the sorted stop list
- # create stop file if needed
- if (! -f $STOP) { open(S,">$STOP"); close(S); }
- X
- &run("$'SORT <$STOP >$TEMPOLD");
- &run("$'COMM -13 $TEMPOLD $TEMPCUR | $'SORT +8 >$TEMPNEW");
- &run("$'COMM -23 $TEMPOLD $TEMPCUR | $'SORT +8 >$TEMPGON");
- X
- local($is_able'silent) = 1;
- local($chk_strings'recurse) = 0 unless defined $chk_strings'recurse;
- X
- # report changes
- if (-s $TEMPNEW || -s $TEMPGON) {
- X if (-s $TEMPNEW) {
- X die "Can't open $TEMPNEW: $!" unless open TEMPNEW;
- X while (<TEMPNEW>) {
- X ($file) = /(\S+)$/;
- X
- X # don't want SUID files to be world writable!
- X # although *reasonable* systems clear the bit on write
- X print "Warning! SUID file $file is _World_ writable!\n"
- X if &'is_able ($file, "w", "w");
- X
- X if (-r $file && -f _ && -T $file) {
- X print "Warning! ", &'Owner($file) ? '' : 'ROOT-owned ',
- X "SUID file $file is a non-binary, executable file!\n";
- X }
- X
- X &'chk_strings($file) if -r _;
- X }
- X close TEMPNEW;
- X }
- X
- X if (-s $TEMPNEW) {
- X die "Can't reopen $TEMPNEW: $!" unless open TEMPNEW;
- X print "\nThese files are newly setuid/setgid:\n\n";
- X print while <TEMPNEW>;
- X }
- X
- X if (-s $TEMPGON) {
- X die "Can't reopen $TEMPGON: $!" unless open TEMPGON;
- X print "\nThese files are no longer setuid/setgid:\n\n";
- X print while <TEMPGON>;
- X }
- X
- }
- X
- unlink $TEMPOLD, $TEMPCUR, $TEMPNEW, $TEMPGON;
- X
- sub run {
- X print "running: $_[0]\n" if $debug;
- X system $_[0];
- X warn "command $_[0] returned $?" if $?;
- }
- X
- # end it all....
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/suid.chk ||
- echo 'restore of p-cops.alpha/suid.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/suid.chk'`"
- test 4477 -eq "$Wc_c" ||
- echo 'p-cops.alpha/suid.chk: original size 4477, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/root.chk ==============
- if test -f 'p-cops.alpha/root.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/root.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/root.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: root.chk
- #
- # This script checks pathnames inside root's startup files for
- # writability, improper umask settings (world writable), non-root
- # entries in /.rhosts, writable binaries in root's path,
- # and to ensure that root is in /etc/ftpuser.
- #
- # Also check for a single "+" in /etc/hosts.equiv (world is trusted),
- # and that /bin, /etc and certain key files are root owned, so that you
- # can't, say, rcp from a host.equived machine and blow over the password
- # file... this may or may not be bad, decide for yourself.
- # Startup files are /.login /.cshrc /.profile
- #
- # Mechanism: These files contain paths and filenames that are stripped
- # out using "grep". These strings are then processed by the "is_able"
- # program to see if they are world writable. Strings of the form:
- #
- # path=(/bin /usr/bin .)
- # and
- # PATH=/bin:/usr/bin:.:
- #
- # are checked to ensure that "." is not in the path. All
- # results are echoed to standard output. In addition, some effort was
- # put into parsing out paths with multiple lines; e.g. ending in "\",
- # and continuing on the next line. Also, all executable files and
- # directories in there are checked for writability as well.
- #
- # For umask stuff, simply grep for umask in startup files, and check
- # umask value. For /etc/ftpuser, simple grep to check if root is in
- # the file. For /etc/hosts.equiv, just check to see if "+" is alone
- # on a line by awking it.
- #
- # NOTE:
- # if you know where perl is and your system groks #!, put its
- # pathname at the top to make this a tad faster.
- #
- # the following magic is from the perl man page
- # and should work to get us to run with perl
- # even if invoked as an sh or csh or foosh script.
- # notice we don't use full path cause we don't
- # know where the user has perl on their system.
- #
- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
- & eval 'exec perl -S $0 $argv:q'
- X if $running_under_some_stupid_shell_instead_of_perl;
- X
- # rewritten in perl by tchrist@convex.com
- #
- X
- # root startup/important files
- X
- require 'file_owner.pl';
- require 'fgrep.pl';
- require 'suckline.pl';
- require 'is_able.pl';
- require 'chk_strings.pl';
- require 'glob.pl';
- X
- package root_chk;
- X
- # use -a true if you care about non-executables
- # in root's path
- X
- $ARGV[0] eq '-a' && ($all_files++, shift);
- X
- die "usage: root.chk [-a]\n" if @ARGV;
- X
- $W = 'Warning! ';
- X
- $cshrc = '/.cshrc';
- $profile= '/.profile';
- $rhosts = '/.rhosts';
- X
- $| = 1;
- X
- @big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
- X
- # root should own *at least* these, + $big_files; you can check for all files
- # in /bin & /etc, or just the directories (the default.)
- # root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
- @root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
- X
- # misc important stuff
- $ftp='/etc/ftpusers';
- $equiv='/etc/hosts.equiv';
- X
- # should't have anyone but root owning /bin or /etc files/directories
- # In case some of the critical files don't exist (/.rhost), toss away error
- # messages
- X
- if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
- X print "$W Root does not own the following file(s):\n";
- X print "\t@bad_files\n";
- }
- X
- local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
- X
- for $file (@big_files) {
- X open file || next;
- X
- X &'chk_strings($file);
- X
- X # check for group or other writable umask
- X while (<file>) {
- X next if /^\s*#/;
- X next unless /umask\s*(\d+)/;
- X next unless ~oct($1) & 022;
- X print "$W root's umask set to $1 in $file\n";
- X }
- }
- X
- print "$W $ftp exists and root is not in it\n"
- X if -e $ftp && !&'fgrep($ftp,'root');
- X
- print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
- X
- if (open rhosts) {
- X while (<rhosts>) {
- X next unless /\S+\s+(\S+)/ && $1 ne 'root';
- X print "$W Non-root entry in $rhosts! $1\n";
- X }
- }
- close(rhosts);
- X
- undef @rootpath;
- X
- # checking paths...
- #
- # Get the root paths from $csh.
- X
- if (open(CSHRC, $cshrc)) {
- X $path = '';
- X while (<CSHRC>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- X $_ = &'suckline($cshrc, $_);
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- X for (@tmppath) { $whence{$_} .= " " . $cshrc; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(CSHRC);
- }
- X
- if (open login) {
- X $path = '';
- X while (<cshrc>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- X $_ = &'suckline('login', $_);
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- X for (@tmppath) { $whence{$_} .= " " . $login; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(login);
- }
- X
- if (open profile) {
- X $path = '';
- X while (<profile>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/PATH=/) {
- X $_ = &'suckline('profile', $_);
- X s/.*PATH=//;
- X s/#.*//;
- X @tmppath = split(/:/);
- X for (@tmppath) { $whence{$_} .= " " . $profile; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(profile);
- }
- X
- for (keys %whence) {
- X $whence{$_} =~ s/^ //;
- X $whence{$_} =~ s/ / and /g;
- }
- X
- undef %seen;
- grep($seen{$_}++, @rootpath);
- X
- $is_able'silent = 1;
- for (keys %seen) {
- X if (!-e && $_ ne ".") {
- X print "$W path component $_ in $whence{$_} doesn't exist!\n";
- X next;
- X }
- X
- X if (/^\.?$/) { # null -> dot
- X print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
- X } elsif (&'is_writable($_)) {
- X print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
- X next;
- X }
- X
- X foreach $file (&'glob("$_/*")) {
- X # can't just check -x here, as that depends on current user
- X $is_executable = -f $file && (&'Mode($file) & 0111);
- X if (($all_files || $is_executable) &&
- X ($how = &'is_writable($file, 'w', 'w'))) {
- X print "$W _World_ $how ",
- X $is_executable ? 'executable' : 'file',
- X " $file in root path component $_ from $whence{$_}!\n";
- X }
- X }
- }
- X
- $is_able'silent = 0;
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/root.chk ||
- echo 'restore of p-cops.alpha/root.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/root.chk'`"
- test 5959 -eq "$Wc_c" ||
- echo 'p-cops.alpha/root.chk: original size 5959, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/README.perl ==============
- if test -f 'p-cops.alpha/README.perl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/README.perl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/README.perl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/README.perl' &&
- X
- **ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**
- X
- (Warning! This is the alpha version of p-cops, or perl cops... beta
- will be forthcoming shortly...)
- X
- X This will attempt to familiarize you with the perl version of cops. If
- you have never used cops before, you can get the complete documentation,
- along with shell/C code, via anon-ftp, at cert.sei.cmu.edu, ~ftp/pub/cops/1.02.
- Ok; the main difference with this version is that (besides being written
- in perl) there is a config file -- "cops.cf", that is used to control the
- cruft inside all of the modules. No more muss, no more fuss. I'll try
- to go over the main ones here. The only important thing you have to set in
- the "cops" main file (or via the "-s" flag) is the secure directory, which
- by default is "."; this is where cops will look for the config file and all
- the programs. Also, if something is flagged as world-writable, and the file
- itself is not writable, but the parent directory is, then there will be an
- asterix after the warning (e.g. /usr/foo/bar is World Writable! (*).)
- Finally, the suid.chk program, like all the rest of the programs, is meant
- to be run as a part of "cops"; it's output will go to stdout if run
- standalone, or either get mailed or saved to the result file if run under
- cops. That's a bit different than the old version. Depending on comments,
- I'll either keep it this way, or put in some more options.
- X
- X As said in the config file -- "cops.cf" (a "#" sign denotes comments):
- X
- # anything beginning with /^\s*[$@%&]/ will be eval'd
- X
- X In general, you can put variables and programs that will be run inside
- the config file. Variables look startlingly like they do in normal perl
- (look at the "PROGRAMS" section below for more on running programs); e.g.:
- X
- $NMAIL = 0; # send mail instead of generating reports
- $ONLY_DIFF = 0; # only send diff from last time
- $SECURE_USERS = "root"; # user to receive mailed report
- X
- X Setting something to "0" (without quotes is fine) generally means that
- the option is not used. "1" (or non-zero values, if you feel gutsy) is
- used for a positive/true/whatever value. The variables in general should
- be very similar to their normal cops counterparts; in this case, setting
- NMAIL to 1 would mean to mail info to the user listed in SECURE_USERS.
- If ONLY_DIFF is 1, it will only mail reports if change has occurred.
- X
- X In general, variables in package main are for cops itself, whereas
- those with package qualifiers are for a particular chk routine or
- for auxiliary routines. For instance, the following lines:
- X
- # this one says to ignore warnings about paths matching these regexps
- @chk_strings'ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' );
- X
- X "chk_strings" is a routine that checks for writable programs within other
- programs, usually executed by root, such as /etc/rc and crontab. This line
- says to ignore any files that start with a "/tmp", "/var/tmp", or "/usr/tmp".
- If you have a file or set of files that always are returning writable that
- are inside your rc and cron files, then you can put exceptions here. One
- possibility is that you don't care about files created by other programs,
- so that anything after a ">" should be ignored. You might add something
- like '>.*' to ignore files like "/usr/bar/snowcone", in a line like
- "/foo/bar/command > /usr/bar/snowcone".
- X
- X Next, there is a nifty option, that does recursive searching inside the
- files chk_strings looks at. This is neat... get it working by setting this
- to 1:
- X
- $chk_strings'recurse = 1;
- X
- X So, if you have a line like this in /etc/rc:
- X
- /usr/bin/foo > /dev/console
- X
- X It will examine "/usr/bin/foo" for programs inside of it -- and it will
- keep going until it has exhausted all possibilities. So you can get warning
- messages like:
- X
- Warning! File /foo/bar (inside /usr/local/X11R4/bin/X inside /usr/local/X11R4
- /bin/xdm inside /etc/rc.local) is _World_ writable!
- X
- X Fun stuff. No one can hide, now...
- X
- PROGRAMS
- =========
- X
- X Running a program within cops is easy; you just have the program with
- any options by itself on a line. Semi-colons are not welcome here.
- E.g.:
- X
- # first test the security of the root account
- root.chk
- X
- X Some variables specific to the various programs are here as well, e.g.:
- X
- # now of the various devices. -g means to check group writability, too
- $MTAB = '/etc/fstab';
- $EXPORTS = '/etc/exports';
- $TAB_STYLE = 'new';
- dev.chk -g
- X
- X This is specifying the export files, etc., and saying that you should
- use the "new" format style in the exports file. Ultrix, etc. uses the
- old style. Suid.chk eats up time -- consider the "-n" flag for systems
- that have big NFS mounted disks. And that's it -- the rest should be very
- similar to the old cops, and theoretically, should give you similar or
- the same results.
- X
- X Good luck! Send bugs, flames, etc. to df@cert.sei.cmu.edu
- X
- X -- dan
- SHAR_EOF
- chmod 0700 p-cops.alpha/README.perl ||
- echo 'restore of p-cops.alpha/README.perl failed'
- Wc_c="`wc -c < 'p-cops.alpha/README.perl'`"
- test 4848 -eq "$Wc_c" ||
- echo 'p-cops.alpha/README.perl: original size 4848, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/root.chk.old ==============
- if test -f 'p-cops.alpha/root.chk.old' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/root.chk.old (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/root.chk.old (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk.old' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: root.chk
- #
- # This script checks pathnames inside root's startup files for
- # writability, improper umask settings (world writable), non-root
- # entries in /.rhosts, writable binaries in root's path,
- # and to ensure that root is in /etc/ftpuser.
- #
- # Also check for a single "+" in /etc/hosts.equiv (world is trusted),
- # and that /bin, /etc and certain key files are root owned, so that you
- # can't, say, rcp from a host.equived machine and blow over the password
- # file... this may or may not be bad, decide for yourself.
- # Startup files are /.login /.cshrc /.profile
- #
- # Mechanism: These files contain paths and filenames that are stripped
- # out using "grep". These strings are then processed by the "is_able"
- # program to see if they are world writable. Strings of the form:
- #
- # path=(/bin /usr/bin .)
- # and
- # PATH=/bin:/usr/bin:.:
- #
- # are checked to ensure that "." is not in the path. All
- # results are echoed to standard output. In addition, some effort was
- # put into parsing out paths with multiple lines; e.g. ending in "\",
- # and continuing on the next line. Also, all executable files and
- # directories in there are checked for writability as well.
- #
- # For umask stuff, simply grep for umask in startup files, and check
- # umask value. For /etc/ftpuser, simple grep to check if root is in
- # the file. For /etc/hosts.equiv, just check to see if "+" is alone
- # on a line by awking it.
- #
- # NOTE:
- # if you know where perl is and your system groks #!, put its
- # pathname at the top to make this a tad faster.
- #
- # the following magic is from the perl man page
- # and should work to get us to run with perl
- # even if invoked as an sh or csh or foosh script.
- # notice we don't use full path cause we don't
- # know where the user has perl on their system.
- #
- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
- & eval 'exec perl -S $0 $argv:q'
- X if $running_under_some_stupid_shell_instead_of_perl;
- X
- # rewritten in perl by tchrist@convex.com
- #
- X
- # root startup/important files
- X
- require 'file_owner.pl';
- require 'fgrep.pl';
- require 'suckline.pl';
- require 'is_able.pl';
- require 'chk_strings.pl';
- require 'glob.pl';
- X
- package root_chk;
- X
- # use -a true if you care about non-executables
- # in root's path
- X
- $ARGV[0] eq '-a' && ($all_files++, shift);
- X
- die "usage: root.chk [-a]\n" if @ARGV;
- X
- $W = 'Warning! ';
- X
- $cshrc = '/.cshrc';
- $profile= '/.profile';
- $rhosts = '/.rhosts';
- X
- $| = 1;
- X
- @big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
- X
- # root should own *at least* these, + $big_files; you can check for all files
- # in /bin & /etc, or just the directories (the default.)
- # root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
- @root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
- X
- # misc important stuff
- $ftp='/etc/ftpusers';
- $equiv='/etc/hosts.equiv';
- X
- # should't have anyone but root owning /bin or /etc files/directories
- # In case some of the critical files don't exist (/.rhost), toss away error
- # messages
- X
- if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
- X print "$W Root does not own the following file(s):\n";
- X print "\t@bad_files\n";
- }
- X
- local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
- X
- for $file (@big_files) {
- X open file || next;
- X
- X &'chk_strings($file);
- X
- X # check for group or other writable umask
- X while (<file>) {
- X next if /^\s*#/;
- X next unless /umask\s*(\d+)/;
- X next unless ~oct($1) & 022;
- X print "$W root's umask set to $1 in $file\n";
- X }
- }
- X
- print "$W $ftp exists and root is not in it\n"
- X if -e $ftp && !&'fgrep($ftp,'root');
- X
- print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
- X
- if (open rhosts) {
- X while (<rhosts>) {
- X next unless /\S+\s+(\S+)/ && $1 ne 'root';
- X print "$W Non-root entry in $rhosts! $1\n";
- X }
- }
- close(rhosts);
- X
- undef @rootpath;
- X
- # checking paths...
- #
- # Get the root paths from $csh.
- X
- if (open $cshrc) {
- X $path = '';
- X while (<cshrc>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- print "FOO-3: @rootpath , $_\n";
- X $_ = &'suckline(cshrc, $_);
- print "FOO-2: @rootpath , $_\n";
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- print "FOO-1: @rootpath , $_\n";
- X for (@tmppath) { $whence{$_} .= " " . $cshrc; }
- X push(@rootpath, @tmppath);
- print "FOO-0: @rootpath , $_\n";
- X }
- X }
- X close(cshrc);
- }
- X
- print "FOO0: @rootpath\n";
- X
- if (open login) {
- X $path = '';
- X while (<cshrc>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- X $_ = &'suckline('login', $_);
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- X for (@tmppath) { $whence{$_} .= " " . $login; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(login);
- }
- X
- print "FOO1: @rootpath\n";
- X
- if (open profile) {
- X $path = '';
- X while (<profile>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/PATH=/) {
- X $_ = &'suckline('profile', $_);
- X s/.*PATH=//;
- X s/#.*//;
- X @tmppath = split(/:/);
- X for (@tmppath) { $whence{$_} .= " " . $profile; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(profile);
- }
- X
- print "FOO2: @rootpath\n";
- X
- for (keys %whence) {
- X $whence{$_} =~ s/^ //;
- X $whence{$_} =~ s/ / and /g;
- }
- X
- undef %seen;
- grep($seen{$_}++, @rootpath);
- X
- $is_able'silent = 1;
- for (keys %seen) {
- X print "WAK: $_\n";
- X if (!-e && $_ ne ".") {
- X print "$W path component $_ in $whence{$_} doesn't exist!\n";
- X next;
- X }
- X
- X if (/^\.?$/) { # null -> dot
- X print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
- X } elsif (&'is_writable($_)) {
- X print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
- X next;
- X }
- X
- X foreach $file (&'glob("$_/*")) {
- X print "BAR: $_\n";
- X # can't just check -x here, as that depends on current user
- X $is_executable = -f $file && (&'Mode($file) & 0111);
- X if (($all_files || $is_executable) &&
- X ($how = &'is_writable($file, 'w', 'w'))) {
- X print "$W _World_ $how ",
- X $is_executable ? 'executable' : 'file',
- X " $file in root path component $_ from $whence{$_}!\n";
- X }
- X }
- }
- X
- $is_able'silent = 0;
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/root.chk.old ||
- echo 'restore of p-cops.alpha/root.chk.old failed'
- Wc_c="`wc -c < 'p-cops.alpha/root.chk.old'`"
- test 6206 -eq "$Wc_c" ||
- echo 'p-cops.alpha/root.chk.old: original size 6206, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/README.kuang ==============
- if test -f 'p-cops.alpha/README.kuang' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/README.kuang (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/README.kuang (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/README.kuang' &&
- This is a perl version of Dan's version of Bob Baldwin's Kuang program
- (originally written as some shell scripts and C programs).
- X
- The original intent was to improve the speed of kuang, which is
- especially important for installations like ours with several thousand
- accounts and NFS things and all that. The shell version of Kuang used
- C programs to add rules, get a groups members, determine the writers
- of a file, and so on, which really slowed things down.
- X
- X "no" problems /etc staff writeable
- X ------------- --------------------
- shell kuang 2:14 (14) 12:26 (98) 0.1 p/s
- perl kuang 1:10 (18) 2:34 (588) 3.8 p/s
- X
- --- Steve Romig, CIS, Ohio State, October 1990
- X
- ------------------------------------------------------------------------------
- X
- Some Features
- ---- --------
- X
- X Caches passwd/group file entries in an associative array for faster
- X lookups. This is particularly helpful on insecure systems using YP
- X where password and group lookups are slow and you have to do alot of
- X them...:-)
- X
- X Can specify target (uid or gid) on command line.
- X
- X Can use -l option to generate PAT for a goal.
- X
- X Can use -f to preload file owner, group and mode info, which is
- X helpful in speeding things up and in avoiding file system
- X 'shadows'... See the man page for details.
- X
- Future plans, things to fix:
- ----------------------------
- X
- - In a large environment (like ours, 260+ machines, 30+ file systems
- X on as many servers, 2000 password file entries served by YP) it
- X would be nice to 'precompute' successful plans that would be common
- X to all systems. In particular, plans for becoming most of the users
- X with home directories on the NFS file systems would be useful, since
- X we don't really want to recheck these on each host. You wouldn't
- X want the plan to be too deep - probably shouldn't span more than 2
- X uids (1 on each end: grant u.romig grant g.staff write ~foo/.login
- X grant u.foo). I'm thinking that you could feed a list of these
- X precomputed plans to kuang and add some code that causes it to
- X splice in relevent plans where it can to short cut the planning
- X steps. For example, if one of the plans in uids.next is something
- X like "grant u.foo ...", and I have the precomputed plan mentioned
- X above, I could splice the two: "grant u.romig grant g.staff write
- X ~foo/.login grant u.foo ..." and skip all the normal steps that
- X would've been taken to get there.
- X
- - Hmmm...thinking about it, it seems like some of the steps are a bit
- X too implicit...maybe the rules should be broken out a bit more.
- X That will cost in processing time, though.
- X
- - Would be really, really nice to be able to deal with PATH variables
- X - location of ., who can write elements of path, etc. Basic rule is
- X "anyone who can replace anything in any of path directories or the
- X path directories themselves can become that PATH's user..." This
- X can be really messy though - in our environment, the path for a user
- X will depend on the architecture type of the machine that he is
- X logged into, and to get the path, you'd have to read and interpret
- X his .login (including variable assignments, source's and
- X conditionals). Urf. One wonders whether it might be better to have
- X something running as root that su's to each username in turn and
- X gets the path that way...
- X
- - ignore plans that start with "uid root", unless that's the only element - root
- X can get to anything, and hopefully nothing can get to root...?
- X
- - remove duplicate names from uid2names and gid2names...
- X
- - with ~/.login world writeable - only follows group path, but not OTHER.
- X
- - add plans to asseccible list.
- X
- Done
- ----
- X
- - Need to find all plans that lead to compromise, not just a plan.
- X
- - An earlier version scanned the password file looking for generally
- X accesible accounts (no password), which would be added to the
- X uids.known list (in addition to -1, "other"). I had planned on also
- X adding a password checker which would allow us to also add accounts
- X with easily guessed passwords. Eventually I nuked the code that
- X scanned the password file to speed things up, and further reflection
- X reveals that it isn't wise to add the password scanning to kuang
- X itself. At some point we should add a comand line option that
- X allows us to add additional uid's (or gid's?) to the uids.known
- X list. That way the user could run some other tool to scan the
- X password file and generate a list of accessible accounts, which
- X could then be fed to kuang. Makes it faster on clients using YP
- X since most of the password file is the same for all N clients, why
- X scan it N times. Means that user can do smarter things to/with the
- X password file checks (list all accounts with no password or easily
- X guessed password, filter out "ok" entries (eg, sync) and etc.)
- X
- - We aren't dealing with uid's and gid's correctly. If there are
- X several entries that list the same UID, but with different names,
- X directories and shells, we'll only check plans for becoming one of
- X them, rather than any of them. Hmmm...this is easier than I
- X thought, when we evaluate some plan for granting a particular uid,
- X we need to evaluate plans for all usernames that can become that
- X uid. Just stick a loop in there somewhere...get CF's for each of
- X username's in turn. Bah, harder than I thought, since it'd have to
- X scan the whole password file to figure which username/home directories
- X can become which uid's. Similarly with groups.
- X
- X Current plan: by default, kuang will have to scan the whole password
- X and group files so it can be sure to get all possible ways to become
- X some uid or gid. Internally, really need several lists:
- X
- X mapping from uid to list of usernames that have that uid
- X mapping from a username to home directory, shell
- X mapping from gid to list of uids that have access to that
- X gid when they login (either member of group with that gid or
- X given as login group in passwd file)
- X mapping from gid to list of group names for that gid
- X
- X Course, this means that we have to read the whole password and group
- X file, most of which will be common to many machines (like in a YP
- X environment). We could preload the tables above from files created
- X once, containing the brunt of the YP info, and then augment that
- X withthe local passwd and group info on each host when kuang is
- X invoked, but then we need to correctly interpret funky YP things
- X like +@netgroup:::*:..., which means that the uid has a name but no
- X password here...and similarly with shell substitutions and so on.
- X Bah.
- X
- - The kuang described in Baldwin's dissertation is somewhat different
- X in nature from this one. The original computes a Privilege Access
- X Table (PAT) which describes for each uid and gid which uids have
- X access to that uid. To assess security, we compare this against the
- X security policy for the site, which similarly describes which uid's
- X are supposed to have access to each uid and gid. A sample SP might
- X be that each uid should be accessible only by itself and root, and
- X each gid should be accessible only to the members of that group and
- X root. If the PAT listed additional uid's for some priv, that would
- X constitute a violation of the Security Policy for the site.
- X
- X The current kuang is different. It registers Success (a problem was
- X found) if it determines that some uid in the uids.known list (-1,
- X "other" by default) can access the target privilege. It may find
- X along the way that extra uids can access some uid, but these aren't
- X reported as specific problems unless they are added to the
- X uids.known list.
- X
- X We could do something similar to the kuang described in the paper by
- X setting uids.known to be all the uids that aren't in the security
- X policy table for the target uid, and running kuang against the
- X target. This would report success for each uid that could access
- X the target. You could do similar things with groups - uids.known
- X would be all the uids that aren't members of the group...
- X
- X Alternately, we could simply have kuang record the list of uids that
- X can access the target priv and print the list when its done. That
- X way you could iterate kuang against all uids and gids and compare
- X the resulting PAT against your security policy and record the
- X differences. You'd probably want to record the plan for each uid
- X reported also.
- X
- X On our system this would mean running kuang roughly 2500
- X times to check 1 host, and we have about 300 hosts...urf...assuming
- X that each kuang invocation has to check 50 plans, that's a total of
- X 125,000 plans per host, or about an hour of real time...not as bad
- X as it could be, though.
- X
- - It would be nice to add to the list of rules. It would be especialy
- X nice to extract the rules from the code so that we can create site
- X specific rule files (for example, we use X11r4 here, and many users
- X have a .Xinitrc that contains shell commands that get executed when
- X they login.)
- X
- X Easiest way to do this would be to extract the rules as Perl code so
- X we can take advantage of conditionals and so on, and include them
- X within the body of kuang somehow. A sample rule in perl:
- X
- X if (&shell($uid) eq "/bin/csh") {
- X &addto("files", &home($uid)."/.login",
- X "replace .login $plan");
- X }
- X
- X which simply means "if the user's shell is csh, then try to replace
- X his .login file."
- SHAR_EOF
- chmod 0600 p-cops.alpha/README.kuang ||
- echo 'restore of p-cops.alpha/README.kuang failed'
- Wc_c="`wc -c < 'p-cops.alpha/README.kuang'`"
- test 9306 -eq "$Wc_c" ||
- echo 'p-cops.alpha/README.kuang: original size 9306, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/suid.stop ==============
- if test -f 'p-cops.alpha/suid.stop' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/suid.stop (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/suid.stop (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suid.stop' &&
- SHAR_EOF
- chmod 0700 p-cops.alpha/suid.stop ||
- echo 'restore of p-cops.alpha/suid.stop failed'
- Wc_c="`wc -c < 'p-cops.alpha/suid.stop'`"
- test 0 -eq "$Wc_c" ||
- echo 'p-cops.alpha/suid.stop: original size 0, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/pwgrid.pl ==============
- if test -f 'p-cops.alpha/pwgrid.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/pwgrid.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/pwgrid.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pwgrid.pl' &&
- # Routines for reading and caching user and group information.
- #
- # Provides a bunch of routines and a bunch of arrays. Routines
- # (and their usage):
- #
- # load_passwd_info($use_getent, $file_name)
- #
- # loads user information into the %uname* and %uid* arrays
- # (see below).
- #
- # If $use_getent is non-zero:
- # get the info via repeated 'getpwent' calls. This can be
- # *slow* on some hosts, especially if they are running as a
- # YP (NIS) client.
- # If $use_getent is 0:
- # if $file_name is "", then get the info from reading the
- # results of "ypcat passwd" and from /etc/passwd. Otherwise,
- # read the named file. The file should be in passwd(5)
- # format.
- #
- # load_group_info($use_gentent, $file_name)
- #
- # is similar to load_passwd_info.
- #
- # Information is stored in several convenient associative arrays:
- #
- # %uname2shell Assoc array, indexed by user name, value is
- # shell for that user name.
- #
- # %uname2dir Assoc array, indexed by user name, value is
- # home directory for that user name.
- #
- # %uname2uid Assoc array, indexed by name, value is uid for
- # that uid.
- #
- # %uname2passwd Assoc array, indexed by name, value is password
- # for that user name.
- #
- # %uid2names Assoc array, indexed by uid, value is list of
- # user names with that uid, in form "name name
- # name...".
- #
- # %gid2members Assoc array, indexed by gid, value is list of
- # group members in form "name name name..."
- #
- # %gname2gid Assoc array, indexed by group name, value is
- # matching gid.
- #
- # %gid2names Assoc array, indexed by gid, value is the
- # list of group names with that gid in form
- # "name name name...".
- #
- # You can also use routines named the same as the arrays - pass the index
- # as the arg, get back the value. If you use this, get{gr|pw}{uid|gid|nam}
- # will be used to lookup entries that aren't found in the cache.
- #
- # To be done:
- # probably ought to add routines to deal with full names.
- # maybe there ought to be some anal-retentive checking of password
- # and group entries.
- # probably ought to cache get{pw|gr}{nam|uid|gid} lookups also.
- # probably ought to avoid overwriting existing entries (eg, duplicate
- # names in password file would collide in the tables that are
- # indexed by name).
- #
- # Disclaimer:
- # If you use YP and you use netgroup entries such as
- # +@servers::::::
- # +:*:::::/usr/local/utils/messages
- # then loading the password file in with &load_passwd_info(0) will get
- # you mostly correct YP stuff *except* that it won't do the password and
- # shell substitutions as you'd expect. You might want to use
- # &load_passwd_info(1) instead to use getpwent calls to do the lookups,
- # which would be more correct.
- #
- X
- %uname2shell = ();
- %uname2dir = ();
- %uname2uid = ();
- %uname2passwd = ();
- %uid2names = ();
- %gid2members = ();
- %gname2gid = ();
- %gid2names = ();
- X
- if (! defined($DOMAINNAME)) {
- X $DOMAINNAME = "/bin/domainname";
- }
- if (! defined($YPCAT)) {
- X $YPCAT = "/bin/ypcat";
- }
- X
- $passwd_loaded = 0; # flags to use to avoid reloading everything
- $group_loaded = 0; # unnecessarily...
- X
- #
- # We provide routines for getting values from the data structures as well.
- # These are named after the data structures they cache their data in. Note
- # that they will all generate password and group file lookups via getpw*
- # and getgr* if they can't find info in the cache, so they will work
- # "right" even if load_passwd_info and load_group_info aren't called to
- # preload the caches.
- #
- # I should point out, however, that if you don't call load_*_info to preload
- # the cache, uid2names, gid2names and gid2members *will not* be complete, since
- # you must read the entire password and group files to get a complete picture.
- # This might be acceptable in some cases, so you can skip the load_*_info
- # calls if you know what you are doing...
- #
- sub uname2shell {
- X local($key) = @_;
- X
- X if (! defined($uname2shell{$key})) {
- X &add_pw_info(getpwnam($key));
- X }
- X
- X return($uname2shell{$key});
- }
- X
- sub uname2dir {
- X local($key) = @_;
- X local(@pw_info);
- X
- X if (! defined($uname2dir{$key})) {
- X &add_pw_info(getpwnam($key));
- X }
- X
- X return($uname2dir{$key});
- }
- X
- sub uname2uid {
- X local($key) = @_;
- X local(@pw_info);
- X
- X if (! defined($uname2uid{$key})) {
- X &add_pw_info(getpwnam($key));
- X }
- X
- X return($uname2uid{$key});
- }
- X
- sub uname2passwd {
- X local($key) = @_;
- X local(@pw_info);
- X
- X if (! defined($uname2passwd{$key})) {
- X &add_pw_info(getpwnam($key));
- X }
- X
- X return($uname2passwd{$key});
- }
- X
- sub uid2names {
- X local($key) = @_;
- X local(@pw_info);
- X
- X if (! defined($uid2names{$key})) {
- X &add_pw_info(getpwuid($key));
- X }
- X
- X return($uid2names{$key});
- }
- X
- sub gid2members {
- X local($key) = @_;
- X local(@gr_info);
- X
- X if (! defined($gid2members{$key})) {
- X &add_gr_info(getgrgid($key));
- X }
- X
- X return($gid2members{$key});
- }
- X
- sub gname2gid {
- X local($key) = @_;
- X local(@gr_info);
- X
- X if (! defined($gname2gid{$key})) {
- X &add_gr_info(getgrnam($key));
- X }
- X
- X return($gname2gid{$key});
- }
- X
- sub gid2names {
- X local($key) = @_;
- X local(@gr_info);
- X
- X if (! defined($gid2names{$key})) {
- X &add_gr_info(getgrgid($key));
- X }
- X
- X return($gid2names{$key});
- }
- X
- #
- # Update user information for the user named $name. We cache the password,
- # uid, login group, home directory and shell.
- #
- X
- sub add_pw_info {
- X local($name, $passwd, $uid, $gid) = @_;
- X local($dir, $shell);
- X
- #
- # Ugh! argh...yech...sigh. If we use getpwent, we get back 9 elts,
- # if we parse /etc/passwd directly we get 7. Pick off the last 2 and
- # assume that they are the $directory and $shell.
- #
- X $dir = $_[$#_ - 1];
- X $shell = $_[$#_];
- X
- X if ($name ne "") {
- X $uname2shell{$name} = $shell;
- X $uname2dir{$name} = $dir;
- X $uname2uid{$name} = $uid;
- X $uname2passwd{$name} = $passwd;
- X
- X if ($gid ne "") {
- X # fixme: should probably check for duplicates...sigh
- X
- X if (defined($gid2members{$gid})) {
- X $gid2members{$gid} .= " $name";
- X } else {
- X $gid2members{$gid} = $name;
- X }
- X }
- X
- X if ($uid ne "") {
- X if (defined($uid2names{$uid})) {
- X $uid2names{$uid} .= " $name";
- X } else {
- X $uid2names{$uid} = $name;
- X }
- X }
- X }
- }
- X
- #
- # Update group information for the group named $name. We cache the gid
- # and the list of group members.
- #
- X
- sub add_gr_info {
- X local($name, $passwd, $gid, $members) = @_;
- X
- X if ($name ne "") {
- X $gname2gid{$name} = $gid;
- X
- X if ($gid ne "") {
- X if (defined($gid2names)) {
- X $gid2names{$gid} .= " $name";
- X } else {
- X $gid2names{$gid} = $name;
- X }
- X
- X # fixme: should probably check for duplicates
- X
- X $members = join(' ', split(/[, \t]+/, $members));
- X
- X if (defined($gid2members{$gid})) {
- X $gid2members{$gid} .= " " . $members;
- X } else {
- X $gid2members{$gid} = $members;
- X }
- X }
- X }
- }
- X
- #
- # We need to suck in the entire group and password files so that we can
- # make the %uid2names, %gid2members and %gid2names lists complete. Otherwise,
- # we would just read the entries as needed with getpw* and cache the results.
- # Sigh.
- #
- # There are several ways that we might find the info. If $use_getent is 1,
- # then we just use getpwent and getgrent calls to read the info in.
- #
- # That isn't real efficient if you are using YP (especially on a YP client), so
- # if $use_getent is 0, we can use ypcat to get a copy of the passwd and
- # group maps in a fairly efficient manner. If we do this we have to also read
- # the local /etc/{passwd,group} files to complete our information. If we aren't
- # using YP, we just read the local pasword and group files.
- #
- sub load_passwd_info {
- X local($use_getent, $file_name) = @_;
- X local(@pw_info);
- X
- X if ($passwd_loaded) {
- X return;
- X }
- X
- X $passwd_loaded = 1;
- X
- X if ($use_getent) {
- X #
- X # Use getpwent to get the info from the system, and add_pw_info to
- X # cache it.
- X #
- X while ((@pw_info = getpwent()) != 0) {
- X &add_pw_info(@pw_info);
- X }
- X
- X endpwent();
- X
- X return();
- X } elsif ($file_name eq "") {
- X chop($has_yp = `$DOMAINNAME`);
- X if ($has_yp) {
- X #
- X # If we have YP (NIS), then use ypcat to get the stuff from the
- X # map.
- X #
- X open(FILE, "$YPCAT passwd|") ||
- X die "can't 'ypcat passwd'";
- X while (<FILE>) {
- X chop;
- X &add_pw_info(split(/:/));
- X }
- X close(FILE);
- X }
- X
- X #
- X # We have to read /etc/passwd no matter what...
- X #
- X $file_name = "/etc/passwd";
- X }
- X
- X open(FILE, $file_name) ||
- X die "can't open $file_name";
- X
- X while (<FILE>) {
- X chop;
- X
- X if ($_ !~ /^\+/) {
- X &add_pw_info(split(/:/));
- X }
- X
- X # fixme: if the name matches +@name, then this is a wierd
- X # netgroup thing, and we aren't dealing with it right. might want
- X # to warn the poor user...suggest that he use the use_getent
- X # method instead.
- X }
- X
- X close(FILE);
- }
- X
- sub load_group_info {
- X local($use_getent, $file_name) = @_;
- X local(@gr_info);
- X
- X if ($group_loaded) {
- X return;
- X }
- X
- X $group_loaded = 1;
- X
- X if ($use_getent) {
- X #
- X # Use getgrent to get the info from the system, and add_gr_info to
- X # cache it.
- X #
- X while ((@gr_info = getgrent()) != 0) {
- X &add_gr_info(@gr_info);
- X }
- X
- X endgrent();
- X
- X return();
- X } elsif ($file_name eq "") {
- X chop($has_yp = `$DOMAINNAME`);
- X if ($has_yp) {
- X #
- X # If we have YP (NIS), then use ypcat to get the stuff from the
- X # map.
- X #
- X open(FILE, "$YPCAT group|") ||
- X die "can't 'ypcat group'";
- X while (<FILE>) {
- X chop;
- X &add_gr_info(split(/:/));
- X }
- X close(FILE);
- X }
- X
- X #
- X # We have to read /etc/group no matter what...
- X #
- X $file_name = "/etc/group";
- X }
- X
- X open(FILE, $file_name) ||
- X die "can't open $file_name";
- X
- X while (<FILE>) {
- X chop;
- X if ($_ !~ /^\+/) {
- X &add_gr_info(split(/:/));
- X }
- X
- X # fixme: if the name matches +@name, then this is a wierd
- X # netgroup thing, and we aren't dealing with it right. might want
- X # to warn the poor user...suggest that he use the use_getent
- X # method instead.
- X }
- X
- X close(FILE);
- }
- X
- 1;
- X
- SHAR_EOF
- chmod 0600 p-cops.alpha/pwgrid.pl ||
- echo 'restore of p-cops.alpha/pwgrid.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/pwgrid.pl'`"
- test 9915 -eq "$Wc_c" ||
- echo 'p-cops.alpha/pwgrid.pl: original size 9915, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/root.chk.new ==============
- if test -f 'p-cops.alpha/root.chk.new' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/root.chk.new (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/root.chk.new (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk.new' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: root.chk
- #
- # This script checks pathnames inside root's startup files for
- # writability, improper umask settings (world writable), non-root
- # entries in /.rhosts, writable binaries in root's path,
- # and to ensure that root is in /etc/ftpuser.
- #
- # Also check for a single "+" in /etc/hosts.equiv (world is trusted),
- # and that /bin, /etc and certain key files are root owned, so that you
- # can't, say, rcp from a host.equived machine and blow over the password
- # file... this may or may not be bad, decide for yourself.
- # Startup files are /.login /.cshrc /.profile
- #
- # Mechanism: These files contain paths and filenames that are stripped
- # out using "grep". These strings are then processed by the "is_able"
- # program to see if they are world writable. Strings of the form:
- #
- # path=(/bin /usr/bin .)
- # and
- # PATH=/bin:/usr/bin:.:
- #
- # are checked to ensure that "." is not in the path. All
- # results are echoed to standard output. In addition, some effort was
- # put into parsing out paths with multiple lines; e.g. ending in "\",
- # and continuing on the next line. Also, all executable files and
- # directories in there are checked for writability as well.
- #
- # For umask stuff, simply grep for umask in startup files, and check
- # umask value. For /etc/ftpuser, simple grep to check if root is in
- # the file. For /etc/hosts.equiv, just check to see if "+" is alone
- # on a line by awking it.
- #
- # NOTE:
- # if you know where perl is and your system groks #!, put its
- # pathname at the top to make this a tad faster.
- #
- # the following magic is from the perl man page
- # and should work to get us to run with perl
- # even if invoked as an sh or csh or foosh script.
- # notice we don't use full path cause we don't
- # know where the user has perl on their system.
- #
- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
- & eval 'exec perl -S $0 $argv:q'
- X if $running_under_some_stupid_shell_instead_of_perl;
- X
- # rewritten in perl by tchrist@convex.com
- #
- X
- # root startup/important files
- X
- require 'file_owner.pl';
- require 'fgrep.pl';
- require 'suckline.pl';
- require 'is_able.pl';
- require 'chk_strings.pl';
- require 'glob.pl';
- X
- package root_chk;
- X
- # use -a true if you care about non-executables
- # in root's path
- X
- $ARGV[0] eq '-a' && ($all_files++, shift);
- X
- die "usage: root.chk [-a]\n" if @ARGV;
- X
- $W = 'Warning! ';
- X
- $cshrc = '/.cshrc';
- $profile= '/.profile';
- $rhosts = '/.rhosts';
- X
- $| = 1;
- X
- @big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
- X
- # root should own *at least* these, + $big_files; you can check for all files
- # in /bin & /etc, or just the directories (the default.)
- # root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
- @root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
- X
- # misc important stuff
- $ftp='/etc/ftpusers';
- $equiv='/etc/hosts.equiv';
- X
- # should't have anyone but root owning /bin or /etc files/directories
- # In case some of the critical files don't exist (/.rhost), toss away error
- # messages
- X
- if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
- X print "$W Root does not own the following file(s):\n";
- X print "\t@bad_files\n";
- }
- X
- local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
- X
- for $file (@big_files) {
- X open file || next;
- X
- X &'chk_strings($file);
- X
- X # check for group or other writable umask
- X while (<file>) {
- X next if /^\s*#/;
- X next unless /umask\s*(\d+)/;
- X next unless ~oct($1) & 022;
- X print "$W root's umask set to $1 in $file\n";
- X }
- }
- X
- print "$W $ftp exists and root is not in it\n"
- X if -e $ftp && !&'fgrep($ftp,'root');
- X
- print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
- X
- if (open rhosts) {
- X while (<rhosts>) {
- X next unless /\S+\s+(\S+)/ && $1 ne 'root';
- X print "$W Non-root entry in $rhosts! $1\n";
- X }
- }
- close(rhosts);
- X
- undef @rootpath;
- X
- # checking paths...
- #
- # Get the root paths from $csh.
- X
- print "FOO-4:\n";
- if (open(CSHRC, $cshrc)) {
- X $path = '';
- X while (<CSHRC>) {
- X print "FOO-3: $_\n";
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- X print "FOO-2: $_\n";
- X $_ = &'suckline($cshrc, $_);
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- X for (@tmppath) { $whence{$_} .= " " . $cshrc; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(CSHRC);
- }
- X
- print "FOO0: @rootpath\n";
- X
- if (open login) {
- X $path = '';
- X while (<cshrc>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/set\s+path\s*=/) {
- X $_ = &'suckline('login', $_);
- X s/.*set\s+path\s*=\s*//;
- X s/\((.*)\)/$1/;
- X s/#.*/./;
- X @tmppath = grep($_ ne '', split(' '));
- X for (@tmppath) { $whence{$_} .= " " . $login; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(login);
- }
- X
- print "FOO1: @rootpath\n";
- X
- if (open profile) {
- X $path = '';
- X while (<profile>) {
- X next if /^\s*#/;
- X chop unless /\\$/;
- X if (/PATH=/) {
- X $_ = &'suckline('profile', $_);
- X s/.*PATH=//;
- X s/#.*//;
- X @tmppath = split(/:/);
- X for (@tmppath) { $whence{$_} .= " " . $profile; }
- X push(@rootpath, @tmppath);
- X }
- X }
- X close(profile);
- }
- X
- print "FOO2: @rootpath\n";
- X
- for (keys %whence) {
- X $whence{$_} =~ s/^ //;
- X $whence{$_} =~ s/ / and /g;
- }
- X
- undef %seen;
- grep($seen{$_}++, @rootpath);
- X
- $is_able'silent = 1;
- for (keys %seen) {
- X print "WAK: $_\n";
- X if (!-e && $_ ne ".") {
- X print "$W path component $_ in $whence{$_} doesn't exist!\n";
- X next;
- X }
- X
- X if (/^\.?$/) { # null -> dot
- X print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
- X } elsif (&'is_writable($_)) {
- X print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
- X next;
- X }
- X
- X foreach $file (&'glob("$_/*")) {
- X print "BAR: $_\n";
- X # can't just check -x here, as that depends on current user
- X $is_executable = -f $file && (&'Mode($file) & 0111);
- X if (($all_files || $is_executable) &&
- X ($how = &'is_writable($file, 'w', 'w'))) {
- X print "$W _World_ $how ",
- X $is_executable ? 'executable' : 'file',
- X " $file in root path component $_ from $whence{$_}!\n";
- X }
- X }
- }
- X
- $is_able'silent = 0;
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/root.chk.new ||
- echo 'restore of p-cops.alpha/root.chk.new failed'
- Wc_c="`wc -c < 'p-cops.alpha/root.chk.new'`"
- test 6147 -eq "$Wc_c" ||
- echo 'p-cops.alpha/root.chk.new: original size 6147, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/rules.pl ==============
- if test -f 'p-cops.alpha/rules.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/rules.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/rules.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/rules.pl' &&
- sub apply_rules {
- X local($op, $value, @plan) = @_;
- X
- X printf("eval($op $value): %s\n", &ascii_plan(@plan)) if $opt_d;
- X
- X #
- X # apply UID attack rules...
- X #
- X if ($op eq "u") {
- X #
- X # If we can replace /etc/passwd or /usr/lib/aliases, we can grant
- X # any uid.
- X #
- X &addto("r", "/etc/passwd", @plan);
- X &addto("r", "/usr/lib/aliases", @plan);
- X &addto("r", "/etc/aliases", @plan);
- X
- X #
- X # Check CF's for all usernames with this uid.
- X #
- uname_loop:
- X foreach $uname (split(/ /, $uid2names{$value})) {
- X $home = $uname2dir{$uname};
- X
- X next uname_loop unless $home;
- X
- X if ($home eq "/") {
- X $home = "";
- X }
- X &addto("r", "$home/.rhosts", @plan);
- X &addto("r", "$home/.login", @plan);
- X &addto("r", "$home/.logout", @plan);
- X &addto("r", "$home/.cshrc", @plan);
- X &addto("r", "$home/.profile", @plan);
- X }
- X
- X #
- X # Controlling files for root...
- X #
- X @rootlist = (
- X "/etc/rc", "/etc/rc.boot", "/etc/rc.single",
- X "/etc/rc.config", "/etc/rc.local", "/usr/lib/crontab",
- X "/usr/spool/cron/crontabs",
- X );
- X
- X if ($value eq "0") {
- X foreach $file (@rootlist) {
- X &addto("r", $file, @plan);
- X }
- X # Experimental!
- X # you can remove this if desired - tjt
- X #do "rc.prog";
- X }
- X
- X #
- X # Other CFs for non-root folks...
- X #
- X if ($value ne "0") {
- X &addto("r", "/etc/hosts.equiv", @plan);
- X if (-s "/etc/hosts.equiv") {
- X &addto("r", "/etc/hosts", @plan);
- X }
- X }
- X
- X #
- X # Plans for attacking GIDs...
- X #
- X } elsif ($op eq "g") { # apply gid attack rules
- X
- X #
- X # If we can replace /etc/group we can become any group
- X #
- X &addto("r", "/etc/group", @plan);
- X
- X #
- X # If we can grant any member of a group we can grant that group
- X #
- member_loop:
- X foreach $uname (split(/ /, $gid2members{$value})) {
- X if (! defined($uname2uid{$uname})) {
- X printf(stderr "group '%s' member '%s' doesn't exist.\n",
- X $value,
- X $uname);
- X next member_loop;
- X }
- X
- X &addto("u", $uname2uid{$uname}, @plan);
- X }
- X
- X #
- X # Plans for attacking files...
- X #
- X
- X } elsif ($op eq "r" || $op eq "w") {
- X
- X ($owner, $group, $other) = &filewriters($value);
- X
- X &addto("u", $owner, @plan) if ($owner ne "");
- X &addto("g", $group, @plan) if ($group ne "");
- X &addto("u", "-1", @plan) if ($other);
- X
- X #
- X # If the goal is to replace the file, check the parent directory...
- X #
- X if ($op eq "r") {
- X $parent = $value;
- X $parent =~ s#/[^/]*$##; # strip last / and remaining stuff
- X
- X if ($parent eq "") {
- X $parent = "/";
- X }
- X
- X if ($parent ne $value) {
- X &addto("r", $parent, @plan);
- X }
- X }
- X
- X } else { # wow, bad $type of object!
- X printf(stderr "kuang: bad op in apply_rules!\n");
- X printf(stderr "op '%s' value '%s' plan '%s'\n",
- X $op,
- X $value,
- X &ascii_plan(@plan));
- X exit(1);
- X }
- }
- X
- 1;
- X
- SHAR_EOF
- chmod 0600 p-cops.alpha/rules.pl ||
- echo 'restore of p-cops.alpha/rules.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/rules.pl'`"
- test 2768 -eq "$Wc_c" ||
- echo 'p-cops.alpha/rules.pl: original size 2768, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- rm -f _shar_seq_.tmp
- echo You have unpacked the last part
- exit 0
-