home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-385-Vol-1of3.iso
/
c
/
cops_104.zip
/
cops_104
/
kuang.pl.shar
/
kuang.pl
< prev
next >
Wrap
Perl Script
|
1992-03-10
|
17KB
|
772 lines
#! /usr/local/bin/perl
#
# 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.
#
do 'yagrip.pl' ||
die "can't do yagrip.pl";
$options = "vdlf:D";
$usage = "usage: kuang [-v] [-d] [-l] [-D] [-f filedata] [u.username|g.groupname]\n";
#
# Simple Unix Kuang, a security checking program.
#
# This is a perl version of Dan Farmer's version of Bob Baldwin's
# shell scripts.
#
#
# passwd_byuid lookup a password entry by uid, return as
# (name, password, directory, shell) list.
#
sub passwd_byuid {
local($uid) = @_;
local($name, $passwd, $gid, $quota, $comment, $gcos, $dir, $shell);
if (! defined($passwd_byuid_list{$uid})) {
($name, $passwd, $t_uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
getpwuid($uid);
if ($t_uid eq "") {
return();
}
$passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
$passwd_byname_list{$name} = $uid;
}
return(split(/:/, $passwd_byuid_list{$uid}));
}
sub passwd_byname {
local($name) = @_;
local($passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell);
if (! defined($passwd_byname_list{$name})) {
($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
getpwnam($name);
if ($uid eq "") {
return();
}
$passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
$passwd_byname_list{$name} = $uid;
}
return($passwd_byname_list{$name});
}
#
# group_bygid lookup a group entry by gid, return as
# (name, members) list.
#
sub group_bygid {
local($gid) = @_;
local($name, $t_passwd, $t_gid, $members);
if (! defined($group_bygid_list{$gid})) {
($name, $t_passwd, $t_gid, $members) = getgrgid($gid);
if ($t_gid eq "") {
return();
}
$group_bygid_list{$gid} = join(':', $name, $members);
$group_byname_list{$name} = $gid;
}
return(split(/:/, $group_bygid_list{$gid}));
}
sub group_byname {
local($name) = @_;
local($gname, $passwd, $gid, $members);
if (! defined($group_byname_list{$name})) {
($gname, $passwd, $gid, $members) = getgrnam($name);
if ($gid eq "") {
printf(stderr "A group named '$name' does not exist!\n");
exit(1);
}
$group_bygid_list{$gid} = join(':', $name, $members);
$group_byname_list{$name} = $gid;
}
return($group_byname_list{$name});
}
#
# Do various initialization type things.
#
sub init_kuang {
local($which, $name, $uid, $gid);
local($f_type, $f_uid, $f_gid, $f_mode, $f_name);
local($count);
$which = "u";
$name = "root";
#
# Deal with args...
#
&getopt($options) ||
die $usage;
if ($#ARGV == 0) {
($which, $name) = split(/\./, $ARGV[0]);
if ($name eq "") {
$name = $which;
$which = "u";
}
if ($which ne "u" && $which ne "g") {
printf(stderr "target must be given as u.user or g.group\n");
exit(1);
}
} elsif ($#ARGV > 0) {
printf(stderr $usage);
exit(1);
}
#
# Preload the file data...
#
if (defined($opt_f)) {
#
# If we are dumping the file data to a DBM file, nuke the existing
# ones and open the dbm file. Otherwise, open the DBM file
# only if they exist.
#
$read_from_file = 1;
if (defined($opt_D)) {
unlink("$opt_f.dir", "$opt_f.pag");
dbmopen(files, $opt_f, 0644) ||
die sprintf("can't open DBM file '%s'", $opt_f);
} elsif (-f "$opt_f.dir") {
dbmopen(files, $opt_f, 0644) ||
die sprintf("can't open DBM file '%s'", $opt_f);
$read_from_file = 0;
}
if ($read_from_file) {
open(FILEDATA, $opt_f) ||
die sprintf("kuang: can't open '%s'", $opt_f);
$count = 0;
while (<FILEDATA>) {
$count++;
chop;
($f_type, $f_uid, $f_gid, $f_mode, $f_name) = split;
if ($count % 1000 == 0) {
printf("line $count, reading entry for $f_name\n");
}
$files{$f_name} = join(' ', $f_uid, $f_gid, $f_mode);
}
close(FILEDATA);
}
}
if (defined($opt_D)) {
dbmclose(files);
exit(0);
}
if (defined($opt_v)) {
printf("done with files\n");
}
#
# Need some of the password and group stuff. Suck in passwd and
# group info, store by uid and gid in an associative array of strings
# which consist of fields corresponding to the passwd and group file
# entries (and what the heck, we'll use : as a delimiter also...:-)
#
$passwd_byuid_list{-1} = "OTHER:::"; # add an entry for OTHER
$passwd_byname_list{"OTHER"} = -1;
$uids_known{-1} = ""; # we can access OTHER
%uids_new = ();
%gids_known = ();
%gids_new = ();
%files_new = ();
#
# Set up initial goal: become target user or group
#
if ($which eq "u") {
$uid = &passwd_byname($name);
if ($uid ne "") {
&addto("uids", $uid, "grant u.$name do anything");
} else {
printf(stderr "There is no user with username '$name'.\n");
exit(1);
}
} else {
$gid = &group_byname($name);
if ($gid ne "") {
&addto("gids", $gid, "grant g.$name");
} else {
printf(stderr "There is no group named '$name'.\n");
exit(1);
}
}
}
#
# Get the home directory for this UID from the passwd file cache.
#
sub gethome {
local($uid) = @_;
local($tmp, $home);
($tmp, $tmp, $home, $tmp) = &passwd_byuid($uid);
return($home);
}
#
# 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 {
local($name) = @_;
local($tmp, $mode, $uid, $gid, $other);
#
# Check the file cache - avoid disk lookups for performance and
# to avoid shadows...
#
if (defined($files{$name})) {
$cache_hit++;
($uid, $gid, $mode) = split(/ /, $files{$name});
$mode = oct($mode);
} else {
$cache_miss++;
if (! -e $name && $read_from_file) {
$files{$name} = "";
return;
}
($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
if ($read_from_file) {
$files{$name} = join(' ', $uid, $gid, $mode);
}
}
if (($mode & 020) != 020) {
$gid = "";
}
if (($mode & 02) == 02) {
$other = 1;
} else {
$other = 0;
}
return($uid, $gid, $other);
}
#
# return # of entries in given associative array.
#
sub sizeof {
local(*which) = @_;
local(@keywords);
@keywords = keys %which;
return($#keywords + 1);
}
#
# return appropriate entry from named associative array of given type.
# returns a (key, value) pair - if key is "", there was no entry.
#
sub getentry {
local($which, $type, $key) = @_;
local($newkey, $value);
$newkey = "";
$value = "";
which: {
if ($which eq "uids") {
type0: {
if ($type eq "known") {
if (defined($uids_known{$key})) {
$newkey = $key; $value = $uids_known{$key};
}
last type0;
}
if ($type eq "new") {
if (defined($uids_new{$key})) {
$newkey = $key; $value = $uids_new{$key};
}
last type0;
}
if ($type eq "pending") {
if (defined($uids_pending{$key})) {
$newkey = $key; $value = $uids_pending{$key};
}
last type0;
}
if ($type eq "old") {
if (defined($uids_old{$key})) {
$newkey = $key; $value = $uids_old{$key};
}
last type0;
}
printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
if ($which eq "gids") {
type1: {
if ($type eq "known") {
if (defined($gids_known{$key})) {
$newkey = $key; $value = $gids_known{$key};
}
last type1;
}
if ($type eq "new") {
if (defined($gids_new{$key})) {
$newkey = $key; $value = $gids_new{$key};
}
last type1;
}
if ($type eq "pending") {
if (defined($gids_pending{$key})) {
$newkey = $key; $value = $gids_pending{$key};
}
last type1;
}
if ($type eq "old") {
if (defined($gids_old{$key})) {
$newkey = $key; $value = $gids_old{$key};
}
last type1;
}
printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
if ($which eq "files") {
type2: {
if ($type eq "known") {
if (defined($files_known{$key})) {
$newkey = $key; $value = $files_known{$key};
}
last type2;
}
if ($type eq "new") {
if (defined($files_new{$key})) {
$newkey = $key; $value = $files_new{$key};
}
last type2;
}
if ($type eq "pending") {
if (defined($files_pending{$key})) {
$newkey = $key; $value = $files_pending{$key};
}
last type2;
}
if ($type eq "old") {
if (defined($files_old{$key})) {
$newkey = $key; $value = $files_old{$key};
}
last type2;
}
printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
printf(stderr "kuang: fatal error in getentry: which is wrong (%s)\n",
$which);
exit(1);
}
return($newkey, $value);
}
#
# stores a (key, value) in the associative array of the given type.
#
sub putentry {
local($which, $type, $key, $value) = @_;
which: {
if ($which eq "uids") {
type0: {
if ($type eq "known") {
$uids_known{$key} = $value; last type0;
}
if ($type eq "new") {
$uids_new{$key} = $value; last type0;
}
if ($type eq "pending") {
$uids_pending{$key} = $value; last type0;
}
if ($type eq "old") {
$uids_old{$key} = $value; last type0;
}
printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
if ($which eq "gids") {
type1: {
if ($type eq "known") {
$gids_known{$key} = $value; last type1;
}
if ($type eq "new") {
$gids_new{$key} = $value; last type1;
}
if ($type eq "pending") {
$gids_pending{$key} = $value; last type1;
}
if ($type eq "old") {
$gids_old{$key} = $value; last type1;
}
printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
if ($which eq "files") {
type2: {
if ($type eq "known") {
$files_known{$key} = $value; last type2;
}
if ($type eq "new") {
$files_new{$key} = $value; last type2;
}
if ($type eq "pending") {
$files_pending{$key} = $value; last type2;
}
if ($type eq "old") {
$files_old{$key} = $value; last type2;
}
printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n",
$type);
exit(1);
}
last which;
}
printf(stderr "kuang: fatal error in putentry: which is wrong (%s)\n",
$which);
exit(1);
}
}
sub addto {
local($which, $key, $plan) = @_;
local($tkey, $tvalue);
#
# See whether there's an entry for $key in the known list for the
# $which array. If so - success, we've found a suitable breakin
# path.
#
($tkey, $tvalue) = &getentry($which, "known", $key);
if ($tkey eq $key) {
printf("Success! $key $plan\n");
return;
}
#
# Check to see if its a duplicate - if so, don't need to do anything.
#
($tkey, $tvalue) = &getentry($which, "pending", $key);
if ($tkey eq $key) {
return;
}
#
# Add to pending list for $which...
#
&putentry($which, "pending", $key, $plan);
#
# Add to next goal list for $which...
#
&putentry($which, "new", $key, $plan);
if (defined($opt_v)) {
printf("addto: $which --> $plan\n");
}
#
# If this is a uid goal, then add the plan to the accessible list.
#
if ($which eq "uids" && $key ne "0" && defined($opt_l)) {
$accessible{$plan} = "1";
}
}
#
#----------------------------------------------------------------------
#Main program follows...initialize and loop till we're done.
#
&init_kuang();
#
# While there's still something to pursue...
#
while (&sizeof(*uids_new) != 0 ||
&sizeof(*gids_new) != 0 ||
&sizeof(*files_new) != 0) {
#
# Deal with uids first...
#
if (&sizeof(*uids_new) != 0) {
%uids_old = %uids_new;
%uids_new = ();
foreach $uid (keys %uids_old) {
$plan = $uids_old{$uid};
if (defined($opd_d)) {
printf("uids evel: $uid '$plan'\n");
}
&addto("files", "/etc/passwd", "replace /etc/passwd $plan");
&addto("files", "/usr/lib/aliases", "replace /usr/lib/aliases $plan");
#
# Add controlling files for this user. There are probably
# others (such as .logout, X tool start things and so on).
# (fixme) add other CF's...
#
$home = &gethome($uid);
if ($home ne "") {
if ($home eq "/") {
$home = "";
}
if (-e "$home/.rhosts") {
&addto("files", "$home/.rhosts", "write $home/.rhosts $plan");
}
if (-e "$home/.login") {
&addto("files", "$home/.login", "replace $home/.login $plan");
}
if (-e "$home/.logout") {
&addto("files", "$home/.logout", "replace $home/.logout $plan");
}
if (-e "$home/.cshrc") {
&addto("files", "$home/.cshrc", "replace $home/.cshrc $plan");
}
if (-e "$home/.profile") {
&addto("files", "$home/.profile", "replace $home/.profile $plan");
}
}
#
# Controlling files for root...
#
if ($uid+0 == 0) {
foreach $file ("/etc/rc", "/etc/rc.boot", "/etc/rc.single", "/etc/rc.config", "/etc/rc.local", "/usr/lib/crontab", "/usr/spool/cron/crontabs") {
if (-e $file) {
&addto("files", $file, "replace $file $plan");
}
}
}
if ($uid+0 != 0) {
&addto("files", "/etc/hosts.equiv", "replace /etc/hosts.equiv allow rlogin $plan");
if (-s "/etc/hosts.equiv") {
&addto("files", "/etc/hosts", "replace /etc/hosts fake hostaddress allow rlogin $plan");
}
}
}
}
#
# Deal with groups...
#
if (&sizeof(*gids_new) != 0) {
%gids_old = %gids_new;
%gids_new = ();
bar_loop:
foreach $gid (keys %gids_old) {
$plan = $gids_old{$gid};
if (defined($opt_d)) {
printf("gids eval: $gid '$plan'\n");
}
($gname, $members) = &group_bygid($gid);
if ($gname eq "") {
printf("There is no group with gid $gid.\n");
next bar_loop;
}
foo_loop:
foreach $uname (split(/[ \t\n]+/, $members)) {
$uid = &passwd_byname($uname);
if ($uid eq "") {
printf(stderr "Group $gname has an unknown user $uname\n");
next foo_loop;
}
&addto("uids", "$uid", "grant u.$uname $plan");
}
&addto("files", "/etc/group", "replace /etc/group $plan");
}
}
#
# Deal with files...
#
if (&sizeof(*files_new) != 0) {
%files_old = %files_new;
%files_new = ();
file_loop:
foreach $file (keys %files_old) {
$plan = $files_old{$file};
($mode) = split(/[ \t\n]+/, $plan);
if (defined($opt_d)) {
printf("files eval: $file '$plan'\n");
}
($owner, $group, $other) = &filewriters($file);
if ($owner eq "") {
printf("%s does not exist\n", $file);
next file_loop;
}
($uname) = &passwd_byuid($owner);
if ($uname eq "") {
$uname = $owner;
}
&addto("uids", $owner, "grant u.$uname $plan");
if ($group ne "") {
($gname, $tmp) = &group_bygid($group);
if ($gname ne "") {
&addto("gids", $group, "grant g.$gname $plan");
} else {
printf(stderr "There is no group with gid $group.\n");
}
}
if ($other) {
&addto("uids", -1, "grant u.OTHER $plan");
}
if ($mode eq "replace") {
$parent = $file;
$parent =~ s|/[^/]*$||; # strip last / and remaining
if ($parent eq "") { # if nothing left, use /
$parent = "/";
}
if ($parent ne $file) { # since $file might've been /
&addto("files", $parent, "replace $parent $plan");
}
}
}
}
}
if (defined($opt_l)) {
foreach $key (keys %accessible) {
printf("$key\n");
}
}
if (defined($opt_v) || $cache_hit) {
printf("File info cache hit/access ratio: %g\n",
$cache_hit / ($cache_hit + $cache_miss));
}