home *** CD-ROM | disk | FTP | other *** search
- From: df@sei.cmu.edu (Dan Farmer)
- Newsgroups: alt.sources
- Subject: alpha perl-cops, 2 of 3
- Message-ID: <25551@as0c.sei.cmu.edu>
- Date: 17 May 91 04:46:54 GMT
-
- Submitted-by: df@death.cert.sei.cmu.edu
- Archive-name: alpha p-cops/part02
-
- #!/bin/sh
- # this is apcops.02 (part 2 of alpha p-cops)
- # do not concatenate these parts, unpack them in order with /bin/sh
- # file p-cops.alpha/kuang continued
- #
- if test ! -r _shar_seq_.tmp; then
- echo 'Please unpack part 1 first!'
- exit 1
- fi
- (read Scheck
- if test "$Scheck" != 2; 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/kuang'
- else
- echo 'x - continuing file p-cops.alpha/kuang'
- sed 's/^X//' << 'SHAR_EOF' >> 'p-cops.alpha/kuang' &&
- # operation (u for uid, g for gid, r for replace, w for write) and
- # value is a uid, gid or pathname.
- #
- # A plan is a chain of CO's that are connected to each other. If
- # /.login were writeable by uid 216, we might have a plan such as:
- #
- # uid 216 => write /.login => uid 0
- #
- # which means (in English) "if we can become uid 216, then write
- # /.login which gives you access to uid 0 (when root next logs in)."
- # Plans are represented in several ways: as arrays:
- #
- # ("u 0", "w /.login", "u 216")
- #
- # Note that the order is reversed. As a string:
- #
- # "u 0\034w /.login\034u 216"
- #
- # The target is the object that we are trying to gain (a uid, gid or
- # file, typically u.root or some other UID).
- #
- # Data Structures
- #
- # %known An assocc array, indexed by CO. This lists
- # the COs that we already have access to. If we
- # find a plan that leads from a CO in the known
- # list to the target, we've succeeded in
- # finding a major security flaw.
- #
- # @new An array of plans that are to be evaluated in
- # the next cycle.
- #
- # @old An array of plans that we are currently
- # evaluating.
- #
- # %beendone An assoc array that lists the plans that have
- # already been tried. Used to prevent loops.
- #
- # @accessible An array of the uids that can reach the
- # target.
- #
- # %files An assoc array, indexed by file name, contains
- # cached file info. value is of form "uid gid
- # mode".
- #
- # From pwgrid:
- #
- # %uname2shell Assoc array, indexed by user name, values are
- # shells.
- #
- # %uname2dir Assoc array, indexed by user name, values are
- # home directories.
- #
- # %uname2uid Assoc array, indexed by name, values are uids.
- #
- # %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 (user names).
- #
- # %gname2gid Assoc array, indexed by group name, values are
- # matching gids.
- #
- # %gid2names Assoc array, indexed by gid, values are
- # matching group names.
- #
- X
- do 'yagrip.pl' ||
- X die "can't do yagrip.pl";
- X
- do 'pwgrid.pl' ||
- X die "can't do pwgrid.pl";
- X
- do 'rules.pl' ||
- X die "can't do rules.pl";
- X
- X
- #
- # Turns a string of the form "operation value" or "value" into
- # standard "CO" form ("operation value"). Converts user or group
- # names into corresponding uid and gid values.
- #
- # Returns nothing if it isn't parseable.
- #
- X
- sub canonicalize {
- X local($string) = @_;
- X local($op, $value);
- X
- X if ($string =~ /^([ugrw]) ([^ \t\n]+)$/) { # of form "op value"
- X $op = $1;
- X $value = $2;
- X } elsif ($string =~ /^[^ \t\n]+$/) { # of form "value"
- X $value = $string;
- X $op = "u";
- X } else {
- X return();
- X }
- X
- X if ($op eq "u" && $value =~ /^[^0-9]+$/) { # user name, not ID
- X if (defined($uname2uid{$value})) {
- X $value = $uname2uid{$value};
- X } else {
- X printf(stderr "There's no user named '%s'.\n", $value);
- X return();
- X }
- X } elsif ($op eq "g" && $value =~/^[^0-9]+$/) {
- X if (defined($gname2gid{$value})) {
- X $value = $gname2gid{$value};
- X } else {
- X printf(stderr "There's no group named '%s'.\n", $value);
- X return();
- X }
- X }
- X
- X return($op, $value);
- }
- X
- X
- #
- # Preload file information from a text file or DBM database.
- # If $opt_f.dir exists, then we just shadow %files from a DBM
- # database. Otherwise, open the file and read the entries into
- # %files.
- #
- # $add_files_to_cache is set to 0 if we get the info from
- # DBM since we wouldn't want to pollute update our DBM cache
- # with local file info which wouldn't apply to other hosts.
- #
- X
- sub preload_file_info {
- X local($count, $f_type, $f_uid, $f_gid, $f_mode, $f_name);
- X
- X if (defined($opt_d)) {
- X printf("loading file info...\n");
- X }
- X
- X if (-f "$opt_f.dir") {
- X $add_files_to_cache = 0;
- X
- X dbmopen(files, $opt_f, 0644) ||
- X die sprintf("can't open DBM file '%s'", $opt_f);
- X } else {
- X open(FILEDATA, $opt_f) ||
- X die sprintf("kuang: can't open '%s'", $opt_f);
- X
- X $count = 0;
- X while (<FILEDATA>) {
- X $count++;
- X
- X chop;
- X ($f_type, $f_uid, $f_gid, $f_mode, $f_name) = split;
- X
- X if ($count % 1000 == 0) {
- X printf("line $count, reading entry for $f_name\n");
- X }
- X $files{$f_name} = join(' ', $f_uid, $f_gid, $f_mode);
- X }
- X
- X close(FILEDATA);
- X }
- }
- X
- #
- # Preload the known information. Reads data from a file, 1 entry per line,
- # each entry is a CO that we "know" can be used.
- #
- X
- sub preload_known_info {
- X local($file_name) = @_;
- X local($op, $value, $co);
- X
- X open(FILE, $file_name) ||
- X die sprintf("kuang: can't open '%s'", $file_name);
- X
- X known_loop:
- X while (<FILE>) {
- X chop;
- X if ((($op, $value) = &canonicalize($_)) == 2) {
- X $co = sprintf("%s %s", $op, $value);
- X $known{$co} = 1;
- X } else {
- X printf(stderr "kuang: invalid entry in known list: line %d '%s'.\n",
- X $.,
- X $_);
- X }
- X }
- X
- X close(FILE);
- }
- X
- X
- #
- # Do various initialization type things.
- #
- X
- sub init_kuang {
- X local($which, $name, $uid, $gid);
- X local($op, $value, $co);
- X
- X #
- X # Deal with args...
- X #
- X
- X &getopt($options) ||
- X die $usage;
- X
- X if ($#ARGV == -1) {
- X push(@ARGV, "u root");
- X }
- X
- X #
- X # Preload anything...
- X #
- X if (defined($opt_f)) {
- X &preload_file_info();
- X }
- X
- X if (defined($opt_d)) {
- X printf("load passwd info...\n");
- X }
- X
- X if (defined($opt_p)) {
- X if (defined($opt_P)) {
- X printf(stderr "You can only specify one of -p or -P, not both.\n");
- X exit(1);
- X }
- X
- X &load_passwd_info(0, $opt_p);
- X } elsif (defined($opt_P)) {
- X &load_passwd_info(0);
- X } else {
- X &load_passwd_info(1);
- X }
- X
- X if (defined($opt_d)) {
- X printf("load group info...\n");
- X }
- X
- X if (defined($opt_g)) {
- X if (defined($opt_G)) {
- X printf(stderr "You can only specify one of -g or -G, not both.\n");
- X exit(1);
- X }
- X
- X &load_group_info(0, $opt_g);
- X } elsif (defined($opt_G)) {
- X &load_group_info(0);
- X } else {
- X &load_group_info(1);
- X }
- X
- X #
- X # Need some of the password and group stuff. Suck in passwd and
- X # group info, store by uid and gid in an associative array of strings
- X # which consist of fields corresponding to the passwd and group file
- X # entries (and what the heck, we'll use : as a delimiter also...:-)
- X #
- X $uname2shell{"OTHER"} = "";
- X $uname2dir{"OTHER"} = "";
- X $uname2uid{"OTHER"} = -1;
- X $uid2names{-1} = "OTHER";
- X
- X $known{"u -1"} = 1; # We can access uid OTHER
- X
- X if (defined($opt_k)) {
- X &preload_known_info($opt_k);
- X }
- X
- X #
- X # Create the target list from the remaining (non-option) args...
- X #
- X while ($#ARGV >= 0) {
- X $elt = pop(@ARGV);
- X if ((($op, $value) = &canonicalize($elt)) == 2) {
- X $co = sprintf("%s %s", $op, $value);
- X push(@targets, $co);
- X } else {
- X printf(stderr "target '%s' isn't of correct form\n", $elt);
- X }
- X }
- }
- X
- X
- #
- # Call this to set things up for a new target. Resets old, new, beendone
- # and accessible.
- #
- sub set_target {
- X local($target) = @_;
- X
- X @old = ();
- X @new = ();
- X %beendone = ();
- X @accessible = ();
- # fixme: reset known?
- X
- X if ($target =~ /^([ugrw]) ([^ \t]+)$/) {
- X &addto($1, $2);
- X return(0);
- X } else {
- X printf(stderr "kuang: bad target '%s'\n", $target);
- X return(1);
- X }
- }
- X
- #
- # Break a CO into an (operation, value) pair and return it. If it
- # isn't in "operation value" form, return ().
- #
- sub breakup {
- X local($co) = @_;
- X local($operation, $value);
- X
- X if ($co =~ /^([ugrw]) ([^ \t]+)$/) {
- X $operation = $1;
- X $value = $2;
- X } else {
- X printf(stderr "Yowza, breakup failed on '%s'\n",
- X $co);
- X exit(1);
- X }
- X
- X return($operation, $value);
- }
- X
- #
- # Get the writers of the named file - return as (UID, GID, OTHER)
- # triplet. Owner can always write, since he can chmod the file if he
- # wants.
- #
- # (fixme) are there any problems in this sort of builtin rule? should
- # we make this knowledge more explicit?
- #
- sub filewriters {
- X local($name) = @_;
- X local($tmp, $mode, $uid, $gid, $other);
- X
- X #
- X # Check the file cache - avoid disk lookups for performance and
- X # to avoid shadows...
- X #
- X if (defined($files{$name})) {
- X $cache_hit++;
- X
- X ($uid, $gid, $mode, $tmp) = split(/ /, $files{$name});
- X } else {
- X $cache_miss++;
- X
- X unless (-e $name) {
- X if ($add_files_to_cache) {
- X $files{$name} = "";
- X }
- X # ENOTDIR = 20
- X ($! == 20) && print "Warning: Illegal Path: '$name'\n";
- X # EACCES = 13
- X ($! == 13) && print "Warning: Permission Denied: '$name'\n";
- X # all values are returned "" here.
- X return;
- X }
- X
- X ($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
- X if ($add_files_to_cache) {
- X $files{$name} = join(' ', "$uid", "$gid", "$mode");
- X }
- X }
- X
- X if (($mode & 020) != 020) {
- X $gid = "";
- X }
- X
- X if (($mode & 02) == 02) {
- X $other = 1;
- X } else {
- X $other = 0;
- X }
- X
- X return($uid, $gid, $other);
- }
- X
- X
- sub ascii_plan {
- X local(@plan) = @_;
- X local($op, $value, $result);
- X
- X for ($i = $#plan; $i >= 0; $i--) {
- X ($op, $value) = &breakup($plan[$i]);
- X
- X case:
- X {
- X if ($op eq "g") {
- X $op = "grant gid";
- X last case;
- X }
- X
- X if ($op eq "u") {
- X $op = "grant uid";
- X last case;
- X }
- X
- X if ($op eq "r") {
- X $op = "replace";
- X last case;
- X }
- X
- X if ($op eq "w") {
- X $op = "write";
- X last case;
- X }
- X
- X printf(stderr "Bad op '%s' in plan '%s'\n",
- X $op,
- X join(';', @plan));
- X last case;
- X }
- X
- X $result .= "$op $value ";
- X }
- X
- X return($result);
- }
- X
- #
- # Add a plan to the list of plans to check out.
- #
- sub addto {
- X local($op, $value, @plan) = @_;
- X local($co);
- X
- X $co = sprintf("%s %s",
- X $op,
- X $value);
- X
- X #
- X # See if the op and value is "uid root" - if so, and if the @plan
- X # isn't empty, then don't bother checking - if the target isn't root,
- X # its silly to pursue plans that require becoming root since if we can
- X # become root, we can become anything. If the target is root, then
- X # this would be a loop anyway.
- X #
- X if ($op eq "u" && $value eq "0" && $#plan >= 0) {
- X if (defined($opt_d)) {
- X printf("addto: aborted root plan '%s'\n",
- X &ascii_plan(@plan, $co));
- X }
- X return;
- X }
- X
- X #
- X # See whether there's an entry for $co in the known list.
- X # If so - success, we've found a suitable breakin plan.
- X #
- X # Yes, we want to check to see whether the whole Controlling Operation
- X # is one that is known to us, rather than just the object. I
- X # might have a hole that allows me to "replace /bin/foo" which is
- X # somewhat different than "write /bin/foo"
- X #
- X if (! defined($opt_l) && defined($known{$co})) {
- X printf("Success! %s\n",
- X &ascii_plan(@plan, $co));
- X }
- X
- X #
- X # Check for loops -- if the new CO is part of the plan that we're
- X # adding it to, this is a loop.
- X #
- X foreach $entry (@plan) {
- X if ($entry eq $co) {
- X if (defined($opt_d)) {
- X printf("addto: aborted loop in plan '%s'\n",
- X &ascii_plan(@plan, $co));
- X }
- X return;
- X }
- X }
- X
- X #
- X # Add this CO to the plan array...
- X #
- X push(@plan, $co);
- X
- X #
- X # Make an ascii version of sorts...
- X #
- X $text_plan = join($;, @plan);
- X
- X #
- X # Check to see if the new plan has been done.
- X #
- X if (defined($beendone{$text_plan})) {
- X if (defined($opt_d)) {
- X printf("addto: plan's been done - '%s'\n",
- X &ascii_plan(@plan));
- X }
- X return;
- X }
- X
- X #
- X # If we made it this far, its a new plan and isn't a loop.
- X #
- X
- X #
- X # Add to the beendone list...
- X #
- X $beendone{$text_plan} = 1;
- X
- X #
- X # Add to new plan list...
- X #
- X push(@new, $text_plan);
- X
- X if (defined($opt_v)) {
- X printf("addto: %s\n",
- X &ascii_plan(@plan));
- X }
- X
- X #
- X # If this is a uid goal, then add the plan to the accessible list.
- X #
- X if ($op eq "u" && $value ne "0" && defined($opt_l)) {
- X push(@accessible, $value);
- X }
- }
- X
- #
- #----------------------------------------------------------------------
- #Main program follows...initialize and loop till we're done.
- #
- X
- &init_kuang();
- X
- target_loop:
- foreach $target (@targets) {
- X if (&set_target($target)) {
- X next target_loop;
- X }
- X
- X while ($#new >= 0) {
- X @old = @new;
- X @new = ();
- X
- X foreach $t_plan (@old) {
- X @plan = split(/\034/, $t_plan);
- X ($op, $value) = &breakup($plan[$#plan]);
- X
- X &apply_rules($op, $value, @plan);
- X }
- X }
- X
- X if (defined($opt_l)) {
- X foreach $elt (@accessible) {
- X printf("$elt\n");
- X }
- X }
- }
- X
- if (defined($opt_d)) {
- X printf("File info cache hit/access ratio: %g\n",
- X ($cache_hit + $cache_miss > 0)
- X ? $cache_hit / ($cache_hit + $cache_miss)
- X : 0.0);
- }
- X
- X
- SHAR_EOF
- echo 'File p-cops.alpha/kuang is complete' &&
- chmod 0700 p-cops.alpha/kuang ||
- echo 'restore of p-cops.alpha/kuang failed'
- Wc_c="`wc -c < 'p-cops.alpha/kuang'`"
- test 14688 -eq "$Wc_c" ||
- echo 'p-cops.alpha/kuang: original size 14688, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/misc.chk ==============
- if test -f 'p-cops.alpha/misc.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/misc.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/misc.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/misc.chk' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: misc.chk.pl [-d]
- #
- # This shell script checks a variety of miscellaneous potential
- # security problems that really don't belong anywhere else.
- #
- # Right now this looks for to see if tftp & rexd are enabled,
- # to check if the uudecode alias is in the mail alias file and
- # not commented out, and if uudecode can create a SUID file.
- #
- # Mechanism: tftp.chk will try to get /etc/motd from the localhost.
- # Not much too it; just connect and try to get it. For rexd, just
- # look in the /etc/inetd.conf file to see if it's enabled (e.g., not
- # commented out).
- #
- # Warning: it may take a minute or so to complete the test, since tftp
- # might take a while to get the test file, or it may take a while to time
- # out the connection (which is what usually happens if the test fails.)
- # 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;
- require 'chk_strings.pl';
- require 'fgrep.pl';
- X
- if ($ARGV[0] eq '-d') {
- X #$chk_strings'debug = 1; # verbose debugging
- X $misc_chk'debug = 1;
- X shift;
- }
- X
- die "Usage: $0 [-d]\n" if @ARGV > 0;
- X
- X
- $TFTP="/usr/ucb/tftp" unless defined $TFTP;
- $UUDECODE="/usr/bin/uudecode" unless defined $UUDECODE;
- X
- package misc_chk;
- X
- # look for uudecode alias in $aliases
- $aliases="/usr/lib/aliases" if -f "/usr/lib/aliases";
- $aliases = ( -f '/usr/lib/aliases' && '/usr/lib/aliases' )
- X || ( -f '/etc/aliases' && '/etc/aliases' )
- X || 'BOGUS';
- $uu="decode";
- X
- # look for rexd in $inetd; this file could be "/etc/servers", too!
- if (!defined($inetd)) {
- X $inetd = ( -f '/etc/inetd.conf' && '/etc/inetd.conf') ||
- X ( -f '/etc/servers' && '/etc/servers') ||
- X 'BOGUS';
- X }
- $rexd="rexecd";
- X
- # tmp and target file (for tftp test)
- $target="/etc/motd";
- $tmp="./tmp.$$";
- X
- # should probably generalize routine for chking for pats in file at some point
- X
- # Read from $inetd to see if daemons are running.
- # Comments are lines starting with a "#", so ignore.
- # Checking for rexd:
- #
- print "Checking for $rexd in $inetd\n" if $debug;
- if (@matches = grep(!/\s*#/, &'fgrep($inetd, $rexd))) {
- X print "Warning! $rexd is enabled in $inetd!\n";
- }
- X
- # Check to see if anything started inetd.conf is writable;
- print "Checking for writable dirs in $inetd\n" if $debug;
- &'chk_strings($inetd);
- X
- # Checking for uudecode alias:
- print "Checking for $uu alias in $aliases\n" if $debug;
- print "Warning! $uu is enabled in $aliases!\n"
- X if &'fgrep($aliases, "^\s*$uu:");
- X
- # uucode stuff -- thanks to pete shipley...
- print "Checking uudecode out\n" if $debug;
- if (-x $'UUDECODE) {
- X open(UU, "| $'UUDECODE");
- X print UU <<EOD_;
- begin 4755 ./foobar.$$
- X
- end
- EOD_
- X close(UU);
- }
- X
- &'is_able($'UUDECODE,'s','s'); # check if uudecode is SUID
- $is_able'silent = 1;
- print "Warning! $'UUDECODE creates setuid files!\n"
- X if &'is_able("./foobar.$$",'s','s');
- $is_able'silent = 0;
- unlink("./foobar.$$");
- X
- # The rest is all for tftp stuff:
- #
- # Get the local hostname...
- $hostname = ( -x '/bin/hostname' && `/bin/hostname` )
- X || ( -x '/bin/uname' && `/bin/uname -n` )
- X || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`)
- X || 'Amnesiac!';
- chop $hostname;
- X
- # Do the dirty work -- check tftp for the localhost, if it was found;
- # this might take a bit, since tftp might have to time out.
- X
- print "Checking out tftp on $hostname\n" if $debug;
- if (-x $'TFTP) {
- X open(SAVOUT, ">&STDOUT"); # suppress file not found
- X open(SAVERR, ">&STDERR"); # it's not as bad as it looks..
- X open(STDOUT, ">/dev/null") || die "Can't redirect stdout: $!\n";
- X open(STDERR, ">&STDOUT") || die "Can't dup stdout: $!\n";
- X close(STDOUT); close(STDERR);
- X open(TFTP, "| $'TFTP");
- print TFTP <<_XXX_;
- connect $hostname
- get $target $tmp
- quit
- _XXX_
- X close(TFTP);
- X open(STDERR, ">&SAVERR"); close(SAVERR);
- X open(STDOUT, ">&SAVOUT"); close(SAVOUT);
- } # > /dev/null 2> /dev/null
- X
- print "Warning! tftp is enabled on $hostname!\n" if -s $tmp;
- unlink $tmp;
- X
- # end of script
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/misc.chk ||
- echo 'restore of p-cops.alpha/misc.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/misc.chk'`"
- test 4413 -eq "$Wc_c" ||
- echo 'p-cops.alpha/misc.chk: original size 4413, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/kuang.1 ==============
- if test -f 'p-cops.alpha/kuang.1' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/kuang.1 (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/kuang.1 (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/kuang.1' &&
- .TH KUANG 1 "4 October 1990"
- .SH NAME
- kuang \- find security problems through rule based analysis
- .SH SYNOPSIS
- .B kuang
- .RB "[\|" \-v "\|]"
- .RB "[\|" \-d "\|]"
- .RB "[\|" \-l "\|]"
- .RB "[\|" \-f filedata "\|]"
- .RB "[\|" \-P "\|]"
- .RB "[\|" \-G "\|]"
- .RB "[\|" \-p passwd "\|]"
- .RB "[\|" \-g group "\|]"
- .RB "[\|"
- .IR u.username | g.groupname "\|]"
- .br
- .SH DESCRIPTION
- .LP
- .B kuang
- uses rule based analysis to examine the current security configuration
- of a site and determine whether certain security problems exist.
- X
- .B kuang
- contains embedded rules that describe the projection model and
- some of the attacker tricks used on Unix systems. It uses these rules
- to reason backward from a desired goal (such as "grant u.root"),
- generating potential "attack" plans from the rules and file system
- state and then evaluating them to see whether they are reachable
- according to the state recorded in the password and group files and in
- the ownership and modes of the file systems.
- X
- By default,
- .B kuang
- uses "grant u.root" as its initial goal. You can change that by
- specifying a username (u.username) or groupname (g.groupname) on the
- command line. Normally
- .B kuang
- determines a plan to be successful if it determines that anyone
- (u.other) can become the initial goal.
- X
- The
- .B \-v
- option causes
- .B kuang
- to print a message about every plan added to the evaluation list.
- This can help one to understand how
- .B kuang
- works. The
- .B \-d
- option causes
- .B kuang
- to print a message when it evaluates a plan to determine whether to
- retain it and add onto it or ignore it. Beware - these options will often
- produce lots of output.
- X
- Normally
- .B kuang
- only registers success when it finds that everyone on the system can
- become the target uid or gid. With the
- .B \-l
- option,
- .B kuang
- will list every uid that can access the goal. This provides a more
- complete picture of the state of security - you might deem it a
- problem if several users can become root, even if u.other cannot.
- X
- One might adopt the view that each uid should only be accessible by
- itself and root, and that each gid should be accessible only by the
- members of that group and root. One can then compare the expected
- access list for a given uid or gid against the
- .B kuang
- generated list to find security problems that
- .B kuang
- wouldn't ordinarily tell you about.
- X
- The goals that
- .B kuang
- use seem cryptic, but are really pretty straightforward. Each goal
- consists of a list of <action> <object> pairs. Typical actions are
- user, group, write and replace. Typical objects are user names,
- group names and file names. The goal
- "user root" (or u.root) means to have access to the root UID (0), or
- in other words, to be able to run any program using that uid.
- Similarly,
- "group staff" (or g.staff) means to have access to group staff.
- The long goal
- "user bill group graphics replace /n/shoe/0/fred replace
- /n/shoe/0/fred/.profile user fred group staff" means become
- user bill, get access to the graphics group, replace the file
- /n/shoe/0/fred, replace /n/shoe/0/fred/.profile, become fred,
- grant access to the staff group. The problem that allows this to
- happen is that the /n/shoe/0 directory is writeable by the graphics
- group, meaning that anyone in that group can replace the .profile file
- for the fred user and gain access to that account and the groups it
- belongs to when fred next logs in. Ooops.
- X
- To do a thorough job,
- .B kuang
- really needs to be able to access all of
- the controlling files of all users. In some environments, home
- directories are located in NFS mounted file systems where the client
- doesn't have root access.
- X
- The problem is that some home directories may be
- protected so that group foo can read/write them, but OTHER can't.
- .B kuang
- running as some user not in group foo won't be able to read or
- search the directory, creating a blind spot that may hide security
- problems (for example, if group foo can write that user's .login and
- gain access to some other important priv...) Running
- .B kuang
- as root
- won't help unless we are running on the server that exports that
- file system, since root==nobody through NFS here. Of course, then
- you'll find other blind spots on other servers, meaning that you'll
- never be able to see a complete picture of how things are from any
- spot on the net. Running
- .B kuang
- on every machine might not even
- help, since the blind spots might prevent them from seeing viable
- paths to Success on any of the machines. Sigh.
- X
- Soooo we've added a
- .B -f
- option that causes
- .B kuang
- to preload owner, group and mode information for a list of files.
- Each line of the file should be of the form "type uid gid mode name".
- .B type
- is ignored by
- .B kuang.
- .B uid
- and
- .B gid
- are the user and group ID numbers, in decimal.
- .B mode
- is the permissions for the file, in octal. And
- .B name
- is the name of the file. We've also added a program called
- .B get-cf
- that can be run as root on a server to create a file of the above form
- for the control files for the user's with home directories on that
- server. Then you can run
- .B get-cf
- on every server as root, concatenate all the data together, and
- preload it into Perl. This will fix the shadow problems mentioned
- above and should also speed things up since you won't need to do all
- the file system references.
- .B kuang -f file
- will use a DBM database in place of a text file if file.dir exists.
- X
- .B Kuang
- needs to read the entire password and group databases before it
- starts, so that it has a complete idea of what users are in what groups
- and so on. This can be somewhat slow on systems using YP, since by
- default
- .B kuang
- uses the getpwent and getgrent routines to get the information (which
- is tedious on a YP client).
- The
- .B -P
- and
- .B -G
- options cause
- .B kuang
- to read /etc/passwd (/etc/group) and to use ypcat to read the rest of
- the passwd (group) YP maps, which can be much faster. In addition,
- the
- .B -p
- and
- .B -g
- options cause
- .B kuang
- to read the named files instead of /etc/passwd.
- X
- .SH "SEE ALSO"
- "Rule Based Analysis of Computer Security", Robert W. Baldwin, MIT,
- June 1987.
- X
- The README file that comes with
- .B kuang
- describes many of the design considerations, problems and future
- plans.
- X
- .SH NOTES
- .LP
- This version of
- .B kuang
- is based on the shell script versions that Dan Farmer included with
- the
- .B COPS
- security package, which in turn were based on code written by Robert
- Baldwin himself.
- X
- You should read the other documentation that should come with this
- version and modify the rules in
- .B kuang
- to suite your site.
- X
- .SH BUGS
- .LP
- Probably many.
- X
- The
- .B -P
- and
- .B -G
- options don't work right if you use +@ constructions with YP. They do
- work right if you use a simple "+:" entry, however.
- X
- SHAR_EOF
- chmod 0600 p-cops.alpha/kuang.1 ||
- echo 'restore of p-cops.alpha/kuang.1 failed'
- Wc_c="`wc -c < 'p-cops.alpha/kuang.1'`"
- test 6730 -eq "$Wc_c" ||
- echo 'p-cops.alpha/kuang.1: original size 6730, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/pass.cache.pl ==============
- if test -f 'p-cops.alpha/pass.cache.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/pass.cache.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/pass.cache.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pass.cache.pl' &&
- #
- # Routines for reading and caching user and group information. These
- # are used in multiple programs... it caches the info once, then hopefully
- # won't be used again.
- #
- # Steve Romig, May 1991.
- #
- # 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
- package main;
- X
- $PASSWD = '/etc/passwd' unless defined $PASSWD;
- X
- require 'pathconf.pl';
- X
- %uname2shell = ();
- %uname2dir = ();
- %uname2uid = ();
- %uname2passwd = ();
- %uid2names = ();
- %gid2members = ();
- %gname2gid = ();
- %gid2names = ();
- X
- $DOMAINNAME = "/bin/domainname" unless defined $DOMAINNAME;
- $YPCAT = "/bin/ypcat" unless defined $YPCAT;
- X
- $yptmp = "./yptmp.$$";
- 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 system("$YPCAT passwd > $yptmp 2> /dev/null");
- X if (-s $yptmp) {
- X open(FILE, "$YPCAT passwd|") ||
- X die "can't 'ypcat passwd'";
- X while (<FILE>) {
- X chop;
- X &add_pw_info(split(/:/));
- X }
- 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 system("$YPCAT passwd > $yptmp 2> /dev/null");
- X if (-s $yptmp) {
- 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 #
- 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
- # Load the password stuff -- Do NOT take this out!
- &'load_passwd_info(0,$PASSWD);
- X
- unlink $yptmp;
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/pass.cache.pl ||
- echo 'restore of p-cops.alpha/pass.cache.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/pass.cache.pl'`"
- test 10420 -eq "$Wc_c" ||
- echo 'p-cops.alpha/pass.cache.pl: original size 10420, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/pass.chk ==============
- if test -f 'p-cops.alpha/pass.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/pass.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/pass.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pass.chk' &&
- #
- # Pass.chk -- a password guesser. Functionally equivalent to the original
- # cops password guesser. The -P option doesn't work right now (alpha release,
- # don't you know :-), since we're doing funky things with password caching,
- # but this will change soon.
- #
- # Usage: $0 [options] dictionary
- #
- # -P pfile password file
- # -p print found passwords (incompatible with -M)
- # -d check prefix/suffix of digits [0-9]
- # -g check all words in gcos, plan, project, signature files
- # -r reverse the word tests
- # -s check all single chars as passwords
- # -u output the current user being checked
- # -U try uppercase word tests
- # -v verbose, print advisory information
- #
- # Originally written by Tim Tessin with lots more features, etc., etc., etc.
- # I ripped out all of his extra functionality that duplicated some of the
- # other cops stuff, and some things that just didn't fit, and added some
- # code to finish the simulation of the old checker. -- dan
- #
- X
- require "pass.cache.pl";
- X
- $Passwd = "/etc/passwd";
- $Adjunct = "/etc/security/passwd.adjunct";
- $Log = "passwd.log";
- $Tmp = "tmpi.$$";
- $Secure_Tmp = "/etc/security/tmpi.$$";
- $Secure_Tmp = "./tmpi.$$";
- $Secure_Log = "./passwd.log";
- $Move = "/bin/mv";
- $Create = "/bin/touch";
- X
- &Getopts("dgiprsuUvwyo:P:l:b:e:") || do {
- X print STDERR "Illegal arguments\n";
- X &usage();
- X exit(1);
- X };
- X
- sub usage {
- X print STDERR "usage: pi -firuwyv -p <pwdfile> -l <logfile>\n -o <nday> -b <bname> -e <ename>\n";
- X }
- X
- # sanity checks
- $opt_P = $Passwd unless $opt_P;
- $opt_l = $Log unless $opt_l;
- unless (-r $opt_P) {
- X print STDERR "Can't read passwd file $opt_P\n";
- X exit(1);
- X }
- X
- # unbuffer output
- select (STDOUT); $| = 1;
- X
- $dups = 0; # duplicate name entries
- $new = 0; # new entries
- $changed = 0; # password (and other data) changed
- $deleted = 0; # deleted entries
- $updated = 0; # data other than password changed
- $nlog = 0; # number of log entries, used for print decisions
- $ntest = 0;
- $ndone = 0;
- X
- for $uid (keys %uname2passwd) {
- X $pass = $uname2passwd{$uid};
- X if ($try = &dopwd()) {
- X $pwd = ($opt_p) ? $try : "";
- X # printf "Username: %-8s <password guessed> $pwd\n",$P[0];
- X printf "Warning! Password Problem: Guessed: %s\t\t$pwd\n",$P[0];
- X }
- X $ndone++;
- X $time = time();
- X print LOG "$time$ok:$value\n";
- X }
- X
- &finish;
- 1;
- # end of program
- X
- X
- ######################## Support Subroutines ###########################
- # dopwd tests each password entry against several simple checks and all
- # the words in the dictionaries specified. The simple checks consists
- # of trying the username as the password ('joe' accounts), words derived
- # from the gecos fields (usually first and last names).
- X
- sub dopwd {
- $tries = 0;
- X
- if ($opt_u) { print "$uid\n"; }
- X
- # try user name
- ($try = &testpwd($uid,$pass)) && return $try;
- X
- # do gcos field?
- if ($opt_g) {
- X @gcos = split(/[.,& -]/,$uname2gcos{$uid});
- X foreach $i (@gcos) {
- X next unless $i; # skip null split values
- X ($try = &testpwd($i,$pass)) && return $try;
- X }
- X
- X # Try names from misc files
- X #
- X undef %words;
- X # files to check
- X @files2chk = ("/.project", "/.plan", "/.signature");
- X $home = $uname2dir{$uid};
- X for $i (@files2chk) {
- X open (FOOFILE, $home . $i);
- X while (<FOOFILE>) {
- X chop;
- X @line = split(/([.,;\s])/);
- X for $j (@line) {
- X $words{$j}=$j unless $j=~/[\s]/;
- X }
- X }
- X close FOOFILE;
- X }
- X for $k (values %words) {
- X # print "word $k\n";
- X ($try = &testpwd($k,$pass)) && return $try;
- X }
- X }
- X
- # do dictionaries
- # save state of upper/reverse so individual dicts can temporarily
- # override.
- foreach $i (@ARGV) {
- X if (open (DICT,$i)) {
- X while (<DICT>) {
- X chop;
- X if ($try = &testpwd($_,$pass)) {
- X close DICT;
- X return $try;
- X }
- X }
- X close DICT;
- X }
- X }
- return 0;
- }
- X
- X
- # small subroutines to help the main password cracker. All are labeled
- # p_xxx, where xxx is the identifying name.
- #
- X
- # if leading character is upper-case, also try lower case version
- sub p_lc {
- local($try) = @_;
- local($ntry);
- if ( $try =~ /^[A-Z]/ ) {
- X ($ntry = $try) =~ y/A-Z/a-z/;
- X push(@total_guesses, $ntry);
- X }
- }
- X
- # reverse check
- sub p_rev {
- local($try) = @_;
- local($ntry);
- $ntry = reverse $try;
- if ($ntry ne $try) {
- X push(@total_guesses, $ntry);
- X }
- }
- X
- # uppercase check
- sub p_up {
- local($try) = @_;
- local($ntry);
- ($ntry = $try) =~ y/a-z/A-Z/;
- if ($ntry ne $try) { push(@total_guesses, $ntry); }
- }
- X
- # testpwd checks a word to see if it matches the encrpted password
- # if the word is capitalized, the lowercase version is tried as well
- X
- sub testpwd {
- local ($try,$pass) = @_;
- local (@total_guesses);
- X
- push(@total_guesses, $try);
- X
- # free (lower case) check if first letter is uppercase
- &p_lc($try);
- # reverse?
- if ($opt_r) { &p_rev($try); }
- # uppercase?
- if ($opt_U) { &p_up($try); }
- X
- if ($opt_d) {
- X if (length ($try) < 8) {
- X foreach $i ('0'..'9') {
- X $ntry = $i.$try;
- X push(@total_guesses, $ntry);
- X if ($opt_r) { &p_rev($ntry); }
- X if ($opt_U) { &p_up($ntry); }
- X }
- X foreach $i ('0'..'9') {
- X $ntry = $try.$i;
- X push(@total_guesses, $ntry);
- X if ($opt_r) { &p_rev($ntry); }
- X if ($opt_U) { &p_up($ntry); }
- X }
- X }
- X }
- X
- # do single letters, #'s, if needed
- if ($opt_s && $user ne $last_user) {
- X $last_user = $user;
- X foreach $i (0..9) { push(@total_guesses, $i); }
- X foreach $i (A..Z) { push(@total_guesses, $i); }
- X foreach $i (a..z) { push(@total_guesses, $i); }
- X }
- X
- foreach $i (@total_guesses) {
- X # print "trying $i\n";
- X $epw = &pcrypt($i, $pass);
- X ($epw eq $pass) && return $i;
- X }
- undef @total_guesses;
- X
- return 0;
- }
- X
- sub finish {
- unlink "/tmp/pi.log.$$";
- unlink "/tmp/xpi.$$";
- unlink $Tmp;
- }
- X
- # These routines taken from the perl library to provide the functions here
- # so we don't have to depend on perl libraries being properly installed.
- X
- # getopts.pl - a better getopt.pl
- # Usage:
- # do Getopts('a:bc'); # -a takes arg. -b & -c not. Sets opt_* as a
- # # side effect.
- 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
- sub pcrypt {
- local($try, $pass) = @_;
- X
- print "Trying \"$try\" on $uid\n" if $opt_v;
- X
- $epw = crypt($try,$pass);
- X
- return $epw;
- }
- X
- SHAR_EOF
- chmod 0700 p-cops.alpha/pass.chk ||
- echo 'restore of p-cops.alpha/pass.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/pass.chk'`"
- test 6684 -eq "$Wc_c" ||
- echo 'p-cops.alpha/pass.chk: original size 6684, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/passwd.chk ==============
- if test -f 'p-cops.alpha/passwd.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/passwd.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/passwd.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/passwd.chk' &&
- #
- # passwd.chk
- #
- # Check password file -- /etc/passwd -- for incorrect number of fields,
- # duplicate uid's, non-alphanumeric uids, and non-numeric group id's.
- #
- # Mechanism: This script ensures that each line of the passwd file (in
- # $etc, line 47) has 7 fields and is non-blank, as well as examining the
- # file for any duplicate users. It then checks to ensure that the first
- # character of the login name is alphanumeric, and that all uid and gid
- # numbers are indeed numeric and non-negative. It also checks the
- # validity of the home directory.
- #
- # For yellow pages passwords, it does the same checking, but in order to
- # get a listing of all members of the password file, it does a "ypcat
- # passwd" and uses the output from that as a passwd file.
- #
- # The /etc/passwd file has a very specific format, making the task fairly
- # simple. Normally it has lines with 7 fields, each field separated by a
- # colon (:). The first field is the user id, 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
- # user id number, the fourth field is the group id number, the fifth field
- # is the GECOS field (basically holds miscellaneous information, varying
- # from site to site), the sixth field is the home directory of the user,
- # and lastly the seventh field is the login shell of the user. No blank
- # lines should be present. Uid's will be flagged if over 8 chars, unless
- # the $OVER_8 variable (line 45) is set to "YES".
- #
- # If a line begins with a plus sign (+), it is a yellow pages entry. See
- # passwd(5) for more information, if this applies to your site.
- #
- X
- # If your kernel doesn't understand the #! notation, this script will
- # feed itself to perl
- eval 'exec /usr/local/bin/perl -S $0 $*'
- X if $running_under_some_shell;
- X
- # Used for Sun C2 security group file. 'FALSE' (default) will flag
- # valid C2 passwd syntax as an error, 'TRUE' attempts to validate it.
- # Thanks to Pete Troxell for pointing this out.
- $C2='FALSE';
- X
- # Some systems allow long uids; set this to 'TRUE', if so (thanks
- # to Pete Shipley (lot of petes around here, eh?)):
- $OVER_8='NO';
- X
- #
- # Important files:
- $etc="/etc/passwd";
- $yptmp = "./yptmp.$$";
- X
- # If YPCAT is in the environment, use that, otherwise use the default
- if (defined $ENV{'YPCAT'}) { $YPCAT = $ENV{'YPCAT'}; }
- else { $YPCAT = "/usr/bin/ypcat"; }
- X
- # read in $etc passwd file. This creates an associative array to check
- # duplicate login names.
- open(PASSWD, $etc) || exit(1);
- while(<PASSWD>) {
- X push(@PW_FILE, $_);
- X ($log, $pass) = split(/:/);
- X if (!defined $foo{$log}) {
- X $foo{$log} = $pass;
- X }
- X else {
- X print "Warning! Duplicate uid found in $etc: $log\n";
- X }
- X }
- close(PASSWD);
- X
- # same for yellow pages.
- if (-s $YPCAT) {
- X system("$YPCAT passwd > $yptmp 2> /dev/null");
- X if (-s $yptmp && open(YP, "$YPCAT passwd|") != 0) {
- X while(<YP>) {
- X push(@YP_FILE, $_);
- X ($log, $pass) = split(/:/);
- X if (!defined $foo{$log}) {
- X $foo{$log} = $pass;
- X }
- X else {
- X print "Warning! Duplicate uid found in yellow pages: $log\n";
- X }
- X close(YP);
- X }
- X }
- X }
- X
- # First line is for a yellow pages entry in the password file.
- # It really should check for correct yellow pages syntax....
- $NR = 0;
- foreach $_ (@PW_FILE) {
- X $NR++;
- X @ARR = split(/:/);
- X next if ($ARR[0] =~ /^\+/o);
- X
- X if (/^\s*$/o) {
- X print "Warning! Password file, line $NR, is blank\n";
- X next;
- X }
- X
- X if ($#ARR != 6) {
- X print "Warning! Password file, line $NR, does not have 7 fields: \n\t$_";
- X }
- X
- X if ($ARR[0] !~ /^[A-Za-z0-9]/o) {
- X print "Warning! Password file, line $NR, nonalphanumeric login: \n\t$_";
- X }
- X
- X if ($OVER_8 ne 'NO' && length($ARR[0]) > 8) {
- X print "Warning! Password file, line $NR, uid > 8 chars\n\t$_";
- X }
- X
- X if ($ARR[1] eq "") {
- X print "Warning! Password file, line $NR, no password:\n\t$_";
- X }
- X
- X if ($C2 eq "TRUE" && $ARR[1] =~ /^##/o && "##".$ARR[0] ne $ARR[1]) {
- X print "Warning! Password file, line $NR, invalid password field for C2: \n\t$_";
- X }
- X
- X if ($ARR[2] !~ /^[0-9]/o) {
- X if ($ARR[2] < 0) {
- X print "Warning! Password file, line $NR, negative user id: \n\t$_";
- X } else {
- X print "Warning! Password file, line $NR, nonnumeric user id: \n\t$_";
- X }
- X }
- X
- X if ($ARR[2] == 0 && $ARR[0] ne "root") {
- X print "Warning! Password file, line $NR, user $ARR[0] has uid = 0 and is not root\n\t$_";
- X }
- X
- X if ($ARR[3] !~ /^[0-9]/o) {
- X print "Warning! Password file, line $NR, nonnumeric group id: \n\t$_";
- X }
- X
- X if ($ARR[5] !~ /^\//o) {
- X print "Warning! Password file, line $NR, invalid login directory: \n\t$_";
- X }
- X
- }
- X
- #
- # Test yellow pages passwords as well
- # before you complain about me putting the test for YP_FILE first, you get less
- # indentation if you do it this way. Change it if you want.
- if (defined @YP_FILE) {
- X
- $NR = 0;
- foreach $_ (@YP_FILE) {
- X $NR++;
- X @ARR = split(/:/);
- X
- X if (/^\s*$/o) {
- X print "Warning! YPassword file, line $NR, is blank\n";
- X next;
- X }
- X
- X if ($#ARR != 6) {
- X print "Warning! YPassword file, line $NR, does not have 7 fields: \n\t$_";
- X }
- X
- X if ($ARR[0] !~ /^[A-Za-z0-9]/o) {
- X print "Warning! YPassword file, line $NR, nonalphanumeric login: \n\t$_";
- X }
- X
- X if ($OVER_8 ne 'NO' && length($ARR[0]) > 8) {
- X print "Warning! YPassword file, line $NR, uid > 8 chars\n\t$_";
- X }
- X
- X if ($ARR[1] eq "") {
- X print "Warning! YPassword file, line $NR, no password:\n\t$_";
- X }
- X
- X if ($ARR[2] !~ /^[0-9]/o) {
- X if ($ARR[2] < 0) {
- X print "Warning! YPassword file, line $NR, negative user id: \n\t$_";
- X } else {
- X print "Warning! YPassword file, line $NR, nonnumeric user id: \n\t$_";
- X }
- X }
- X
- X if ($ARR[2] == 0 && $ARR[0] ne "root") {
- X print "Warning! YPassword file, line $NR, user $ARR[0] has uid = 0 and is not root\n\t$_";
- X }
- X
- X if ($ARR[3] !~ /^[0-9]/o) {
- X print "Warning! YPassword file, line $NR, nonnumeric group id: \n\t$_";
- X }
- X
- X if ($ARR[5] !~ /^\//o) {
- X print "Warning! YPassword file, line $NR, invalid login directory: \n\t$_";
- X }
- X
- X }
- }
- X
- unlink $yptmp;
- X
- 1;
- # end
- SHAR_EOF
- chmod 0700 p-cops.alpha/passwd.chk ||
- echo 'restore of p-cops.alpha/passwd.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/passwd.chk'`"
- test 5989 -eq "$Wc_c" ||
- echo 'p-cops.alpha/passwd.chk: original size 5989, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/pathconf.pl ==============
- if test -f 'p-cops.alpha/pathconf.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/pathconf.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/pathconf.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pathconf.pl' &&
- $YPCAT = '/usr/bin/ypcat';
- $STRINGS = '/usr/ucb/strings';
- $TFTP = '/usr/ucb/tftp';
- $UUDECODE = '/usr/bin/uudecode';
- $CMP = '/bin/cmp';
- $LS = '/bin/ls';
- X
- # end of perl needed programs
- X
- $AWK = '/bin/awk';
- $CAT = '/bin/cat';
- $CC = '/bin/cc';
- $CHMOD = '/bin/chmod';
- $COMM = '/usr/bin/comm';
- $CP = '/bin/cp';
- $DATE = '/bin/date';
- $DIFF = '/bin/diff';
- $ECHO = '/bin/echo';
- $EGREP = '/usr/bin/egrep';
- $EXPR = '/bin/expr';
- $FIND = '/usr/bin/find';
- $GREP = '/bin/grep';
- $MAIL = '/bin/mail';
- $MKDIR = '/bin/mkdir';
- $MV = '/bin/mv';
- $RM = '/bin/rm';
- $SED = '/bin/sed';
- $SH = '/bin/sh';
- $SORT = '/usr/bin/sort';
- $TEST = '/bin/test';
- $TOUCH = '/usr/bin/touch';
- $UNIQ = '/usr/bin/uniq';
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/pathconf.pl ||
- echo 'restore of p-cops.alpha/pathconf.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/pathconf.pl'`"
- test 677 -eq "$Wc_c" ||
- echo 'p-cops.alpha/pathconf.pl: original size 677, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/pathconf.sh ==============
- if test -f 'p-cops.alpha/pathconf.sh' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/pathconf.sh (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/pathconf.sh (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pathconf.sh' &&
- YPCAT = '/usr/bin/ypcat';
- STRINGS = '/usr/ucb/strings';
- TFTP = '/usr/ucb/tftp';
- UUDECODE = '/usr/bin/uudecode';
- X
- # end of perl needed programs
- X
- AWK = '/bin/awk';
- CAT = '/bin/cat';
- CC = '/bin/cc';
- CHMOD = '/bin/chmod';
- CMP = '/bin/cmp';
- COMM = '/usr/bin/comm';
- CP = '/bin/cp';
- DATE = '/bin/date';
- DIFF = '/bin/diff';
- ECHO = '/bin/echo';
- EGREP = '/usr/bin/egrep';
- EXPR = '/bin/expr';
- FIND = '/usr/bin/find';
- GREP = '/bin/grep';
- LS = '/bin/ls';
- MAIL = '/bin/mail';
- MKDIR = '/bin/mkdir';
- MV = '/bin/mv';
- RM = '/bin/rm';
- SED = '/bin/sed';
- SH = '/bin/sh';
- SORT = '/usr/bin/sort';
- TEST = '/bin/test';
- TOUCH = '/usr/bin/touch';
- UNIQ = '/usr/bin/uniq';
- SHAR_EOF
- chmod 0700 p-cops.alpha/pathconf.sh ||
- echo 'restore of p-cops.alpha/pathconf.sh failed'
- Wc_c="`wc -c < 'p-cops.alpha/pathconf.sh'`"
- test 644 -eq "$Wc_c" ||
- echo 'p-cops.alpha/pathconf.sh: original size 644, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/rc.chk ==============
- if test -f 'p-cops.alpha/rc.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/rc.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/rc.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/rc.chk' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # Usage: rc.chk
- #
- # This checks pathnames and files inside the shell script files /etc/rc*
- # for writability. The commands inside the files /etc/rc* are executed when
- # the machine is booted, so are of special interest.
- #
- # Made easy by chk_strings :-)
- #
- # Name: Martin Foord Username: maf Date: Thu Jan 17 15:11:09 EST 1991
- # Email: maf%dbsm.oz.au@munnari.oz.au
- #
- # 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 'chk_strings.pl';
- X
- while (</etc/rc*>) {
- X &chk_strings($_);
- }
- X
- 1;
- SHAR_EOF
- chmod 0700 p-cops.alpha/rc.chk ||
- echo 'restore of p-cops.alpha/rc.chk failed'
- Wc_c="`wc -c < 'p-cops.alpha/rc.chk'`"
- test 1014 -eq "$Wc_c" ||
- echo 'p-cops.alpha/rc.chk: original size 1014, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/reconfig.pl ==============
- if test -f 'p-cops.alpha/reconfig.pl' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/reconfig.pl (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/reconfig.pl (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/reconfig.pl' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- # 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
- # Target shell scripts in question:
- $COPS_CONFIG="pathconf.pl";
- X
- # Potential directories to find commands:
- @all_dirs=("/bin",
- X "/usr/bin",
- X "/usr/ucb",
- X "/usr/local/bin", # scary
- X "/usr/bsd");
- X
- # comment out next line if you want your own current path used instead
- #
- # @all_dirs = split(/:/, $ENV{'PATH'});
- X
- # Target commands in question, sans those checked above:
- @all_commands= ("cc", "awk", "cat",
- X "chmod", "cmp", "comm", "cp",
- X "date", "diff", "echo", "egrep", "expr",
- X "find", "grep", "ls", "mail",
- X "mkdir", "mv", "rm", "sed",
- X "sh", "sort", "test", "tftp", "touch",
- X "uudecode", "uniq", "ypcat");
- X
- @want{@all_commands} = ();
- X
- %exceptions= ('strings', 'chk_strings',
- X 'tftp', 'misc.chk',
- X 'cmp', 'ftp.chk',
- X 'uudecode', 'misc.chk');
- X
- # grab the current values:
- open COPS_CONFIG || die "Can't open $COPS_CONFIG: $!";
- X
- $new = "$COPS_CONFIG.$$";
- open(NEW_CONFIG, ">$new") || die "Can't open $new: $!";
- X
- while (<COPS_CONFIG>) {
- X unless (/\$(\w+)\s*=\s*(['"])(\S*)\2/) {
- X print NEW_CONFIG;
- X next;
- X }
- X ($cap_command, $path) = ($1, $3);
- X ($command = $cap_command) =~ tr/A-Z/a-z/;
- X unless (($newpath = &getpath($command)) || $command =~ /^yp/) {
- X warn "Warning! no path for $command!\n";
- X warn " $exceptions{$command} will not work as planned!\n"
- X if $exceptions{$command};
- X $errors++;
- X } else {
- X delete $want{$command};
- X }
- X print "old $path now in $newpath\n" if $newpath ne $path;
- X print NEW_CONFIG "\$$cap_command = '$newpath';\n";
- X
- }
- X
- for (sort keys %want) {
- X delete $want{$_} if $path = &getpath($_);
- X tr/a-z/A-Z/;
- X print NEW_CONFIG '$', $_, " = '", $path, "';\n";
- }
- X
- close(COPS_CONFIG) || die "can't close $COPS_CONFIG: $!";
- close(NEW_CONFIG) || die "can't close $new: $!";
- X
- if (@missing = keys %want) {
- X warn "Warning! missing paths for @missing!\n";
- X warn "The shell version may not work right!\n";
- }
- X
- X
- if ($errors) {
- X print STDERR "Not all paths were found: write anyway? ";
- X exit 1 if <STDIN> !~ /^\s*y/i;
- X print STDERR "Ok, but this might not be right...\n";
- }
- X
- $old = "$COPS_CONFIG.old";
- X
- rename($COPS_CONFIG, $old)
- X || die "can't rename $COPS_CONFIG to $old: $!";
- X
- rename($new, $COPS_CONFIG)
- X || die "can't rename $new to $COPS_CONFIG: $!";
- X
- X
- open COPS_CONFIG || die "can't re-open $COPS_CONFIG: $!";
- ($SH_CONF = $COPS_CONFIG) =~ s/\.pl$/.sh/;
- open (SH_CONF, ">$SH_CONF") || die "can't create $SH_CONF: $!";
- X
- while (<COPS_CONFIG>) {
- X s/^\$//;
- X print SH_CONF;
- }
- close SH_CONF || die "can't close $SH_CONF: $!";
- X
- Xexit 0;
- X
- #############
- X
- sub getpath {
- X local($cmd) = @_;
- X local($path);
- X
- X for (@all_dirs) {
- X return $path if -x ($path = "$_/$cmd");
- X }
- X '';
- }
- SHAR_EOF
- chmod 0700 p-cops.alpha/reconfig.pl ||
- echo 'restore of p-cops.alpha/reconfig.pl failed'
- Wc_c="`wc -c < 'p-cops.alpha/reconfig.pl'`"
- test 3296 -eq "$Wc_c" ||
- echo 'p-cops.alpha/reconfig.pl: original size 3296, current size' "$Wc_c"
- rm -f _shar_wnt_.tmp
- fi
- # ============= p-cops.alpha/user.chk ==============
- if test -f 'p-cops.alpha/user.chk' -a X"$1" != X"-c"; then
- echo 'x - skipping p-cops.alpha/user.chk (File already exists)'
- rm -f _shar_wnt_.tmp
- else
- > _shar_wnt_.tmp
- echo 'x - extracting p-cops.alpha/user.chk (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/user.chk' &&
- #!/bin/sh # need to mention perl here to avoid recursion
- #
- # This combines user.chk and home.chk. It searches for home directories
- # and various user startup files for world writability, as well as flagging
- # any .rhosts and .netrc files that are readable. You can change the
- # files checked by changing @ftable and @readables, 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
- SHAR_EOF
- true || echo 'restore of p-cops.alpha/user.chk failed'
- fi
- echo 'End of alpha p-cops part 2'
- echo 'File p-cops.alpha/user.chk is continued in part 3'
- echo 3 > _shar_seq_.tmp
- exit 0
-