|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T k
Length: 15363 (0x3c03)
Types: TextFile
Names: »kuang«
└─⟦4f9d7c866⟧ Bits:30007245 EUUGD6: Sikkerheds distributionen
└─⟦3da311d67⟧ »./cops/1.04/cops_104.tar.Z«
└─⟦6a2577110⟧
└─⟦4f9d7c866⟧ Bits:30007245 EUUGD6: Sikkerheds distributionen
└─⟦6a2577110⟧ »./cops/1.04/cops_104.tar«
└─⟦this⟧ »cops_104/perl/kuang«
#!/bin/sh -- need to mention perl here to avoid recursion
'true' || eval 'exec perl -S $0 $argv:q';
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
& eval 'exec /usr/users/df/bin/perl.sun4 -S $0 $argv:q'
if 0;
# & eval 'exec /usr/local/bin/perl -S $0 $argv:q'
#
# 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.
#
#
# 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.
# -Y preload passwd info from ypcat + /etc/passwd
# -g group preload group info from the named file.
# -G preload group info from ypcat + /etc/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.
#
$options = "ldVk:p:g:f:YG";
$usage = "usage: kuang [-l] [-d] [-v] [-k known] [-f file] [-Y] [-G] [-p passwd] [-g group] [u.username|g.groupname]\n";
$add_files_to_cache = 1; # Whether to update the %files cache
# with local file info or not.
#
# 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
# 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.
#
do 'yagrip.pl' ||
die "can't do yagrip.pl";
# do 'pwgrid.pl' ||
# die "can't do pwgrid.pl";
do 'pass.cache.pl' ||
die "can't do pass.cache.pl";
do 'rules.pl' ||
die "can't do rules.pl";
\f
#
# 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.
#
sub canonicalize {
local($string) = @_;
local($op, $value);
if ($string =~ /^([ugrw]) ([^ \t\n]+)$/) { # of form "op value"
$op = $1;
$value = $2;
} elsif ($string =~ /^[^ \t\n]+$/) { # of form "value"
$value = $string;
$op = "u";
} else {
return();
}
if ($op eq "u" && $value =~ /^[^0-9]+$/) { # user name, not ID
if (defined($uname2uid{$value})) {
$value = $uname2uid{$value};
} else {
printf(stderr "There's no user named '%s'.\n", $value);
return();
}
} elsif ($op eq "g" && $value =~/^[^0-9]+$/) {
if (defined($gname2gid{$value})) {
$value = $gname2gid{$value};
} else {
printf(stderr "There's no group named '%s'.\n", $value);
return();
}
}
return($op, $value);
}
\f
#
# 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.
#
sub preload_file_info {
local($count, $f_type, $f_uid, $f_gid, $f_mode, $f_name);
if (defined($opt_d)) {
printf("loading file info...\n");
}
if (-f "$opt_f.dir") {
$add_files_to_cache = 0;
dbmopen(files, $opt_f, 0644) ||
die sprintf("can't open DBM file '%s'", $opt_f);
} else {
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);
}
}
#
# Preload the known information. Reads data from a file, 1 entry per line,
# each entry is a CO that we "know" can be used.
#
sub preload_known_info {
local($file_name) = @_;
local($op, $value, $co);
open(FILE, $file_name) ||
die sprintf("kuang: can't open '%s'", $file_name);
known_loop:
while (<FILE>) {
chop;
if ((($op, $value) = &canonicalize($_)) == 2) {
$co = sprintf("%s %s", $op, $value);
$known{$co} = 1;
} else {
printf(stderr "kuang: invalid entry in known list: line %d '%s'.\n",
$.,
$_);
}
}
close(FILE);
}
\f
#
# Do various initialization type things.
#
sub init_kuang {
local($which, $name, $uid, $gid);
local($op, $value, $co);
#
# Deal with args...
#
&getopt($options) ||
die $usage;
if ($#ARGV == -1) {
push(@ARGV, "u root");
}
#
# Preload anything...
#
if (defined($opt_f)) {
&preload_file_info();
}
if (defined($opt_d)) {
printf("load passwd info...\n");
}
if (defined($opt_p)) {
if (defined($opt_Y)) {
printf(stderr "You can only specify one of -p or -P, not both.\n");
exit(1);
}
&load_passwd_info(0, $opt_p);
} elsif (defined($opt_Y)) {
&load_passwd_info(0);
} else {
&load_passwd_info(1);
}
if (defined($opt_d)) {
printf("load group info...\n");
}
if (defined($opt_g)) {
if (defined($opt_G)) {
printf(stderr "You can only specify one of -g or -G, not both.\n");
exit(1);
}
&load_group_info(0, $opt_g);
} elsif (defined($opt_G)) {
&load_group_info(0);
} else {
&load_group_info(1);
}
#
# 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...:-)
#
$uname2shell{"OTHER"} = "";
$uname2dir{"OTHER"} = "";
$uname2uid{"OTHER"} = -1;
$uid2names{-1} = "OTHER";
$known{"u -1"} = 1; # We can access uid OTHER
if (defined($opt_k)) {
&preload_known_info($opt_k);
}
#
# Create the target list from the remaining (non-option) args...
#
while ($#ARGV >= 0) {
$elt = pop(@ARGV);
if ((($op, $value) = &canonicalize($elt)) == 2) {
$co = sprintf("%s %s", $op, $value);
push(@targets, $co);
} else {
printf(stderr "target '%s' isn't of correct form\n", $elt);
}
}
}
\f
#
# Call this to set things up for a new target. Resets old, new, beendone
# and accessible.
#
sub set_target {
local($target) = @_;
@old = ();
@new = ();
%beendone = ();
@accessible = ();
# fixme: reset known?
if ($target =~ /^([ugrw]) ([^ \t]+)$/) {
&addto($1, $2);
return(0);
} else {
printf(stderr "kuang: bad target '%s'\n", $target);
return(1);
}
}
#
# Break a CO into an (operation, value) pair and return it. If it
# isn't in "operation value" form, return ().
#
sub breakup {
local($co) = @_;
local($operation, $value);
if ($co =~ /^([ugrw]) ([^ \t]+)$/) {
$operation = $1;
$value = $2;
} else {
printf(stderr "Yowza, breakup failed on '%s'\n",
$co);
exit(1);
}
return($operation, $value);
}
#
# 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, $tmp) = split(/ /, $files{$name});
} else {
$cache_miss++;
unless (-e $name) {
if ($add_files_to_cache) {
$files{$name} = "";
}
# ENOTDIR = 20
($! == 20) && print "Warning: Illegal Path: '$name'\n";
# EACCES = 13
($! == 13) && print "Warning: Permission Denied: '$name'\n";
# all values are returned "" here.
return;
}
($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
if ($add_files_to_cache) {
$files{$name} = join(' ', "$uid", "$gid", "$mode");
}
}
if (($mode & 020) != 020) {
$gid = "";
}
if (($mode & 02) == 02) {
$other = 1;
} else {
$other = 0;
}
return($uid, $gid, $other);
}
\f
sub ascii_plan {
local(@plan) = @_;
local($op, $value, $result);
for ($i = $#plan; $i >= 0; $i--) {
($op, $value) = &breakup($plan[$i]);
case:
{
if ($op eq "g") {
$op = "grant gid";
last case;
}
if ($op eq "u") {
$op = "grant uid";
last case;
}
if ($op eq "r") {
$op = "replace";
last case;
}
if ($op eq "w") {
$op = "write";
last case;
}
printf(stderr "Bad op '%s' in plan '%s'\n",
$op,
join(';', @plan));
last case;
}
$result .= "$op $value ";
}
return($result);
}
#
# Add a plan to the list of plans to check out.
#
sub addto {
local($op, $value, @plan) = @_;
local($co);
$co = sprintf("%s %s",
$op,
$value);
#
# See if the op and value is "uid root" - if so, and if the @plan
# isn't empty, then don't bother checking - if the target isn't root,
# its silly to pursue plans that require becoming root since if we can
# become root, we can become anything. If the target is root, then
# this would be a loop anyway.
#
if ($op eq "u" && $value eq "0" && $#plan >= 0) {
if (defined($opt_d)) {
printf("addto: aborted root plan '%s'\n",
&ascii_plan(@plan, $co));
}
return;
}
#
# See whether there's an entry for $co in the known list.
# If so - success, we've found a suitable breakin plan.
#
# Yes, we want to check to see whether the whole Controlling Operation
# is one that is known to us, rather than just the object. I
# might have a hole that allows me to "replace /bin/foo" which is
# somewhat different than "write /bin/foo"
#
if (! defined($opt_l) && defined($known{$co})) {
printf("Success! %s\n",
&ascii_plan(@plan, $co));
}
#
# Check for loops -- if the new CO is part of the plan that we're
# adding it to, this is a loop.
#
foreach $entry (@plan) {
if ($entry eq $co) {
if (defined($opt_d)) {
printf("addto: aborted loop in plan '%s'\n",
&ascii_plan(@plan, $co));
}
return;
}
}
#
# Add this CO to the plan array...
#
push(@plan, $co);
#
# Make an ascii version of sorts...
#
$text_plan = join($;, @plan);
#
# Check to see if the new plan has been done.
#
if (defined($beendone{$text_plan})) {
if (defined($opt_d)) {
printf("addto: plan's been done - '%s'\n",
&ascii_plan(@plan));
}
return;
}
#
# If we made it this far, its a new plan and isn't a loop.
#
#
# Add to the beendone list...
#
$beendone{$text_plan} = 1;
#
# Add to new plan list...
#
push(@new, $text_plan);
if (defined($opt_V)) {
printf("addto: %s\n",
&ascii_plan(@plan));
}
#
# If this is a uid goal, then add the plan to the accessible list.
#
if ($op eq "u" && $value ne "0" && defined($opt_l)) {
push(@accessible, $value);
}
}
#
#----------------------------------------------------------------------
#Main program follows...initialize and loop till we're done.
#
&init_kuang();
target_loop:
foreach $target (@targets) {
if (&set_target($target)) {
next target_loop;
}
while ($#new >= 0) {
@old = @new;
@new = ();
foreach $t_plan (@old) {
@plan = split(/\034/, $t_plan);
($op, $value) = &breakup($plan[$#plan]);
&apply_rules($op, $value, @plan);
}
}
if (defined($opt_l)) {
foreach $elt (@accessible) {
printf("$elt\n");
}
}
}
if (defined($opt_d)) {
printf("File info cache hit/access ratio: %g\n",
($cache_hit + $cache_miss > 0)
? $cache_hit / ($cache_hit + $cache_miss)
: 0.0);
}
1;