df@sei.cmu.edu (Dan Farmer) (05/17/91)
Submitted-by: df@death.cert.sei.cmu.edu Archive-name: alpha p-cops/part01 #!/bin/sh # This is alpha p-cops, a shell archive (produced by shar 3.49) # To extract the files from this archive, save it to a file, remove # everything above the "!/bin/sh" line above, and type "sh file_name". # # made 05/17/1991 04:38 UTC by df@death.cert.sei.cmu.edu # Source directory /usr/users/df/COPS/perl # # existing files will NOT be overwritten unless -c is specified # # This is part 1 of a multipart archive # do not concatenate these parts, unpack them in order with /bin/sh # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 7064 -rwx------ p-cops.alpha/ftp.chk # 1776 -rwx------ p-cops.alpha/get-cf # 1292 -rwx------ p-cops.alpha/chk_strings # 3559 -rwx------ p-cops.alpha/chk_strings.pl # 2129 -rwx------ p-cops.alpha/cops.cf # 2399 -rwx------ p-cops.alpha/cron.chk # 6390 -rwx------ p-cops.alpha/cops # 463 -rwx------ p-cops.alpha/fgrep.pl # 413 -rwx------ p-cops.alpha/file_mode.pl # 398 -rwx------ p-cops.alpha/file_owner.pl # 2739 -rwx------ p-cops.alpha/dev.chk # 902 -rwx------ p-cops.alpha/getopts.pl # 2960 -rwx------ p-cops.alpha/glob.pl # 5060 -rwx------ p-cops.alpha/group.chk # 444 -rwx------ p-cops.alpha/hostname.pl # 1591 -rwx------ p-cops.alpha/is_able.chk # 1603 -rwx------ p-cops.alpha/is_able.lst # 2835 -rwx------ p-cops.alpha/is_able.pl # 14688 -rwx------ p-cops.alpha/kuang # 4413 -rwx------ p-cops.alpha/misc.chk # 6730 -rw------- p-cops.alpha/kuang.1 # 10420 -rwx------ p-cops.alpha/pass.cache.pl # 6684 -rwx------ p-cops.alpha/pass.chk # 5989 -rwx------ p-cops.alpha/passwd.chk # 677 -rwx------ p-cops.alpha/pathconf.pl # 644 -rwx------ p-cops.alpha/pathconf.sh # 1014 -rwx------ p-cops.alpha/rc.chk # 3296 -rwx------ p-cops.alpha/reconfig.pl # 2048 -rwx------ p-cops.alpha/user.chk # 653 -rwx------ p-cops.alpha/stat.pl # 229 -rwx------ p-cops.alpha/suckline.pl # 4477 -rwx------ p-cops.alpha/suid.chk # 5959 -rwx------ p-cops.alpha/root.chk # 4848 -rwx------ p-cops.alpha/README.perl # 6206 -rwx------ p-cops.alpha/root.chk.old # 9306 -rw------- p-cops.alpha/README.kuang # 0 -rwx------ p-cops.alpha/suid.stop # 9915 -rw------- p-cops.alpha/pwgrid.pl # 6147 -rwx------ p-cops.alpha/root.chk.new # 2768 -rw------- p-cops.alpha/rules.pl # if test -r _shar_seq_.tmp; then echo 'Must unpack archives in sequence!' echo Please unpack part `cat _shar_seq_.tmp` next exit 1 fi # ============= p-cops.alpha/ftp.chk ============== if test ! -d 'p-cops.alpha'; then echo 'x - creating directory p-cops.alpha' mkdir 'p-cops.alpha' fi if test -f 'p-cops.alpha/ftp.chk' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/ftp.chk (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/ftp.chk (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/ftp.chk' && #!/bin/sh # need to mention perl here to avoid recursion # # Usage: ftp.chk # # This shell script checks to see if you've set up (mainly anonymous) # ftp correctly. There seems to be some different types of ftp's # around; for instance, some allow "chmod" -- and if the home dir is # owned by "ftp", you're toast. So I've tried to err on the side of # safety... # # See the man page for a more detailed description, here's what this # checks for: # # - User ftp exists in the password file. # - root (or all root equivalents) are in ftpusers file. # - Home directory for ftp should exist, and not be / # - The ~ftp/etc/{passwd|group} should not be the same as the real ones. # - Various critical files/directories should exist, and have correct # permissions and owners; variables "$primary" and "$secondary" can be set # to whomever you want owning the files: # # File/Dir Perms Owner Other # ========= ====== ====== ====== # ~ftp non-w.w. root # or # ~ftp 555 ftp if no chmod command exists # # All of these are ftp owned iff no chmod exists... # # ~ftp/bin non-w.w. root/ftp # ~ftp/bin/ls 111 root/ftp # ~ftp/etc non-w.w. root/ftp # ~ftp/etc/passwd non-w.w. root/ftp 0 size or nonexistant # ~ftp/etc/group non-w.w. root/ftp 0 size or nonexistant # ~ftp/pub non-w.w. root/ftp # ~ftp/incoming world-writable root/ftp This can be set to "pub" # ~ftp/.rhosts non-w.w. root 0 size, is optional # ~ftp/* non-w.w. other dirs/files in ~ftp # # # 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 'is_able.pl'; require 'file_mode.pl'; require 'glob.pl'; require 'fgrep.pl'; require 'pass.cache.pl'; require 'file_owner.pl'; require 'pathconf.pl'; X $CMP="/bin/cmp" unless defined $CMP; X package ftp; X # Primary and secondary owners of the ftp files/dirs; if you *don't* have # chmod, you can probably change the secondary owner to "ftp". If you have # chmod in your ftp, definitely have secondary to some other account (root # is fine for this.) local($primary)="root" unless defined $primary; local($secondary)="ftp" unless defined $secondary; X # some might have this as ftpd; is the account in /etc/passwd local($ftpuid)="ftp"; X # system files local($ftpusers)="/etc/ftpusers"; local($passwd)="/etc/passwd" unless defined $PASSWD; local($group)="/etc/group" unless defined $GROUP; X # ftp's home: $ftproot = &'uname2dir($ftpuid); $anonymous = $ftproot ne ''; X local($ftprhosts)="$ftproot/.rhosts"; local($ftpbin)="$ftproot/bin"; local($ftpls)="$ftpbin/ls"; local($ftpetc)="$ftproot/etc"; local($ftppasswd)="$ftpetc/passwd"; local($ftpgroup)="$ftpetc/group"; X local($W) = 'Warning! ' unless defined $W; X # the pub/incoming stuff; by default, pub is *not* world writable, incoming # is; if you want pub to be world writable, just change incoming to "pub" local($incoming)="pub"; X @crit_files=($ftpgroup, X $ftppasswd, X $ftpls); X if (-s $ftpusers) { X # check to see if root (or root equivalents) is in ftpusers file X @all_roots = split(" ", $'uid2names{0}); X if (@all_roots ne "") { X for $i (@all_roots) { X if (length($user2passwd{$i}) == 13 && ! &'fgrep($ftpusers, "^$i$")) { X print "Warning! $i should be in $ftpusers!\n"; X } X } X } } X # do the anonymous ftp checking stuff now? die unless $anonymous; X # if the user ftp doesn't exist, no-anon stuff.... # if $TEST -z $ftproot -a "$anonymous" = "yes" ; then X die "${W}Need user $ftpuid for anonymous ftp to work!\n" if ($ftpuid eq ""); X # if the user ftp doesn't exist, no-anon stuff.... if (! -d $ftproot || $ftproot eq "") { X die "${W}Home directory for ftp doesn\'t exist!\n"; } if ($ftproot eq "/") { X print "${W}$ftproot ftp\'s home directory should not be \"/\"!\n"; } X # want to check all the critical files and directories for correct # ownership. Some versions of ftp don't need much of anything... no # etc directory or password/group files. # others need etc directory & password/group files. Experiment. # push(@crit_files, $ftpbin, $ftpetc); for $i (@crit_files) { X $owner = &'Owner($i); X X if ($owner eq 'BOGUS') { X print "${W}Critical anon-ftp file $i is missing!\n"; X next; X } X X $owner = $'uid2names{$owner}; X X if ($owner ne $primary && $owner ne $secondary) { X print "${W}$i should be owned by $primary or $secondary, not $owner!\n"; X } } X # Don't want the passwd and group files to be the real ones! if (&'Owner($ftppasswd) ne 'BOGUS' && X $passwd ne $ftppasswd && X system "$CMP -s $passwd $ftppasswd") { X print "${W}$ftppasswd and $passwd are the same!\n"; } X if (&'Owner($ftpgroup) ne 'BOGUS' && X $group ne $ftpgroup && X system "$CMP -s $passwd $ftpgroup") { X print "${W}$ftpgroup and $group are the same!\n"; } X # ftproot is special; if owned by root; should be !world writable; # if owned by ftp, should be mode 555 X if (&'Owner($ftproot) ne BOGUS) { X $owner = $'uid2names{&'Owner($ftproot)}; X $perms=&'Mode($ftproot); X if ($owner ne $primary && $owner ne $secondary) { X print "${W}$ftproot should be owned by $primary or $secondary, not $owner!\n"; X } X X # ftp-root should not be world-writable: X &'is_able($ftproot, "w", "w"); X X # if ftp owns root-dir, then mode should be 555: X if ($owner eq $ftpuid && $perms ne 00555) { X print "${W}$ftproot should be mode 555!\n"; X } } X # # check the .rhosts file: if (-f $ftprhosts) { X if (-s $ftprhosts) { X print "${W}$ftprhosts should be be empty!\n"; X } X $owner=$'uid2names{&'Owner($ftprhosts)}; X if ($owner ne $primary && $owner ne $secondary) { X print "${W}$ftprhosts should be owned by $primary or $secondary!\n"; X } } X # finally, some permissions of miscellaneous files: if (($perms=&'Mode($ftpls)) & 0666) { X printf "${W}Incorrect permissions (%04o) on $ftpls!\n", $perms; } X if (($perms=&'Mode($ftppasswd)) & 0333) { X printf "${W}Incorrect permissions (%04o) on $ftppasswd!\n", $perms; } X X if (($perms=&'Mode($ftpgroup)) & 0333) { X printf "${W}Incorrect permissions (%04o) on $ftpgroup!\n", $perms; } X # Finally, the ~ftp/{pub|incoming|whatever} stuff: opendir(FTPDIR, $ftproot) || die "can't opendir $ftproot: $!"; X @all_dirs=grep(-d, readdir(FTPDIR)); X local($is_able'silent) = 1; for $i (@all_dirs) { X if ($i ne $incoming && &'is_able($ftproot . "/$i", "w", "w")) { X print "${W}Anon-ftp directory $i is World Writable!\n"; X } } X 1; # end of script SHAR_EOF chmod 0700 p-cops.alpha/ftp.chk || echo 'restore of p-cops.alpha/ftp.chk failed' Wc_c="`wc -c < 'p-cops.alpha/ftp.chk'`" test 7064 -eq "$Wc_c" || echo 'p-cops.alpha/ftp.chk: original size 7064, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/get-cf ============== if test -f 'p-cops.alpha/get-cf' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/get-cf (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/get-cf (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/get-cf' && #! /usr/local/bin/perl X @dot_files = ( X ".login", ".logout", ".cshrc", # csh, cshe or tcsh X ".profile", # ksh, sh X ".env", # ksh X ".alias", ".aliases", # common for all shells X "user.ps", ".user.ps", "tools.ps", ".tools.ps", X "startup.ps", ".startup.ps", # NeWS X ".mgrc", # MGR X ".X11init", ".awmrc", ".twmrc", ".xinitrc", # X11 X ".emacs" # emacs ); X %seen = {}; X open(HOST, "/bin/hostname |") || die "can't get the hostname"; chop($hostname=<HOST>); close(HOST); X user_loop: X for (($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent(); X $name ne ""; X ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent()) { X X # X # If the user has a home directory on this server, get the info X # about the directory, his CF's and so on. X # X if ($dir =~ m,^/n/$hostname/,) { X if (! -d $dir) { X printf(stderr "home directory '%s' for user '%s' doesn't exist.\n", X $dir, X $name); X next user_loop; X } X X ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, X $atime,$mtime,$ctime,$blksize,$blocks) X = stat(_); X $mode = $mode & 07777; X X &spit_it_out("d", $uid, $gid, $mode, $dir); X X foreach $file (@dot_files) { X $path = "$dir/$file"; X X if (-f $path) { X ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, X $atime,$mtime,$ctime,$blksize,$blocks) X = stat(_); X $mode = $mode & 07777; X X &spit_it_out("f", $uid, $gid, $mode, $dir); X } X } X } X } X X X X sub spit_it_out { X local($type, $uid, $gid, $mode, $name) = @_; X X if (defined($seen{$name})) { X return; X } X X printf("%s %d %d 0%o %s\n", $type, $uid, $gid, $mode, $name); X $seen{$name} = 1; } X SHAR_EOF chmod 0700 p-cops.alpha/get-cf || echo 'restore of p-cops.alpha/get-cf failed' Wc_c="`wc -c < 'p-cops.alpha/get-cf'`" test 1776 -eq "$Wc_c" || echo 'p-cops.alpha/get-cf: original size 1776, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/chk_strings ============== if test -f 'p-cops.alpha/chk_strings' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/chk_strings (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/chk_strings (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/chk_strings' && #!/bin/sh # need to mention perl here to avoid recursion # # Usage: chk_strings filename # # This will check pathnames inside executable files for writability, # The big string "@ignores" is a list of files that are ignored by # this; you can set it to whatever you want -- default is: # '^/tmp/?' and '^/(var|usr)/tmp/?' # # No program root EVER runs should be show up here. # # 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 package main; X $| = 1; X require 'getopts.pl'; require 'chk_strings.pl'; X die "Usage: $0 [-fr] file ...\n" unless &Getopts('rd') && @ARGV; X package chk_strings; X $debug = $'opt_d; $recurse = $'opt_r; @ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' ) X unless defined @ignores; X #%paths = (); # faster than local X for (@'ARGV) { X (warn("$0: $_: $!\n"), next) unless -e; X &'chk_strings($_); } SHAR_EOF chmod 0700 p-cops.alpha/chk_strings || echo 'restore of p-cops.alpha/chk_strings failed' Wc_c="`wc -c < 'p-cops.alpha/chk_strings'`" test 1292 -eq "$Wc_c" || echo 'p-cops.alpha/chk_strings: original size 1292, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/chk_strings.pl ============== if test -f 'p-cops.alpha/chk_strings.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/chk_strings.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/chk_strings.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/chk_strings.pl' && # # This is a big one. Support routines to check for strings # that look like pathnames and make sure they're not writable. # Will recurse if $recurse is set. the shell version can't do # this (yet). call &ignore with list of regexps that you don't # care about. (or set @ignores) # # originally by Tom Christiansen <tchrist@convex.com> # since hacked on by parties various and sundry. X require 'is_able.pl'; require 'file_mode.pl'; require 'pathconf.pl'; X package chk_strings; X X $'STRINGS = $'STRINGS || '/usr/ucb/strings'; X for ( '/dev/null', '/dev/tty' ) { X $seen{$_}++; } X sub main'chk_strings { X local($ARGV) = @_; X local($_); X local($word); X local(*STRINGS); # XXX: might run out of fd's on deep recursion! -tchrist X local(%paths, $text); X local($STRINGS) = "$'STRINGS $ARGV |"; X X &ignore(@ignores) if defined @ignores && !$already_ignored; X X $STRINGS="< $ARGV", $text=1 if -T $ARGV; X print "Opening via: $STRINGS\n" if $debug; X X open (STRINGS, $STRINGS); X while (<STRINGS>) { X next unless m#/#; # was m#/.*/#; #--------------------------------------------------------------------------- # Comments and modifications by Martin Foord (maf%dbsm.oz.au@munnari.oz.au). X #s/#.*$// if $text; # strip out comments if -T file X # Comments start in the shell at the beginning of a word or at the X # beggining of a line X if ($text) { X s/\s+#.*$//; X s/^#.*$//; X } X X # Get rid of semicolons, they can hang around on filenames ... X s/;//g; #--------------------------------------------------------------------------- X X s/"([^"]*)"/ $1 /g; X s/'([^']*)'/ $1 /g; X # See my comments below on how to deal with this stuff ... (line 64). X #s/`([^`]*)`/ $1 /g; X X X s!([<>])\s+/!$1/!g; # "> /foo" goes to ">foo"; X X s/=/ /g; # warning -- mangled files with = in them X for $word (split) { X if ($word =~ m#:/#) { X @paths{split(/:/, $word)} = (); X } elsif ($word =~ m#^[<>]?/#) { X print "push $word\n" if $debug; X $paths{$word}++; X } X } X } X close (STRINGS); X push(@files, $ARGV); X X for (keys %paths) { X s/\)$//; X s/^\(//; X s#^/+#/#; X s#^(/.*)/$#$1#; # get rid of trailing slash X #--------------------------------------------------------------------------- # Comments and modifications by Martin Foord (maf%dbsm.oz.au@munnari.oz.au). X # It's best to evaluate what's in backquotes rather than remove them X # as in the substitution above, due to files which X # look like this /var/yp/`domainname` (eg in my /etc/rc.local). X s`\`(.+)\``$1`; # eval what's in backquotes. X chop if /\n$/; # fang off \n if there ... #--------------------------------------------------------------------------- X next if &ignored($_); X s/^[<>]//; X next if $_ eq ''; X next unless !$seen{$_}++ && -e && !-S _; X print "checking $_\n" if $debug; X if ($how = &'is_writable($_)) { X print "Warning! File $_ (inside ", X join(' inside ', reverse @files), ") is _World_ $how!\n"; X } elsif ($recurse && (&'Mode($_) & 0111) && -f _) { X print "recursing $_\n" if $debug; X &'chk_strings($_); X } X } X pop(@files); } X sub ignore { X local($_); X local($prog); X X $already_ignored = 1; X X $prog = <<'EOCODE'; X sub ignored { X local($return) = 1; X local($prog); X local($_) = @_; X { EOCODE X for (@_) { X $prog .= "\tlast if m\201${_}\201;\n"; X } X $prog .= <<'EOCODE'; X $return = 0; X } X print "$_ IGNORED\n" if $debug && $return; X $return; } EOCODE X X print $prog if $debug; X eval $prog; X die $@ if $@; } X sub ignored {}; # in case they never ignore anything X 1; SHAR_EOF chmod 0700 p-cops.alpha/chk_strings.pl || echo 'restore of p-cops.alpha/chk_strings.pl failed' Wc_c="`wc -c < 'p-cops.alpha/chk_strings.pl'`" test 3559 -eq "$Wc_c" || echo 'p-cops.alpha/chk_strings.pl: original size 3559, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/cops.cf ============== if test -f 'p-cops.alpha/cops.cf' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/cops.cf (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/cops.cf (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cops.cf' && # # COPS CONFIGURATION FILE # # put user variables here: anything beginning with /^\s*[$@%&]/ will be eval'd # 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. X # COPS main variables follow... $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 $C2 = 0; # Sun c2 security package X # these don't really work for pass.cache yet $IGNORE_YP = 0; $PASSWD = '/etc/passwd'; $GROUP = '/etc/group'; X ############################################################### # many things call &chk_strings (including {cron,misc,rc,root}.chk) # the following two variable settings affects its behaviour... # this one says to ignore warnings about paths matching these regexps X @chk_strings'ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' ); X # this will take a bit longer, but can find dangerous things much better # the shell cops can't do this.... sigh X $chk_strings'recurse = 0; X # if we want stat failure warnings from is_able... X $is_able'noisy = 0; X ############################################################### # finally the checks to execute. these can also all be run # stand-alone from the shell. X # first test the security of the root account root.chk 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 # exaustive tests for things that shouldn't be readable/writable etc is_able.chk X # check for insecuities in /etc/rc* rc.chk X # and in cron cron.chk X # some consistency and idiocy (null or trivial passwds) in /etc/passwd passwd.chk pass.chk X # consistency checks in /etc/group group.chk X # make sure user accounts don't have trojanable dot files user.chk X # check ftp security $ftp'primary = "root"; $ftp'secondary = "ftp"; ftp.chk X # and anything else we forgot X # Sys V types use /etc/servers here: $misc'inetd = "/etc/inetd"; misc.chk X # Kuang! kuang X # SUID checking # suid.chk SHAR_EOF chmod 0700 p-cops.alpha/cops.cf || echo 'restore of p-cops.alpha/cops.cf failed' Wc_c="`wc -c < 'p-cops.alpha/cops.cf'`" test 2129 -eq "$Wc_c" || echo 'p-cops.alpha/cops.cf: original size 2129, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/cron.chk ============== if test -f 'p-cops.alpha/cron.chk' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/cron.chk (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/cron.chk (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cron.chk' && #!/bin/sh # need to mention perl here to avoid recursion # # Usage: cron.chk.pl [-rd] # # This checks pathnames and files inside the cron files /usr/lib/crontab # for writability. # # Mechanism: The commands inside the file /usr/lib/crontab are executed # by root. This perl script uses chk_strings.pl for chking for writable # files/dirs. # # cron.chk.pl will try to find a file in /usr/lib/crontab first (bsd), # and then if it isn't there, it will look in the any alternate # possible locations next -- right now, /usr/spool/cron/crontab -- to # see if a directory exists, and, if it does, it checks all the cron # files in turn. # # WARNING! # # Spurious messages can occur; a more stringent method (if perhaps less # careful of a check) would be to test just the 6th field, instead of # all the fields after the fifth. Also throwing away /tmp, etc. could # be a mistake. # # 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 package main; X require 'getopts.pl'; require 'glob.pl'; require 'chk_strings.pl'; require 'pathconf.pl'; X # should also add args to override default crontab locations die "Usage: $0 [-rd]\n" unless &Getopts('rd') && !@ARGV; X $chk_strings'debug = $opt_d; $chk_strings'recurse = $opt_r; X package cron_chk; X # Possible location of crontab file: $cron = "/usr/lib/crontab"; # alternate reality locations of crontab file: @alt_cron = ("/usr/spool/cron/crontabs"); X if ( ! -s $cron) { X for (@alt_cron) { X # are there ever multiple crontab directories? X (@crons = &'glob("$_/*")), last if -d; X } X die "No crontabs?\n" if ! @crons; } @crons = ($cron) unless @crons; X # ignore /tmp /dev/null and tty stuff # &'chk_strings ignores all of above # STILL NEED to ignore stuff after `>' # when we add @ignore stuff to &'chk_strings X # finally, do the checking -- maybe for one, maybe for lots of cron-ites: for (@crons) { X if (! -e) { X warn "$0: $_: $!\n"; X next; X } X &'chk_strings($_); } X 1; SHAR_EOF chmod 0700 p-cops.alpha/cron.chk || echo 'restore of p-cops.alpha/cron.chk failed' Wc_c="`wc -c < 'p-cops.alpha/cron.chk'`" test 2399 -eq "$Wc_c" || echo 'p-cops.alpha/cron.chk: original size 2399, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/cops ============== if test -f 'p-cops.alpha/cops' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/cops (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/cops (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cops' && #!/bin/sh # need to mention perl here to avoid recursion # # Usage: cops [-v] [-c config file] [-s secure_dir] [architecture] # # This will change into the $SECURE/architecture directory, suck lots # of info and configuration stuff out of "cops.cf", and runs all of the # security programs in that file. If any of the programs find any # security problems, it either sends mail to everyone in the $SECURE_USERS # list (see "cops.cf"), or saves the results in a file # $SECURE/architecture/hostname. It then destroys all temporary files, # and exits the program. Programs that are run (besides this one): # # root.chk dev.chk group.chk # rc.chk passwd.chk is_able.chk # pass.chk user.chk cron.chk # misc.chk ftp.chk # # This -v (verbose) flag prints out the name each program to # the results file as it is executed. The -s and -c flags allow you # to specify the $SECURE directory and $CONFIG file, respectively. # # 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 ###################################### # perl COPS main driver. # tchrist@convex.com ###################################### X # security sanity settings # $ENV{'IFS'} = '' if $ENV{'IFS'}; $ENV{'PATH'} = '/bin:/usr/bin:/usr/ucb'; $| = 1; umask 077; X # # Getopts stuff $usage = "Usage: $0 [-v] [-c config_file] [-s secure_dir] architecture\n"; require 'getopts.pl'; # Process the command args; Either specify verbose or an alternate config file: die $usage unless &Getopts('vc:s:'); X if (defined($opt_v)) { $verbose = $opt_v;} else { $verbose = 0; } X if (defined($opt_s)) { $SECURE = $LIBCOPS = $opt_s; } else { $SECURE = $LIBCOPS = '.'; } X if (defined($opt_c)) { $CONFIG = $opt_c; } else {$CONFIG = "$SECURE/cops.cf"; } X if (@ARGV > 1) { X die $usage; } elsif (@ARGV == 1) { X $SECURE = shift; X die "Architecture directory $SECURE does not exist\n" unless -d $SECURE; X chdir($SECURE) || die "can't cd to $SECURE: $!"; X exec './cops'; } X # internal cops stuff needed require "$LIBCOPS/pathconf.pl"; require "$LIBCOPS/is_able.pl"; X chmod 0700, $SECURE; chdir ($SECURE) || die "Error -- Security directory $SECURE doesn't exist"; X # Read stuff to do from the config file die "$0: Can't trust $CONFIG to reconfig!" if &'is_writable($CONFIG); open CONFIG || die "can't open $CONFIG: $!"; X &argh unless -s $CONFIG; X &init_result; X while (<CONFIG>) { X next if /^\s*#/; X next if /^\s*$/; X X if (/^\s*[\$&\@\%]/) { # reset a config variable X s/#.*//; X eval; X warn "Bad config variable at line $. of $CONFIG:\n\t$_\t$@\n" if $@; X next; X } X X # must be a program to run X chop; X s/#.*//; X s/;$//; X @ARGV=split; X $program = shift; X if ($verbose) { print "\n******* $program *******\n\n"; } X &flush; X &run("$LIBCOPS/$program"); X &flush; X } X &save_result; X &argh unless $ran_something; X Xexit 0; X ###################################################################### sub run { X local($module) = @_; X local($status); X local($0) = $module; # so it shows up in ps X local($!); X X X $ran_something++; X X open(STDERR, $COPS_ERRORS ? ">&STDOUT" : ">/dev/null"); X X unless ($status = do $module) { X if ($@) { X warn "cops: unexpected exit from $module:\n\t-> $@\n"; X } elsif ($! != 0) { X warn "cops: couldn't run $module: $!\n"; X } else { X warn "cops: $module returned $status\n"; X } X } X X # hack for kuang, who doesn't write to STDOUT (yet!) X $SUCCESS = "$SECURE/Success"; X if ($module =~ /^kuang/ && -e $SUCCESS) { X if (open SUCCESS) { X print while <SUCCESS>; # STDOUT is $REPORT X close SUCCESS; X unlink $SUCCESS; X } else { X warn "can't open $SUCCESS: $!"; X } X } } ###################################################################### sub init_result { X $REPORT = "$SECURE/result.$$"; # global! X open (REPORT, ">$REPORT") || die "can't create $REPORT: $!"; X X # assume dups work X open (STDOUT, ">&REPORT"); X open (SAVERR, ">&STDERR"); X open (STDERR, ">&STDOUT"); X X ($sec, $min, $hour, $mday, $mon, $year, X $wday, $yday, $isdst) = localtime(time); X X $name = sprintf("%s_%s_%s", $year + 1900, X (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$mon], X $mday); X X $host = ( -x '/bin/hostname' && `/bin/hostname` ) X || ( -x '/bin/uname' && `/bin/uname -n` ) X || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`) X || 'Amnesiac!'; X X chop $host; X $host =~ s/\..*//; X X print "ATTENTION:\nSecurity report for ", `date`; X print "\nfrom host $host, $name\n\n"; X $report = $name; X X &flush; } ###################################################################### sub save_result { X open(STDERR, ">&SAVERR"); X X close REPORT || die "can't close $REPORT: $!"; X X $dir = "$SECURE/$host"; X $report = $dir . "/" . $report; X X mkdir($dir,0700) unless -d $dir; X X if ($NMAIL) { X system "$MAIL $SECURE_USERS < $REPORT" X unless $ONLY_DIFF && !&different($dir, $REPORT); X } else { # rename ($REPORT, $dir . "/" . $name) || # die "can't put $REPORT into $dir/$name: $!"; X rename ($REPORT, $report) || X die "can't put $REPORT into $report: $!"; X } X unlink $REPORT; } X ###################################################################### sub different { X local($dir, $FILE1) = @_; X local($FILE2, $f1, $f2, $_); X X open (LS, "$LS -t $dir |"); X chop($FILE2 = <LS>); X close(LS); # snuff it out X X X return 1 if !$FILE2 || -s $FILE1 != -s $FILE2; X X open FILE1 || die "can't open $FILE1: $!"; X open FILE2 || die "can't open $FILE2: $!"; X X for (1..5) { X $_ = <FILE1>; X $_ = <FILE2>; X } X X while ( ($f1 = <FILE1>), ($f2 = <FILE2>) ) { X last if $f1 ne $f2; X } X X close FILE1; X close FILE2; X X defined($f1) || defined($f2); } X ###################################################################### sub flush { X local($old) = $|; X $| = 1; X print ''; X $| = $old; } X sub argh { X die "Argh -- Can't find anything in $CONFIG\n"; } SHAR_EOF chmod 0700 p-cops.alpha/cops || echo 'restore of p-cops.alpha/cops failed' Wc_c="`wc -c < 'p-cops.alpha/cops'`" test 6390 -eq "$Wc_c" || echo 'p-cops.alpha/cops: original size 6390, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/fgrep.pl ============== if test -f 'p-cops.alpha/fgrep.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/fgrep.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/fgrep.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/fgrep.pl' && # # Just a quick perl fgrep... # package fgrep; X sub main'fgrep { X local($file, @exprs) = @_; X local(@list); X X if (open file) { X $code = "while (<file>) {\n\tchop;\n"; X for (@exprs) { X $code .= "\tpush(\@list, \$_), next if m\201${_}\201;\n"; X } X $code .= "}\n"; X warn "fgrep code is $code" if $debug; X eval $code; X warn "fgrep @exprs $file: $@\n" if $@; X } elsif ($debug) { X warn "main'fgrep: can't open $file: $!\n"; X } X X @list; } X 1; SHAR_EOF chmod 0700 p-cops.alpha/fgrep.pl || echo 'restore of p-cops.alpha/fgrep.pl failed' Wc_c="`wc -c < 'p-cops.alpha/fgrep.pl'`" test 463 -eq "$Wc_c" || echo 'p-cops.alpha/fgrep.pl: original size 463, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/file_mode.pl ============== if test -f 'p-cops.alpha/file_mode.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/file_mode.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/file_mode.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/file_mode.pl' && # # This retrieves a possibly cached mode on file. # If it returns "BOGUS", it means that the stat failed. # # tchrist@convx.com X package main; require 'stat.pl'; X package file_mode; X sub main'Mode { X local($file) = @_; X X if (!defined $modes{$file}) { X if (&'Stat($file)) { X $modes{$file} = $'st_mode; X } else { X $modes{$file} = 'BOGUS'; X } X } X $modes{$file}; } SHAR_EOF chmod 0700 p-cops.alpha/file_mode.pl || echo 'restore of p-cops.alpha/file_mode.pl failed' Wc_c="`wc -c < 'p-cops.alpha/file_mode.pl'`" test 413 -eq "$Wc_c" || echo 'p-cops.alpha/file_mode.pl: original size 413, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/file_owner.pl ============== if test -f 'p-cops.alpha/file_owner.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/file_owner.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/file_owner.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/file_owner.pl' && # # This retrieves possibly cached owner of a file. # If it returns "BOGUS", it means that the stat failed. X package main; require 'stat.pl'; X package file_owner; X sub main'Owner { X local($file) = @_; X X if (!defined $owners{$file}) { X if (&'Stat($file)) { X $owners{$file} = $'st_uid; X } else { X $owners{$file} = 'BOGUS'; X } X } X $owners{$file}; } SHAR_EOF chmod 0700 p-cops.alpha/file_owner.pl || echo 'restore of p-cops.alpha/file_owner.pl failed' Wc_c="`wc -c < 'p-cops.alpha/file_owner.pl'`" test 398 -eq "$Wc_c" || echo 'p-cops.alpha/file_owner.pl: original size 398, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/dev.chk ============== if test -f 'p-cops.alpha/dev.chk' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/dev.chk (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/dev.chk (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/dev.chk' && #!/bin/sh # need to mention perl here to avoid recursion # # dev.chk [-g] # # This shell script checks the permissions of all devs listed in the # file /etc/fstab (the "mount" command would be a preferable way of # getting the file system name, but the syntax of the output is variable # from machine to machine), and flags them if they are readable by using # the "is_readable" command. It also checks for unrestricted NFS # mountings. By default, dev_check will flag devs only if world readable # or writable. The -g option tells it to print out devs that are also # group readable/writable. # As an aside, the fact that NFS mounted dirs are world readable isn't # a big deal, but they shouldn't be world writable. So do two checks here, # instead of one. # # Two types of /etc/fstab formats I've seen so far: # # "old" -- # spec:file:type:freq:passno:name:options # NFS are indicated by an "@" # # "new" -- # fsname dir type opts freq passno # NFS are indicated by an ":" # # 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 # # dev.chk [-g] # # tchrist@convx.com # X require 'is_able.pl'; X $MTAB = '/etc/fstab' unless defined $MTAB; $EXPORTS = '/etc/exports' unless defined $EXPORTS; $TAB_STYLE = 'new' unless defined $TAB_STYLE; # or 'old' X &usage if @ARGV > 1; X sub usage { die "Usage: $0 [-g]\n"; } X if (@ARGV == 1) { X if ($ARGV[0] eq '-g') { X $group++; X } else { X &usage; X } } X X open MTAB || die "can't open $MTAB: $!"; X while (<MTAB>) { X next if /^#/; X chop; X if ($TAB_STYLE eq 'new') { X ($dev, $fs) = split; X next unless $fs; X if ($dev =~ /:/) { X push(@nfs_devs, $fs); X } else { X push(@local_devs, $dev); X } X } else { X ($dev, $fs) = split(/:/); X next unless $fs; X if ($dev =~ /@/) { X push(@nfs_devs, $fs); X } else { X push(@local_devs, $dev); X } X } X } X if (open EXPORTS) { X while (<EXPORTS>) { X next if /^\s*#/; X next if /\S\s+\S/; X chop; X printf "Warning! NFS file system $_ exported with no restrictions.\n"; X } } X # WARNING: we may hang if server down.... # for (@nfs_devs, @local_devs) { X &is_able($_, 'w', 'w'); X next unless $group; X &is_able($_, 'g', 'w'); } X for (@local_devs) { X &is_able($_, 'w', 'r'); X next unless $group; X &is_able($_, 'g', 'r'); } X 1; SHAR_EOF chmod 0700 p-cops.alpha/dev.chk || echo 'restore of p-cops.alpha/dev.chk failed' Wc_c="`wc -c < 'p-cops.alpha/dev.chk'`" test 2739 -eq "$Wc_c" || echo 'p-cops.alpha/dev.chk: original size 2739, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/getopts.pl ============== if test -f 'p-cops.alpha/getopts.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/getopts.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/getopts.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/getopts.pl' && ;# getopts.pl - a better getopt.pl X ;# Usage: ;# do Getopts('a:bc'); # -a takes arg. -b & -c not. Sets opt_* as a ;# # side effect. X sub Getopts { X local($argumentative) = @_; X local(@args,$_,$first,$rest,$errs); X local($[) = 0; X X @args = split( / */, $argumentative ); X while(($_ = $ARGV[0]) =~ /^-(.)(.*)/) { X ($first,$rest) = ($1,$2); X $pos = index($argumentative,$first); X if($pos >= $[) { X if($args[$pos+1] eq ':') { X shift(@ARGV); X if($rest eq '') { X $rest = shift(@ARGV); X } X eval "\$opt_$first = \$rest;"; X } X else { X eval "\$opt_$first = 1"; X if($rest eq '') { X shift(@ARGV); X } X else { X $ARGV[0] = "-$rest"; X } X } X } X else { X print STDERR "Unknown option: $first\n"; X ++$errs; X if($rest ne '') { X $ARGV[0] = "-$rest"; X } X else { X shift(@ARGV); X } X } X } X $errs == 0; } X 1; SHAR_EOF chmod 0700 p-cops.alpha/getopts.pl || echo 'restore of p-cops.alpha/getopts.pl failed' Wc_c="`wc -c < 'p-cops.alpha/getopts.pl'`" test 902 -eq "$Wc_c" || echo 'p-cops.alpha/getopts.pl: original size 902, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/glob.pl ============== if test -f 'p-cops.alpha/glob.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/glob.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/glob.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/glob.pl' && # # This does shell or perl globbing without resorting # to the shell -- we were having problems with the shell blowing # up with extra long pathnames and lots of file names. set $glob'debug # for trace information. # # tom christiansen <tchrist@convex.com> X package glob; X sub main'glob { X local($expr) = @_; X local(@files); X X $? = 0; X open(SAVERR, ">&STDERR"); close(STDERR); # suppress args too long X @files = <${expr}>; X if ($?) { X print SAVERR "shell glob blew up on $expr\n" if $debug; X @files = &SHglob($expr); X } X open (STDERR, ">&SAVERR"); X # if (@files == 1 && $files[0] eq $expr) { @files = ''; } # sh foo X @files; } X sub main'SHglob { X local($expr) = @_; X local(@retlist) = (); X local($dir); X X printf "SHglob: globbing $expr\n" if $debug; X X $expr =~ s/([.{+\\])/\\$1/g; X $expr =~ s/\*/.*/g; X $expr =~ s/\?/./g; X X for $dir (split(' ',$expr)) { X push(@retlist, &main'REglob($dir)); X } X X return sort @retlist; } X sub main'REglob { X local($path) = @_; X local($_); X local(@retlist) = (); X local($root,$expr,$pos); X local($relative) = 0; X local(@dirs); X local($user); X X $haveglobbed = 0; X X @dirs = split(/\/+/, $path); X X if ($dirs[$[] =~ m!~(.*)!) { X $dirs[$[] = &homedir($1); X return @retlist unless $dirs[$[]; X } elsif ($dirs[$[] eq '') { X $dirs[$[] = '/' unless $dirs[$[] =~ m!^\.{1,2}$!; X } else { X unshift(@dirs, '.'); X $relative = 1; X } X X printf "REglob: globbing %s\n", join('/',@dirs) if $debug; X X @retlist = &expand(@dirs); X X for (@retlist) { X if ($relative) { X s!^\./!!o; X } X s!/{2,}!/!g; X } X X return sort @retlist; } X sub expand { X local($dir, $thisdir, @rest) = @_; X local($nextdir); X local($_); X local(@retlist) = (); X local(*DIR); X X unless ($haveglobbed || $thisdir =~ /([^\\]?)[?.*{[+\\]/ && $1 ne '\\') { X @retlist = ($thisdir); X } else { X unless (opendir(DIR,$dir)) { X warn "glob: can't opendir $dir: $!\n" if $debug; X } else { X @retlist = grep(/^$thisdir$/,readdir(DIR)); X @retlist = grep(!/^\./, @retlist) unless $thisdir =~ /^\\\./; X $haveglobbed++; X } X closedir DIR; X } X X for (@retlist) { X $_ = $dir . '/' . $_; X } X X if ($nextdir = shift @rest) { X local(@newlist) = (); X for (@retlist) { X push(@newlist,&expand($_,$nextdir,@rest)); X } X @retlist = @newlist; X } X X return @retlist; } X sub homedir { X local($user) = @_; X local(@pwent); X # global %homedir X X if (!$user) { X return $ENV{'HOME'} if $ENV{'HOME'}; X ($user = $ENV{'USER'}) || X ($user = getlogin) || X (($user) = getpwnam($>)); X warn "glob'homedir: who are you, user #$>?" unless $user; X return '/'; X } X unless (defined $homedir{$user}) { X if (@pwent = getpwnam($user)) { X $homedir{$user} = $pwent[$#pwent - 1]; X } else { X warn "glob'homedir: who are you, user #$>?" unless $user; X $homedir{$user} = '/'; X } X } X return $homedir{$user}; } X X 1; SHAR_EOF chmod 0700 p-cops.alpha/glob.pl || echo 'restore of p-cops.alpha/glob.pl failed' Wc_c="`wc -c < 'p-cops.alpha/glob.pl'`" test 2960 -eq "$Wc_c" || echo 'p-cops.alpha/glob.pl: original size 2960, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/group.chk ============== if test -f 'p-cops.alpha/group.chk' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/group.chk (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/group.chk (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/group.chk' && #!/bin/sh # need to mention perl here to avoid recursion # # group.chk.pl # # Check group file -- /etc/group -- for incorrect number of fields, # duplicate groups, non-alphanumeric group names, and non-numeric group # id's. # # Mechanism: Group.check uses awk to ensure that each line of the group # has 4 fields, as well as examining each line for any duplicate groups or # any duplicate user id's in a given group by using "sort -u" to ferret # out any duplications. It also checks to make sure that the password # field (the second one) is a "*", meaning the group has no password (a # group password is usually not necessary because each member listed on # the line has all the privilages that the group has.) All results are # echoed to standard output. Finally it ensures that the group names # are alphanumeric, that the group id's are numeric, and that there are # no blank lines. For yellow pages groups, it does the same checking, # but in order to get a listing of all members of the groups, it does a # "ypcat group". # # The /etc/group file has a very specific format, making the task # fairly simple. Normally it has lines with 4 fields, each field # separated by a colon (:). The first field is the group name, the second # field is the encrypted password (an asterix (*) means the group has no # password, otherwise the first two characters are the salt), the third # field is the group id number, and the fourth field is a list of user # ids in the group. If a line begins with a plus sign (+), it is a yellow # pages entry. See group(5) for more information. # # 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 # should get below config stuff from cops.cf file package main; X die "Usage: $0\n" if @ARGV; X require 'pathconf.pl'; X # Used for Sun C2 security group file. FALSE (default) will flag # valid C2 group syntax as an error, TRUE attempts to validate it. # Thanks to Pete Troxell for pointing this out. # # moved to cops.cf X $etc_group=$GROUP || '/etc/group'; X package group_chk; X $yptmp = "./yptmp.$$"; X # Testing $etc_group for potential problems.... open (Group, "< $'etc_group") || warn "$0: Can't open $'etc_group: $!\n"; &chk_group_file_format('Group'); close Group; X # Testing ypcat group for potential problems $yp=0; if (-s $'YPCAT && -x _) { X system("$'YPCAT group >$yptmp 2>/dev/null"); X if (-s $yptmp) { X open(YGroup, "$'YPCAT group |") || die "$0: Can't popen $'YPCAT: $!\n"; X &chk_group_file_format('YGroup'); X close(YGroup); X $yp=1; X } } X # usage: &chk_group_file_format('Filehandle-name'); # skips over lines that begin with "+:" # It really should check for correct yellow pages syntax.... # # this routine checks lines read from a filehandle for potential format # problems .. should be matching group(5) # # checks for duplicate users in a group as it reads the lines instead # of after (as the original shell script does) X sub chk_group_file_format { X local($file) = @_; X local($W) = "Warning! $file file,"; X X while (<$file>) { X next if /^\+:/; X print "$W line $., is blank\n" if /^\s*$/; X split(/:/); X $groups{$_[0]}++; # keep track of dups X print "$W line $., does not have 4 fields: $_" if (@_ != 4); X print "$W line $., nonalphanumeric group name: $_" X if $_[0] !~ /^[A-Za-z0-9-]+$/; X if ($_[1] && $_[1] ne '*') { X if ( ! $C2 || $yp ) { X print "$W line $., group has password: $_" X if length($_[1]) == 13; X } else { X print "$W line $., group has invalid field for C2:\n$_" X if $_[1] ne "#\$$_[0]"; X } X } X print "$W line $., nonnumeric group id: $_" if $_[2] !~ /^\d+$/; X X # look for duplicate users in a group X # kinda ugly, but it works .. and I have too much other work right X # now to clean it up. maybe later.. ;-) X chop($_[3]); # down here, 'cos split gets rid of final null fields X @users = sort split(/\s*,\s*/, $_[3]); X # %users = # of times user is in group, $dup_user = duplicate found X undef %users; $dup_user=0; X grep(!$users{$_}++, @users); X for (keys %users) { X (print "Warning! Group $_[0] has duplicate user(s):\n"), X $dup_user=1 if !$dup_user && $users{$_} > 1; X print "$_ " if $users{$_} > 1; X } X print "\n" if $dup_user; X X } X # find duplicate group names X # not the best way, but it works.. X # boy, this is ugly too .. but, not as bad as above.. :) X $dup_warned = 0; X for (sort keys %groups) { X (print "Warning! Duplicate Group(s) found in $file:\n"), $dup_warned++ X if !$dup_warned && $groups{$_} > 1; X print "$_ " if $groups{$_} > 1; X } X print "\n" if $dup_warned; } X unlink $yptmp; X 1; # end SHAR_EOF chmod 0700 p-cops.alpha/group.chk || echo 'restore of p-cops.alpha/group.chk failed' Wc_c="`wc -c < 'p-cops.alpha/group.chk'`" test 5060 -eq "$Wc_c" || echo 'p-cops.alpha/group.chk: original size 5060, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/hostname.pl ============== if test -f 'p-cops.alpha/hostname.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/hostname.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/hostname.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/hostname.pl' && # # file: hostname.pl # usage: $hostname = &'hostname; # # purpose: get hostname -- try method until we get an answer # or return "Amnesiac!" # X package hostname; X sub main'hostname { X if (!defined $hostname) { X $hostname = ( -x '/bin/hostname' && `/bin/hostname` ) X || ( -x '/bin/uname' && `/bin/uname -n` ) X || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`) X || 'Amnesiac!'; X chop $hostname; X } X $hostname; } X 1; SHAR_EOF chmod 0700 p-cops.alpha/hostname.pl || echo 'restore of p-cops.alpha/hostname.pl failed' Wc_c="`wc -c < 'p-cops.alpha/hostname.pl'`" test 444 -eq "$Wc_c" || echo 'p-cops.alpha/hostname.pl: original size 444, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/is_able.chk ============== if test -f 'p-cops.alpha/is_able.chk' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/is_able.chk (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/is_able.chk (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.chk' && #!/bin/sh # need to mention perl here to avoid recursion # # is_able.chk # # This shell script checks the permissions of all files and directories # listed in the configuration file "is_able.lst", and prints warning messages # according to the status of files. You can specify world or group readability # or writeability. See the config file for the format of the configuration # file. # # Mechanism: This shell script parses each line from the configure file # and uses the "is_able.pl" program to check if any of # the directories in question are writable by world/group. # # 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; require 'is_able.pl'; require 'file_mode.pl'; require 'glob.pl'; X if ($ARGV[0] eq '-d') { X shift; X $debug = $glob'debug = 1; # maybe should turn off glob'debug afterwards } X unshift (@ARGV, "is_able.lst" ) unless @ARGV; X while (<>) { X next if /^\s*#/; X split; X next unless @_ == 3; X ($file, $x, $y) = @_; X @files = $file =~ /[\[?*]/ ? &'glob($file) : ($file); X for $file (@files) { X print STDERR "is_able $file $x $y\n" if $debug; X &'is_able($file, $x, $y); X } } X 1; SHAR_EOF chmod 0700 p-cops.alpha/is_able.chk || echo 'restore of p-cops.alpha/is_able.chk failed' Wc_c="`wc -c < 'p-cops.alpha/is_able.chk'`" test 1591 -eq "$Wc_c" || echo 'p-cops.alpha/is_able.chk: original size 1591, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/is_able.lst ============== if test -f 'p-cops.alpha/is_able.lst' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/is_able.lst (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/is_able.lst (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.lst' && # This lists any/all sensitive files the administration wants to ensure # non-read/writability of. Comments are lines starting with a "#". # # USE FULL PATHNAMES! # # Lines are of the format: # # /path/to/{dir|file} World/Group Read/Write/Both # # as above {w|g} {r|w|b} # / w w /etc w w /usr w w /bin w w /dev w w /usr/bin w w /usr/etc w w /usr/adm w w /usr/lib w w /usr/spool w w /usr/spool/mail w w /usr/spool/news w w /usr/spool/uucp w w /usr/spool/at w w /usr/local w w /usr/local/bin w w /usr/local/lib w w /usr/users w w /Mail w w X # some Un*x's put shadowpass stuff here: /etc/security w r X # /.login /.profile /.cshrc /.rhosts /.* w w X # I think everything in /etc should be !world-writable, as a rule; but # if you're selecting individual files, do at *least* these: # /etc/passwd /etc/group /etc/inittab /etc/rc /etc/rc.local /etc/rc.boot # /etc/hosts.equiv /etc/profile /etc/syslog.conf /etc/export /etc/utmp # /etc/wtmp /etc/* w w X /bin/* w w /usr/bin/* w w /usr/etc/* w w /usr/adm/* w w /usr/lib/* w w /usr/local/lib/* w w /usr/local/bin/* w w /usr/etc/yp* w w /usr/etc/yp/* w w X # individual files: /usr/lib/crontab w b /usr/lib/aliases w w /usr/lib/sendmail w w /usr/spool/uucp/L.sys w b X # NEVER want these readable! /dev/kmem w b /dev/mem w b X # Optional List of assorted files that shouldn't be # write/readable (mix 'n match; add to the list as desired): /usr/adm/sulog w r /.netrc w b # HP-UX and others: /etc/btmp w b /etc/securetty w b # Sun-fun /dev/drum w b /dev/nit w b SHAR_EOF chmod 0700 p-cops.alpha/is_able.lst || echo 'restore of p-cops.alpha/is_able.lst failed' Wc_c="`wc -c < 'p-cops.alpha/is_able.lst'`" test 1603 -eq "$Wc_c" || echo 'p-cops.alpha/is_able.lst: original size 1603, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/is_able.pl ============== if test -f 'p-cops.alpha/is_able.pl' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/is_able.pl (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/is_able.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.pl' && # # (This takes the place of the C program is_able.c, BTW.) # # is_able filename {w|g|s|S} {r|w|B|b|s} # (world/group/SUID/SGID read/write/{read&write}/{suid&write}/s[ug]id) # # The second arg of {r|w} determines whether a file is (group or world # depending on the first arg of {w|g}) writable/readable, or if it is # SUID/SGID (first arg, either s or S, respectively), and prints out a # short message to that effect. # # So: # is_able w w # checks if world writable # is_able g r # checks if group readable # is_able s s # checks if SUID # is_able S b # checks if world writable and SGID X package main; require 'file_mode.pl'; X package is_able; X # package statics # %wg = ( X 'w', 00006, X 'g', 00060, X 's', 04000, X 'S', 02000, X ); X %rwb= ( X 'r', 00044, X 'w', 00022, X 'B', 00066, X 'b', 04022, X 's', 06000, X ); X $silent = 0; # for suppressing diagnostic messages X X sub main'is_able { X local($file, $wg, $rwb) = @_; X X local ( X $mode, # file mode X $piece, # 1 directory component X @pieces, # all the pieces X @dirs, # all the directories X $p, # punctuation; (*) mean writable X # due to writable parent X $retval, # true if vulnerable X $[ # paranoia X ); X X &usage, return undef if @_ != 3 || $file eq ''; X X &usage, return undef unless defined $wg{$wg} && defined $rwb{$rwb}; X X if (&'Mode($file) eq 'BOGUS' && $noisy) { X warn "is_able: can't stat $file: $!\n"; X return undef; X } X X $retval = 0; X X if ($rwb{$rwb} & $rwb{'w'}) { X @pieces = split(m#/#, $file); X for ($i = 1; $i <= $#pieces; $i++) { X push(@dirs, join('/', @pieces[0..$i])); X } X } else { X @dirs = ( $file ); X } X X for $piece ( reverse @dirs ) { X X next unless $mode = &'Mode($piece); X next if $mode eq 'BOGUS'; X X next unless $mode &= 07777 & $wg{$wg} & $rwb{$rwb}; X X $retval = 1; X X $p = $piece eq $file ? '!' : '! (*)'; X X $parent_is_writable = $p eq '! (*)'; # for later X X next if $silent; # for &is_writable X X print "Warning! $file is group readable$p\n" if $mode & 00040; X print "Warning! $file is _World_ readable$p\n" if $mode & 00004; X print "Warning! $file is group writable$p\n" if $mode & 00020; X print "Warning! $file is _World_ writable$p\n" if $mode & 00002; X print "Warning! $file is SUID!\n" if $mode & 04000; X print "Warning! $file is SGID!\n" if $mode & 02000; X X last if $piece ne $file; # only complain on first writable parent X } X $retval; } X sub main'is_writable { X local($silent) = 1; X &'is_able($_[0], 'w', 'w') X ? $parent_is_writable X ? "writable (*)" X : "writable" X : 0; } X sub main'is_readable { X local($silent) = 1; X &'is_able($_[0], 'w', 'r'); } X sub usage { X warn <<EOF; Usage: is_able file {w|g|S|s} {r|w|B|b|s} X (not: is_able @_) EOF } X 1; SHAR_EOF chmod 0700 p-cops.alpha/is_able.pl || echo 'restore of p-cops.alpha/is_able.pl failed' Wc_c="`wc -c < 'p-cops.alpha/is_able.pl'`" test 2835 -eq "$Wc_c" || echo 'p-cops.alpha/is_able.pl: original size 2835, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= p-cops.alpha/kuang ============== if test -f 'p-cops.alpha/kuang' -a X"$1" != X"-c"; then echo 'x - skipping p-cops.alpha/kuang (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting p-cops.alpha/kuang (Text)' sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/kuang' && #! /usr/local/bin/perl X # # kuang - rule based analysis of Unix security # # Perl version by Steve Romig of the CIS department, The Ohio State # University, October 1990. # # Based on the shell script version by Dan Farmer from his COPS # package, which in turn is based on a shell version by Robert # Baldwin. # #----------------------------------------------------------------------------- # Players: # romig Steve Romig, romig@cis.ohio-state.edu # tjt Tim Tessin, tjt@cirrus.com # # History: # 4/25/91 tjt, romig Various fixes to filewriters (better messages about # permission problems) and don't update the DBM cache # with local file info. # 11/1/90 romig Major rewrite - generic lists, nuking get_entry # and put_entry, moved rules to separate file. # X # # Options # # -l list uid's that can access the given target, directly # or indirectly # -d debug # -v verbose # # -k file load the list of known CO's # -f file preload file information from the named file. # -p file preload passwd info from the named file. # -P preload passwd info from ypcat + /etc/passwd # -g group preload group info from the named file. # -G preload group info from ypcat + /etc/group # X $options = "ldvk:p:g:f:PG"; $usage = "usage: kuang [-l] [-d] [-v] [-k known] [-f file] [-P] [-G] [-p passwd] [-g group] [u.username|g.groupname]\n"; X $add_files_to_cache = 1; # Whether to update the %files cache X # with local file info or not. X # # Terminology: # # An "op" is an operation, such as uid, gid, write, or replace. # 'uid' means to gain access to some uid, 'gid' means to gain access # to some gid. 'write' and 'replace' refer to files - replace means # that we can delete a file and replace it with a new one somehow # (for example, if we could write the directory it is in). # # An object is a uid, gid or pathname. # # A Controlling Operation (CO) is a (operation, object) pair # represented as "op object": "uid 216" (become uid 216) or "replace # /.rhosts" (replace file /.rhosts). These are represented # internally as "c value", where "c" is a character representing an SHAR_EOF true || echo 'restore of p-cops.alpha/kuang failed' fi echo 'End of alpha p-cops part 1' echo 'File p-cops.alpha/kuang is continued in part 2' echo 2 > _shar_seq_.tmp exit 0