home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3526 < prev    next >
Encoding:
Internet Message Format  |  1991-06-23  |  59.6 KB

  1. From: df@sei.cmu.edu (Dan Farmer)
  2. Newsgroups: alt.sources
  3. Subject: perl COPS, 2/3
  4. Message-ID: <27544@as0c.sei.cmu.edu>
  5. Date: 22 Jun 91 04:29:00 GMT
  6.  
  7. #!/bin/sh
  8. # this is p-cops.103.02 (part 2 of a multipart archive)
  9. # do not concatenate these parts, unpack them in order with /bin/sh
  10. # file beta/group.chk continued
  11. #
  12. if test ! -r _shar_seq_.tmp; then
  13.     echo 'Please unpack part 1 first!'
  14.     exit 1
  15. fi
  16. (read Scheck
  17.  if test "$Scheck" != 2; then
  18.     echo Please unpack part "$Scheck" next!
  19.     exit 1
  20.  else
  21.     exit 0
  22.  fi
  23. ) < _shar_seq_.tmp || exit 1
  24. if test ! -f _shar_wnt_.tmp; then
  25.     echo 'x - still skipping beta/group.chk'
  26. else
  27. echo 'x - continuing file beta/group.chk'
  28. sed 's/^X//' << 'SHAR_EOF' >> 'beta/group.chk' &&
  29. package main;
  30. X
  31. die "Usage: $0\n" if @ARGV;
  32. X
  33. require 'pathconf.pl';
  34. X
  35. #   Used for Sun C2 security group file.  FALSE (default) will flag
  36. # valid C2 group syntax as an error, TRUE attempts to validate it.
  37. # Thanks to Pete Troxell for pointing this out.
  38. #
  39. # moved to cops.cf
  40. X
  41. package group_chk;
  42. X
  43. $etc_group = $'GROUP || '/etc/group';
  44. X
  45. # Testing $etc_group for potential problems....
  46. open (Group, "< $etc_group") || warn "$0: Can't open $etc_group: $!\n";
  47. &chk_group_file_format('Group');
  48. close Group;
  49. X
  50. # Testing ypcat group for potential problems
  51. $yp=0;
  52. if (-s $'YPCAT && -x _) {
  53. X    open(YGroup, "$'YPCAT group 2>/dev/null |")
  54. X    || die "$0: Can't popen $'YPCAT: $!\n";
  55. X    $yp=1;
  56. X    &chk_group_file_format('YGroup');
  57. X    close(YGroup);
  58. }
  59. X
  60. # usage: &chk_group_file_format('Filehandle-name');
  61. # skips over lines that begin with "+:"
  62. # It really should check for correct yellow pages syntax....
  63. #
  64. # this routine checks lines read from a filehandle for potential format
  65. # problems .. should be matching group(5)
  66. #
  67. # checks for duplicate users in a group as it reads the lines instead
  68. # of after (as the original shell script does)
  69. X
  70. sub chk_group_file_format {
  71. X    local($file) = @_;
  72. X    local($W) = "Warning!  $file file,";
  73. X    undef %groups;
  74. X
  75. X    while (<$file>) {
  76. X    # should really check for correct YP syntax
  77. X    next if /^[-+]:/;   # skipping YP lines for now
  78. X    print "$W line $., is blank\n" if /^\s*$/;
  79. X    ($group,$pass,$gid,$users) = split(?:?);
  80. X    $groups{$group}++;   # keep track of dups
  81. X    print "$W line $., does not have 4 fields:\n\t$_" if (@_ != 4);
  82. X    print "$W line $., nonalphanumeric group name:\n\t$_"
  83. X        if $group !~ /^[A-Za-z0-9-]+$/;
  84. X    if ($pass && $pass ne '*') {
  85. X        if ( ! $C2 || $yp ) {
  86. X        print "$W line $., group has password:\n\t$_"
  87. X            if length($pass) == 13;
  88. X        } else {
  89. X        print "$W line $., group has invalid field for C2:\n\t$_"
  90. X            if $pass ne "#\$$user";
  91. X        }
  92. X    }
  93. X    print "$W line $., nonnumeric group id: $_" if $_[2] !~ /^\d+$/;
  94. X
  95. X    # look for duplicate users in a group
  96. X    # kinda ugly, but it works .. and I have too much other work right
  97. X    # now to clean it up.  maybe later.. ;-)
  98. X    chop($users);    # down here, 'cos split gets rid of final null fields
  99. X    @users = sort split(/\s*,\s*/, $users);
  100. X    # %users = # of times user is in group, $dup_user = duplicate found
  101. X    undef %users;  $dup_user=0;
  102. X    grep(!($users{$_}++) && 0, @users);
  103. X    for (keys %users) {
  104. X        (print "Warning!  Group $group has duplicate user(s):\n"),
  105. X        $dup_user=1 if !$dup_user && $users{$_} > 1;
  106. X        print "$_ " if $users{$_} > 1;
  107. X    }
  108. X    print "\n" if $dup_user;
  109. X
  110. X    }
  111. X    # find duplicate group names 
  112. X    # not the best way, but it works..
  113. X    # boy, this is ugly too .. but, not as bad as above.. :)
  114. X    $dup_warned = 0;
  115. X    for (sort keys %groups) {
  116. X    (print "Warning!  Duplicate Group(s) found in $file:\n"), $dup_warned++
  117. X        if !$dup_warned && $groups{$_} > 1;
  118. X    print "$_ " if $groups{$_} > 1;
  119. X    }
  120. X    print "\n" if $dup_warned;
  121. }
  122. X
  123. 1;
  124. # end
  125. SHAR_EOF
  126. echo 'File beta/group.chk is complete' &&
  127. chmod 0700 beta/group.chk ||
  128. echo 'restore of beta/group.chk failed'
  129. Wc_c="`wc -c < 'beta/group.chk'`"
  130. test 4819 -eq "$Wc_c" ||
  131.     echo 'beta/group.chk: original size 4819, current size' "$Wc_c"
  132. rm -f _shar_wnt_.tmp
  133. fi
  134. # ============= beta/hostname.pl ==============
  135. if test -f 'beta/hostname.pl' -a X"$1" != X"-c"; then
  136.     echo 'x - skipping beta/hostname.pl (File already exists)'
  137.     rm -f _shar_wnt_.tmp
  138. else
  139. > _shar_wnt_.tmp
  140. echo 'x - extracting beta/hostname.pl (Text)'
  141. sed 's/^X//' << 'SHAR_EOF' > 'beta/hostname.pl' &&
  142. #
  143. # file: hostname.pl
  144. # usage: $hostname = &'hostname;
  145. #
  146. # purpose: get hostname -- try method until we get an answer 
  147. #    or return "Amnesiac!"
  148. #
  149. X
  150. package hostname;
  151. X
  152. sub main'hostname {
  153. X    if (!defined $hostname) {
  154. X    $hostname =  ( -x '/bin/hostname'   && `/bin/hostname` ) 
  155. X          || ( -x '/bin/uname'      && `/bin/uname -n` )
  156. X          || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`)
  157. X          || 'Amnesiac! ';  # trailing space is for chop
  158. X    chop $hostname;
  159. X    }
  160. X    $hostname;
  161. }
  162. X
  163. 1;
  164. SHAR_EOF
  165. chmod 0700 beta/hostname.pl ||
  166. echo 'restore of beta/hostname.pl failed'
  167. Wc_c="`wc -c < 'beta/hostname.pl'`"
  168. test 475 -eq "$Wc_c" ||
  169.     echo 'beta/hostname.pl: original size 475, current size' "$Wc_c"
  170. rm -f _shar_wnt_.tmp
  171. fi
  172. # ============= beta/is_able.chk ==============
  173. if test -f 'beta/is_able.chk' -a X"$1" != X"-c"; then
  174.     echo 'x - skipping beta/is_able.chk (File already exists)'
  175.     rm -f _shar_wnt_.tmp
  176. else
  177. > _shar_wnt_.tmp
  178. echo 'x - extracting beta/is_able.chk (Text)'
  179. sed 's/^X//' << 'SHAR_EOF' > 'beta/is_able.chk' &&
  180. #!/bin/sh -- need to mention perl here to avoid recursion
  181. 'true' || eval 'exec perl -S $0 $argv:q';
  182. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
  183. & eval 'exec /usr/bin/perl -S $0 $argv:q'
  184. X        if 0;
  185. X
  186. #
  187. #  is_able.chk
  188. #
  189. #   This shell script checks the permissions of all files and directories
  190. # listed in the configuration file "is_able.lst", and prints warning messages
  191. # according to the status of files.  You can specify world or group readability
  192. # or writeability.  See the config file for the format of the configuration
  193. # file.
  194. #
  195. #   Mechanism:  This shell script parses each line from the configure file
  196. # and uses the "is_able.pl" program to check if any of
  197. # the directories in question are writable by world/group.
  198. #
  199. X
  200. require 'is_able.pl';
  201. require 'file_mode.pl';
  202. require 'glob.pl';
  203. X
  204. if ($ARGV[0] eq '-d') {
  205. X    shift;
  206. X    $debug = $glob'debug = 1;  # maybe should turn off glob'debug afterwards
  207. }
  208. X
  209. unshift (@ARGV, "is_able.lst" ) unless @ARGV;
  210. X
  211. while (<>) {
  212. X    next if /^\s*#/;
  213. X    split;
  214. X    next unless @_ == 3;
  215. X    ($file, $x, $y) = @_;
  216. X    @files = $file =~ /[\[?*]/ ? &'glob($file) : ($file);
  217. X    for $file (@files) {
  218. X    print STDERR "is_able $file $x $y\n" if $debug;
  219. X    &'is_able($file, $x, $y);
  220. X    }
  221. }
  222. X
  223. 1;
  224. SHAR_EOF
  225. chmod 0700 beta/is_able.chk ||
  226. echo 'restore of beta/is_able.chk failed'
  227. Wc_c="`wc -c < 'beta/is_able.chk'`"
  228. test 1235 -eq "$Wc_c" ||
  229.     echo 'beta/is_able.chk: original size 1235, current size' "$Wc_c"
  230. rm -f _shar_wnt_.tmp
  231. fi
  232. # ============= beta/is_able.lst ==============
  233. if test -f 'beta/is_able.lst' -a X"$1" != X"-c"; then
  234.     echo 'x - skipping beta/is_able.lst (File already exists)'
  235.     rm -f _shar_wnt_.tmp
  236. else
  237. > _shar_wnt_.tmp
  238. echo 'x - extracting beta/is_able.lst (Text)'
  239. sed 's/^X//' << 'SHAR_EOF' > 'beta/is_able.lst' &&
  240. #  This lists any/all sensitive files the administration wants to ensure
  241. # non-read/writability of.  Comments are lines starting with a "#".
  242. #
  243. # USE FULL PATHNAMES!
  244. #
  245. #   Lines are of the format:
  246. #
  247. # /path/to/{dir|file}    World/Group    Read/Write/Both
  248. #
  249. # as above        {w|g}        {r|w|b}
  250. #
  251. /            w        w
  252. /etc            w        w
  253. /usr            w        w
  254. /bin            w        w
  255. /dev            w        w
  256. /usr/bin        w        w
  257. /usr/etc        w        w
  258. /usr/adm        w        w
  259. /usr/lib        w        w
  260. /usr/spool        w        w
  261. /usr/spool/mail        w        w
  262. /usr/spool/news        w        w
  263. /usr/spool/uucp        w        w
  264. /usr/spool/at        w        w
  265. /usr/local        w        w
  266. /usr/local/bin        w        w
  267. /usr/local/lib        w        w
  268. /usr/users        w        w
  269. /Mail            w        w
  270. X
  271. # some Un*x's put shadowpass stuff here:
  272. /etc/security        w        r
  273. X
  274. # /.login /.profile /.cshrc /.rhosts
  275. /.*            w        w
  276. X
  277. #   I think everything in /etc should be !world-writable, as a rule; but
  278. # if you're selecting individual files, do at *least* these:
  279. #   /etc/passwd /etc/group /etc/inittab /etc/rc /etc/rc.local /etc/rc.boot
  280. #   /etc/hosts.equiv /etc/profile /etc/syslog.conf /etc/export /etc/utmp
  281. #   /etc/wtmp
  282. /etc/*            w        w
  283. X
  284. /bin/*            w        w
  285. /usr/bin/*        w        w
  286. /usr/etc/*        w        w
  287. /usr/adm/*        w        w
  288. /usr/lib/*        w        w
  289. /usr/local/lib/*    w        w
  290. /usr/local/bin/*    w        w
  291. /usr/etc/yp*        w        w
  292. /usr/etc/yp/*        w        w
  293. X
  294. # individual files:
  295. /usr/lib/crontab    w        b
  296. /usr/lib/aliases    w        w
  297. /usr/lib/sendmail    w        w
  298. /usr/spool/uucp/L.sys    w        b
  299. X
  300. #  NEVER want these readable!
  301. /dev/kmem        w        b
  302. /dev/mem        w        b
  303. X
  304. #   Optional List of assorted files that shouldn't be
  305. # write/readable (mix 'n match; add to the list as desired):
  306. /usr/adm/sulog        w        r
  307. /.netrc            w        b
  308. # HP-UX and others:
  309. /etc/btmp        w        b
  310. /etc/securetty        w        b
  311. # Sun-fun
  312. /dev/drum        w        b
  313. /dev/nit        w        b
  314. SHAR_EOF
  315. chmod 0700 beta/is_able.lst ||
  316. echo 'restore of beta/is_able.lst failed'
  317. Wc_c="`wc -c < 'beta/is_able.lst'`"
  318. test 1603 -eq "$Wc_c" ||
  319.     echo 'beta/is_able.lst: original size 1603, current size' "$Wc_c"
  320. rm -f _shar_wnt_.tmp
  321. fi
  322. # ============= beta/kuang ==============
  323. if test -f 'beta/kuang' -a X"$1" != X"-c"; then
  324.     echo 'x - skipping beta/kuang (File already exists)'
  325.     rm -f _shar_wnt_.tmp
  326. else
  327. > _shar_wnt_.tmp
  328. echo 'x - extracting beta/kuang (Text)'
  329. sed 's/^X//' << 'SHAR_EOF' > 'beta/kuang' &&
  330. #!/bin/sh -- need to mention perl here to avoid recursion
  331. 'true' || eval 'exec perl -S $0 $argv:q';
  332. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
  333. & eval 'exec /usr/users/df/bin/perl.sun4 -S $0 $argv:q'
  334. X        if 0;
  335. # & eval 'exec /usr/local/bin/perl -S $0 $argv:q'
  336. #
  337. # kuang - rule based analysis of Unix security
  338. #
  339. # Perl version by Steve Romig of the CIS department, The Ohio State
  340. # University, October 1990. 
  341. # Based on the shell script version by Dan Farmer from his COPS
  342. # package, which in turn is based on a shell version by Robert
  343. # Baldwin. 
  344. #
  345. #-----------------------------------------------------------------------------
  346. # Players:
  347. #    romig    Steve Romig, romig@cis.ohio-state.edu
  348. #    tjt    Tim Tessin, tjt@cirrus.com
  349. #
  350. # History:
  351. # 4/25/91  tjt, romig    Various fixes to filewriters (better messages about 
  352. #            permission problems) and don't update the DBM cache 
  353. #            with local file info.
  354. # 11/1/90  romig    Major rewrite - generic lists, nuking get_entry 
  355. #            and put_entry, moved rules to separate file.
  356. #
  357. X
  358. #
  359. # Options
  360. #
  361. # -l        list uid's that can access the given target, directly
  362. #        or indirectly
  363. # -d        debug
  364. # -V         verbose
  365. #
  366. # -k file    load the list of known CO's
  367. # -f file    preload file information from the named file.
  368. # -p file    preload passwd info from the named file.
  369. # -Y        preload passwd info from ypcat + /etc/passwd
  370. # -g group    preload group info from the named file.
  371. # -G        preload group info from ypcat + /etc/group
  372. # NOTE:
  373. #   If you know where perl is and your system groks #!, put its
  374. # pathname at the top to make this a tad faster.
  375. #
  376. # the following magic is from the perl man page
  377. # and should work to get us to run with perl 
  378. # even if invoked as an sh or csh or foosh script.
  379. # notice we don't use full path cause we don't
  380. # know where the user has perl on their system.
  381. #
  382. X
  383. $options = "ldVk:p:g:f:YG";
  384. $usage = "usage: kuang [-l] [-d] [-v] [-k known] [-f file] [-Y] [-G] [-p passwd] [-g group] [u.username|g.groupname]\n";
  385. X
  386. $add_files_to_cache = 1;        # Whether to update the %files cache
  387. X                    # with local file info or not.
  388. X
  389. #
  390. # Terminology:
  391. #
  392. #   An "op" is an operation, such as uid, gid, write, or replace. 
  393. #   'uid' means to gain access to some uid, 'gid' means to gain access 
  394. #   to some gid.  'write' and 'replace' refer to files - replace means
  395. #   that we can delete a file and replace it with a new one somehow
  396. #   (for example, if we could write the directory it is in).
  397. #
  398. #   An object is a uid, gid or pathname.  
  399. #
  400. #   A Controlling Operation (CO) is a (operation, object) pair
  401. #   represented as "op object": "uid 216" (become uid 216) or "replace
  402. #   /.rhosts" (replace file /.rhosts).  These are represented
  403. #   internally as "c value", where "c" is a character representing an
  404. #   operation (u for uid, g for gid, r for replace, w for write) and
  405. #   value is a uid, gid or pathname.
  406. #
  407. #   A plan is a chain of CO's that are connected to each other.  If
  408. #   /.login were writeable by uid 216, we might have a plan such as:
  409. #
  410. #    uid 216 => write /.login => uid 0
  411. #
  412. #   which means (in English) "if we can become uid 216, then write 
  413. #   /.login which gives you access to uid 0 (when root next logs in)."
  414. #   Plans are represented in several ways: as arrays:
  415. #
  416. #    ("u 0", "w /.login", "u 216")
  417. #
  418. #   Note that the order is reversed.  As a string:
  419. #
  420. #    "u 0\034w /.login\034u 216"
  421. #
  422. #   The target is the object that we are trying to gain (a uid, gid or
  423. #   file, typically u.root or some other UID).
  424. #
  425. # Data Structures
  426. #
  427. #   %known        An assocc array, indexed by CO.  This lists
  428. #            the COs that we already have access to.  If we
  429. #                       find a plan that leads from a CO in the known
  430. #                       list to the target, we've succeeded in
  431. #                       finding a major security flaw.  
  432. #
  433. #   @new        An array of plans that are to be evaluated in
  434. #            the next cycle. 
  435. #
  436. #   @old        An array of plans that we are currently
  437. #            evaluating. 
  438. #
  439. #   %beendone        An assoc array that lists the plans that have
  440. #            already been tried.  Used to prevent loops.
  441. #
  442. #   @accessible        An array of the uids that can reach the
  443. #            target. 
  444. #
  445. #   %files        An assoc array, indexed by file name, contains
  446. #            cached file info.  value is of form "uid gid
  447. #            mode". 
  448. #
  449. # From pwgrid:
  450. #
  451. #   %uname2shell    Assoc array, indexed by user name, values are
  452. #            shells. 
  453. #
  454. #   %uname2dir        Assoc array, indexed by user name, values are
  455. #            home directories.
  456. #
  457. #   %uname2uid        Assoc array, indexed by name, values are uids.
  458. #            
  459. #   %uid2names        Assoc array, indexed by uid, value is list of
  460. #            user names with that uid, in form "name name
  461. #            name...". 
  462. #
  463. #   %gid2members    Assoc array, indexed by gid, value is list of
  464. #            group members (user names).
  465. #
  466. #   %gname2gid        Assoc array, indexed by group name, values are
  467. #            matching gids.
  468. #
  469. #   %gid2names        Assoc array, indexed by gid, values are
  470. #            matching group names.
  471. #
  472. X
  473. do 'yagrip.pl' ||
  474. X  die "can't do yagrip.pl";
  475. X
  476. # do 'pwgrid.pl' ||
  477. #   die "can't do pwgrid.pl";
  478. do 'pass.cache.pl' ||
  479. X  die "can't do pass.cache.pl";
  480. X
  481. do 'rules.pl' ||
  482. X  die "can't do rules.pl";
  483. X
  484. X
  485. #
  486. # Turns a string of the form "operation value" or "value" into
  487. # standard "CO" form ("operation value").  Converts user or group
  488. # names into corresponding uid and gid values. 
  489. #
  490. # Returns nothing if it isn't parseable.
  491. #
  492. X
  493. sub canonicalize {
  494. X    local($string) = @_;
  495. X    local($op, $value);
  496. X
  497. X    if ($string =~ /^([ugrw]) ([^ \t\n]+)$/) { # of form "op value"
  498. X    $op = $1;
  499. X    $value = $2;
  500. X    } elsif ($string =~ /^[^ \t\n]+$/) {       # of form "value"
  501. X        $value = $string;
  502. X    $op = "u";
  503. X    } else {
  504. X    return();
  505. X    }
  506. X
  507. X    if ($op eq "u" && $value =~ /^[^0-9]+$/) { # user name, not ID
  508. X        if (defined($uname2uid{$value})) {
  509. X        $value = $uname2uid{$value};
  510. X    } else {
  511. X        printf(stderr "There's no user named '%s'.\n", $value);
  512. X        return();
  513. X    }
  514. X    } elsif ($op eq "g" && $value =~/^[^0-9]+$/) {
  515. X    if (defined($gname2gid{$value})) {
  516. X        $value = $gname2gid{$value};
  517. X    } else {
  518. X        printf(stderr "There's no group named '%s'.\n", $value);
  519. X        return();
  520. X    }
  521. X    }
  522. X
  523. X    return($op, $value);
  524. }
  525. X
  526. X
  527. #
  528. # Preload file information from a text file or DBM database.  
  529. # If $opt_f.dir exists, then we just shadow %files from a DBM
  530. # database.  Otherwise, open the file and read the entries into 
  531. # %files.  
  532. #
  533. # $add_files_to_cache is set to 0 if we get the info from 
  534. # DBM since we wouldn't want to pollute update our DBM cache
  535. # with local file info which wouldn't apply to other hosts.
  536. #
  537. X
  538. sub preload_file_info {
  539. X    local($count, $f_type, $f_uid, $f_gid, $f_mode, $f_name);
  540. X
  541. X    if (defined($opt_d)) {
  542. X    printf("loading file info...\n");
  543. X    }
  544. X
  545. X    if (-f "$opt_f.dir") {
  546. X    $add_files_to_cache = 0;
  547. X
  548. X    dbmopen(files, $opt_f, 0644) ||
  549. X      die sprintf("can't open DBM file '%s'", $opt_f);
  550. X    } else {
  551. X    open(FILEDATA, $opt_f) || 
  552. X      die sprintf("kuang: can't open '%s'", $opt_f);
  553. X
  554. X    $count = 0;
  555. X    while (<FILEDATA>) {
  556. X        $count++;
  557. X
  558. X        chop;
  559. X        ($f_type, $f_uid, $f_gid, $f_mode, $f_name) = split;
  560. X        
  561. X        if ($count % 1000 == 0) {
  562. X        printf("line $count, reading entry for $f_name\n");
  563. X        }
  564. X        $files{$f_name} = join(' ', $f_uid, $f_gid, $f_mode);
  565. X    }
  566. X
  567. X    close(FILEDATA);
  568. X    }
  569. }
  570. X
  571. #
  572. # Preload the known information.  Reads data from a file, 1 entry per line,
  573. # each entry is a CO that we "know" can be used.
  574. #
  575. X
  576. sub preload_known_info {
  577. X    local($file_name) = @_;
  578. X    local($op, $value, $co);
  579. X
  580. X    open(FILE, $file_name) ||
  581. X      die sprintf("kuang: can't open '%s'", $file_name);
  582. X
  583. X  known_loop:
  584. X    while (<FILE>) {
  585. X    chop;
  586. X    if ((($op, $value) = &canonicalize($_)) == 2) {
  587. X        $co = sprintf("%s %s", $op, $value);
  588. X        $known{$co} = 1;
  589. X    } else {
  590. X        printf(stderr "kuang: invalid entry in known list: line %d '%s'.\n",
  591. X           $.,
  592. X           $_);
  593. X    }
  594. X    }
  595. X
  596. X    close(FILE);
  597. }
  598. X    
  599. X
  600. #
  601. # Do various initialization type things.
  602. #
  603. X
  604. sub init_kuang {
  605. X    local($which, $name, $uid, $gid);
  606. X    local($op, $value, $co);
  607. X
  608. X    #
  609. X    # Deal with args...
  610. X    #
  611. X
  612. X    &getopt($options) ||
  613. X      die $usage;
  614. X
  615. X    if ($#ARGV == -1) {
  616. X    push(@ARGV, "u root");
  617. X    }
  618. X
  619. X    #
  620. X    # Preload anything...
  621. X    #
  622. X    if (defined($opt_f)) {
  623. X    &preload_file_info();
  624. X    }
  625. X
  626. X    if (defined($opt_d)) {
  627. X    printf("load passwd info...\n");
  628. X    }
  629. X
  630. X    if (defined($opt_p)) {
  631. X    if (defined($opt_Y)) {
  632. X        printf(stderr "You can only specify one of -p or -P, not both.\n");
  633. X        exit(1);
  634. X    }
  635. X
  636. X    &load_passwd_info(0, $opt_p);
  637. X    } elsif (defined($opt_Y)) {
  638. X    &load_passwd_info(0);
  639. X    } else {
  640. X    &load_passwd_info(1);
  641. X    }
  642. X
  643. X    if (defined($opt_d)) {
  644. X    printf("load group info...\n");
  645. X    }
  646. X
  647. X    if (defined($opt_g)) {
  648. X    if (defined($opt_G)) {
  649. X        printf(stderr "You can only specify one of -g or -G, not both.\n");
  650. X        exit(1);
  651. X    }
  652. X
  653. X    &load_group_info(0, $opt_g);
  654. X    } elsif (defined($opt_G)) {
  655. X    &load_group_info(0);
  656. X    } else {
  657. X    &load_group_info(1);
  658. X    }
  659. X
  660. X    #
  661. X    # Need some of the password and group stuff.  Suck in passwd and 
  662. X    # group info, store by uid and gid in an associative array of strings
  663. X    # which consist of fields corresponding to the passwd and group file 
  664. X    # entries (and what the heck, we'll use : as a delimiter also...:-)
  665. X    #
  666. X    $uname2shell{"OTHER"} = "";
  667. X    $uname2dir{"OTHER"} = "";
  668. X    $uname2uid{"OTHER"} = -1;
  669. X    $uid2names{-1} = "OTHER";
  670. X
  671. X    $known{"u -1"} = 1;        # We can access uid OTHER
  672. X
  673. X    if (defined($opt_k)) {
  674. X    &preload_known_info($opt_k);
  675. X    }
  676. X
  677. X    #
  678. X    # Create the target list from the remaining (non-option) args...
  679. X    #
  680. X    while ($#ARGV >= 0) {
  681. X    $elt = pop(@ARGV);
  682. X    if ((($op, $value) = &canonicalize($elt)) == 2) {
  683. X        $co = sprintf("%s %s", $op, $value);
  684. X        push(@targets, $co);
  685. X    } else {
  686. X        printf(stderr "target '%s' isn't of correct form\n", $elt);
  687. X    }
  688. X    }
  689. }
  690. X
  691. X
  692. #
  693. # Call this to set things up for a new target.  Resets old, new, beendone 
  694. # and accessible.  
  695. #
  696. sub set_target {
  697. X    local($target) = @_;
  698. X
  699. X    @old = ();
  700. X    @new = ();
  701. X    %beendone = ();
  702. X    @accessible = ();
  703. # fixme: reset known?
  704. X
  705. X    if ($target =~ /^([ugrw]) ([^ \t]+)$/) {
  706. X    &addto($1, $2);
  707. X    return(0);
  708. X    } else {
  709. X    printf(stderr "kuang: bad target '%s'\n", $target);
  710. X    return(1);
  711. X    }
  712. }
  713. X
  714. #
  715. # Break a CO into an (operation, value) pair and return it.  If it
  716. # isn't in "operation value" form, return ().
  717. #
  718. sub breakup {
  719. X    local($co) = @_;
  720. X    local($operation, $value);
  721. X
  722. X    if ($co =~ /^([ugrw]) ([^ \t]+)$/) {
  723. X    $operation = $1;
  724. X    $value = $2;
  725. X    } else {
  726. X    printf(stderr "Yowza, breakup failed on '%s'\n",
  727. X        $co);
  728. X    exit(1);
  729. X    }
  730. X
  731. X    return($operation, $value);
  732. }
  733. X
  734. #
  735. # Get the writers of the named file - return as (UID, GID, OTHER)
  736. # triplet.  Owner can always write, since he can chmod the file if he
  737. # wants. 
  738. #
  739. # (fixme) are there any problems in this sort of builtin rule?  should
  740. # we make this knowledge more explicit?
  741. #
  742. sub filewriters {
  743. X    local($name) = @_;
  744. X    local($tmp, $mode, $uid, $gid, $other);
  745. X    
  746. X    #
  747. X    # Check the file cache - avoid disk lookups for performance and 
  748. X    # to avoid shadows...
  749. X    #
  750. X    if (defined($files{$name})) {
  751. X    $cache_hit++;
  752. X    
  753. X    ($uid, $gid, $mode, $tmp) = split(/ /, $files{$name});
  754. X    } else {
  755. X    $cache_miss++;
  756. X
  757. X    unless (-e $name) {
  758. X        if ($add_files_to_cache) {
  759. X        $files{$name} = "";
  760. X        }
  761. X        # ENOTDIR = 20 
  762. X        ($! == 20) && print "Warning: Illegal Path: '$name'\n";
  763. X        # EACCES = 13
  764. X        ($! == 13) && print "Warning: Permission Denied: '$name'\n";
  765. X        # all values are returned "" here.
  766. X        return;
  767. X    }
  768. X
  769. X    ($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
  770. X    if ($add_files_to_cache) {
  771. X        $files{$name} = join(' ', "$uid", "$gid", "$mode");
  772. X    }
  773. X    }
  774. X
  775. X    if (($mode & 020) != 020) {
  776. X    $gid = "";
  777. X    }
  778. X    
  779. X    if (($mode & 02) == 02) {
  780. X    $other = 1;
  781. X    } else {
  782. X    $other = 0;
  783. X    }
  784. X
  785. X    return($uid, $gid, $other);
  786. }
  787. X
  788. X
  789. sub ascii_plan {
  790. X    local(@plan) = @_;
  791. X    local($op, $value, $result);
  792. X
  793. X    for ($i = $#plan; $i >= 0; $i--) {
  794. X    ($op, $value) = &breakup($plan[$i]);
  795. X
  796. X      case: 
  797. X    {
  798. X        if ($op eq "g") {
  799. X        $op = "grant gid";
  800. X        last case;
  801. X        }
  802. X
  803. X        if ($op eq "u") {
  804. X        $op = "grant uid";
  805. X        last case;
  806. X        }
  807. X
  808. X        if ($op eq "r") {
  809. X        $op = "replace";
  810. X        last case;
  811. X        }
  812. X
  813. X        if ($op eq "w") {
  814. X        $op = "write";
  815. X        last case;
  816. X        }
  817. X
  818. X        printf(stderr "Bad op '%s' in plan '%s'\n",
  819. X           $op,
  820. X           join(';', @plan));
  821. X        last case;
  822. X    }
  823. X
  824. X    $result .= "$op $value ";
  825. X    }
  826. X
  827. X    return($result);
  828. }
  829. X
  830. #
  831. # Add a plan to the list of plans to check out.
  832. #
  833. sub addto {
  834. X    local($op, $value, @plan) = @_;
  835. X    local($co);
  836. X
  837. X    $co = sprintf("%s %s",
  838. X          $op,
  839. X          $value);
  840. X
  841. X    #
  842. X    # See if the op and value is "uid root" - if so, and if the @plan 
  843. X    # isn't empty, then don't bother checking - if the target isn't root, 
  844. X    # its silly to pursue plans that require becoming root since if we can 
  845. X    # become root, we can become anything.  If the target is root, then 
  846. X    # this would be a loop anyway.
  847. X    #
  848. X    if ($op eq "u" && $value eq "0" && $#plan >= 0) {
  849. X    if (defined($opt_d)) {
  850. X        printf("addto: aborted root plan '%s'\n",
  851. X           &ascii_plan(@plan, $co));
  852. X    }
  853. X    return;
  854. X    }
  855. X
  856. X    #
  857. X    # See whether there's an entry for $co in the known list.
  858. X    # If so - success, we've found a suitable breakin plan.
  859. X    #
  860. X    # Yes, we want to check to see whether the whole Controlling Operation 
  861. X    # is one that is known to us, rather than just the object.  I
  862. X    # might have a hole that allows me to "replace /bin/foo" which is
  863. X    # somewhat different than "write /bin/foo"  
  864. X    #
  865. X    if (! defined($opt_l) && defined($known{$co})) {
  866. X    printf("Success! %s\n",
  867. X           &ascii_plan(@plan, $co));
  868. X    }
  869. X
  870. X    #
  871. X    # Check for loops -- if the new CO is part of the plan that we're
  872. X    # adding it to, this is a loop.
  873. X    #
  874. X    foreach $entry (@plan) {
  875. X    if ($entry eq $co) {
  876. X        if (defined($opt_d)) {
  877. X        printf("addto: aborted loop in plan '%s'\n",
  878. X               &ascii_plan(@plan, $co));
  879. X        }
  880. X        return;
  881. X    }
  882. X    }
  883. X
  884. X    #
  885. X    # Add this CO to the plan array...
  886. X    #
  887. X    push(@plan, $co);
  888. X
  889. X    #
  890. X    # Make an ascii version of sorts...
  891. X    #
  892. X    $text_plan = join($;, @plan);
  893. X
  894. X    #
  895. X    # Check to see if the new plan has been done.
  896. X    #
  897. X    if (defined($beendone{$text_plan})) {
  898. X    if (defined($opt_d)) {
  899. X        printf("addto: plan's been done - '%s'\n",
  900. X           &ascii_plan(@plan));
  901. X    }
  902. X    return;
  903. X    }
  904. X
  905. X    #
  906. X    # If we made it this far, its a new plan and isn't a loop.  
  907. X    #
  908. X
  909. X    #
  910. X    # Add to the beendone list...
  911. X    #
  912. X    $beendone{$text_plan} = 1;
  913. X
  914. X    #
  915. X    # Add to new plan list...
  916. X    #
  917. X    push(@new, $text_plan);
  918. X
  919. X    if (defined($opt_V)) {
  920. X    printf("addto: %s\n", 
  921. X           &ascii_plan(@plan));
  922. X    }
  923. X
  924. X    #
  925. X    # If this is a uid goal, then add the plan to the accessible list.
  926. X    #
  927. X    if ($op eq "u" && $value ne "0" && defined($opt_l)) {
  928. X    push(@accessible, $value);
  929. X    }
  930. }
  931. X
  932. #
  933. #----------------------------------------------------------------------
  934. #Main program follows...initialize and loop till we're done.
  935. #
  936. X
  937. &init_kuang();
  938. X
  939. target_loop:
  940. foreach $target (@targets) {
  941. X    if (&set_target($target)) {
  942. X    next target_loop;
  943. X    }
  944. X
  945. X    while ($#new >= 0) {
  946. X    @old = @new;
  947. X    @new = ();
  948. X
  949. X    foreach $t_plan (@old) {
  950. X        @plan = split(/\034/, $t_plan);
  951. X        ($op, $value) = &breakup($plan[$#plan]);
  952. X
  953. X        &apply_rules($op, $value, @plan);
  954. X    }
  955. X    }
  956. X
  957. X    if (defined($opt_l)) {
  958. X    foreach $elt (@accessible) {
  959. X        printf("$elt\n");
  960. X    }
  961. X    }
  962. }
  963. X
  964. if (defined($opt_d)) {
  965. X    printf("File info cache hit/access ratio: %g\n", 
  966. X           ($cache_hit + $cache_miss > 0) 
  967. X            ? $cache_hit / ($cache_hit + $cache_miss)
  968. X            : 0.0);
  969. }
  970. X
  971. 1;
  972. SHAR_EOF
  973. chmod 0700 beta/kuang ||
  974. echo 'restore of beta/kuang failed'
  975. Wc_c="`wc -c < 'beta/kuang'`"
  976. test 15363 -eq "$Wc_c" ||
  977.     echo 'beta/kuang: original size 15363, current size' "$Wc_c"
  978. rm -f _shar_wnt_.tmp
  979. fi
  980. # ============= beta/is_able.pl ==============
  981. if test -f 'beta/is_able.pl' -a X"$1" != X"-c"; then
  982.     echo 'x - skipping beta/is_able.pl (File already exists)'
  983.     rm -f _shar_wnt_.tmp
  984. else
  985. > _shar_wnt_.tmp
  986. echo 'x - extracting beta/is_able.pl (Text)'
  987. sed 's/^X//' << 'SHAR_EOF' > 'beta/is_able.pl' &&
  988. #
  989. #  (This takes the place of the C program is_able.c, BTW.)
  990. #  is_able filename {w|g|s|S}       {r|w|B|b|s}
  991. #      (world/group/SUID/SGID   read/write/{read&write}/{suid&write}/s[ug]id)
  992. #     The second arg of {r|w} determines whether a file is (group or world
  993. #   depending on the first arg of {w|g}) writable/readable, or if it is
  994. #   SUID/SGID (first arg, either s or S, respectively), and prints out a
  995. #   short message to that effect.
  996. #  So:
  997. #     is_able w w        # checks if world writable
  998. #     is_able g r        # checks if group readable
  999. #     is_able s s        # checks if SUID
  1000. #     is_able S b        # checks if world writable and SGID
  1001. X
  1002. package main;
  1003. require 'file_mode.pl';
  1004. X
  1005. package is_able;
  1006. X
  1007. # package statics
  1008. #
  1009. %wg = ( 
  1010. X    'w', 00006,
  1011. X    'g', 00060,
  1012. X    's', 04000,
  1013. X    'S', 02000,
  1014. X       );
  1015. X
  1016. %rwb= (
  1017. X    'r', 00044,
  1018. X    'w', 00022,
  1019. X    'B', 00066,
  1020. X    'b', 04022,
  1021. X    's', 06000,
  1022. X      );
  1023. X
  1024. $silent = 0;  # for suppressing diagnostic messages
  1025. X
  1026. X
  1027. sub main'is_able {
  1028. X    local($file, $wg, $rwb) = @_;
  1029. X
  1030. X    local ( 
  1031. X       $mode,             # file mode
  1032. X           $piece,            # 1 directory component
  1033. X       @pieces,             # all the pieces
  1034. X       @dirs,             # all the directories
  1035. X       $p,                 # punctuation; (*) mean writable
  1036. X                       #       due to writable parent
  1037. X       $retval,            # true if vulnerable
  1038. X       $[                # paranoia
  1039. X      );
  1040. X
  1041. X    &usage, return undef    if @_ != 3 || $file eq '';
  1042. X
  1043. X    &usage, return undef    unless defined $wg{$wg} && defined $rwb{$rwb};
  1044. X
  1045. X    if (&'Mode($file) eq 'BOGUS' && $noisy) {
  1046. X    warn "is_able: can't stat $file: $!\n";
  1047. X    return undef;
  1048. X    }
  1049. X
  1050. X    $retval = 0;
  1051. X
  1052. X    if ($rwb{$rwb} & $rwb{'w'}) {
  1053. X    @pieces = split(m#/#, $file);
  1054. X    for ($i = 1; $i <= $#pieces; $i++) {
  1055. X        push(@dirs, join('/', @pieces[0..$i]));
  1056. X    }
  1057. X    } else {
  1058. X    @dirs = ( $file );
  1059. X    } 
  1060. X
  1061. X    for $piece ( reverse @dirs ) {
  1062. X
  1063. X    next unless $mode = &'Mode($piece);
  1064. X    next if $mode eq 'BOGUS';
  1065. X
  1066. X    next unless $mode &= 07777 & $wg{$wg} & $rwb{$rwb};
  1067. X
  1068. X    $retval = 1;
  1069. X
  1070. X    $p = $piece eq $file ? '!' : '! (*)';
  1071. X
  1072. X    $parent_is_writable = $p eq '! (*)'; # for later
  1073. X
  1074. X    next if $silent; # for &is_writable
  1075. X
  1076. X    print "Warning!  $file is group readable$p\n"    if $mode & 00040; 
  1077. X    print "Warning!  $file is _World_ readable$p\n"    if $mode & 00004; 
  1078. X    print "Warning!  $file is group writable$p\n"    if $mode & 00020; 
  1079. X    print "Warning!  $file is _World_ writable$p\n"    if $mode & 00002; 
  1080. X    print "Warning!  $file is SUID!\n"        if $mode & 04000; 
  1081. X    print "Warning!  $file is SGID!\n"        if $mode & 02000; 
  1082. X
  1083. X    last if $piece ne $file;  # only complain on first writable parent
  1084. X    }
  1085. X    $retval;
  1086. }
  1087. X
  1088. sub main'is_writable {
  1089. X    local($silent) = 1;
  1090. X    &'is_able($_[0], 'w', 'w') 
  1091. X    ? $parent_is_writable 
  1092. X         ? "writable (*)"
  1093. X         : "writable" 
  1094. X    : 0;
  1095. X
  1096. sub main'is_readable {
  1097. X    local($silent) = 1;
  1098. X    &'is_able($_[0], 'w', 'r');
  1099. }
  1100. X
  1101. sub usage { 
  1102. X    warn <<EOF;
  1103. Usage: is_able file {w|g|S|s} {r|w|B|b|s}
  1104. X (not: is_able @_)
  1105. EOF
  1106. }
  1107. X
  1108. 1;
  1109. SHAR_EOF
  1110. chmod 0700 beta/is_able.pl ||
  1111. echo 'restore of beta/is_able.pl failed'
  1112. Wc_c="`wc -c < 'beta/is_able.pl'`"
  1113. test 2835 -eq "$Wc_c" ||
  1114.     echo 'beta/is_able.pl: original size 2835, current size' "$Wc_c"
  1115. rm -f _shar_wnt_.tmp
  1116. fi
  1117. # ============= beta/kuang.1 ==============
  1118. if test -f 'beta/kuang.1' -a X"$1" != X"-c"; then
  1119.     echo 'x - skipping beta/kuang.1 (File already exists)'
  1120.     rm -f _shar_wnt_.tmp
  1121. else
  1122. > _shar_wnt_.tmp
  1123. echo 'x - extracting beta/kuang.1 (Text)'
  1124. sed 's/^X//' << 'SHAR_EOF' > 'beta/kuang.1' &&
  1125. .TH KUANG 1 "4 October 1990"
  1126. .SH NAME
  1127. kuang \- find security problems through rule based analysis
  1128. .SH SYNOPSIS
  1129. .B kuang
  1130. .RB "[\|" \-v  "\|]"
  1131. .RB "[\|" \-d "\|]"
  1132. .RB "[\|" \-l "\|]"
  1133. .RB "[\|" \-k known"\|]"
  1134. .RB "[\|" \-f filedata "\|]"
  1135. .RB "[\|" \-P "\|]"
  1136. .RB "[\|" \-G "\|]"
  1137. .RB "[\|" \-p passwd "\|]"
  1138. .RB "[\|" \-g group "\|]"
  1139. .RB "[\|" 
  1140. .IR u.username | g.groupname "\|]"
  1141. .br
  1142. .SH DESCRIPTION
  1143. .LP
  1144. .B kuang
  1145. uses rule based analysis to examine the current security configuration
  1146. of a site and determine whether certain security problems exist.
  1147. X
  1148. .B kuang 
  1149. contains embedded rules that describe the projection model and
  1150. some of the attacker tricks used on Unix systems.  It uses these rules
  1151. to reason backward from a desired goal (such as "grant u.root"),
  1152. generating potential "attack" plans from the rules and file system
  1153. state and then evaluating them to see whether they are reachable
  1154. according to the state recorded in the password and group files and in
  1155. the ownership and modes of the file systems.
  1156. X
  1157. By default, 
  1158. .B kuang 
  1159. uses "grant u.root" as its initial goal.  You can change that by
  1160. specifying a username (u.username) or groupname (g.groupname) on the
  1161. command line.  Normally 
  1162. .B kuang
  1163. determines a plan to be successful if it determines that anyone
  1164. (u.other) can become the initial goal.  
  1165. X
  1166. The 
  1167. .B \-v
  1168. option causes 
  1169. .B kuang
  1170. to print a message about every plan added to the evaluation list.
  1171. This can help one to understand how 
  1172. .B kuang 
  1173. works.  The 
  1174. .B \-d 
  1175. option causes 
  1176. .B kuang
  1177. to print a message when it evaluates a plan to determine whether to
  1178. retain it and add onto it or ignore it.  Beware - these options will often
  1179. produce lots of output.
  1180. X
  1181. Normally 
  1182. .B kuang
  1183. only registers success when it finds that everyone on the system can
  1184. become the target uid or gid.  With the 
  1185. .B \-l
  1186. option, 
  1187. .B kuang
  1188. will list every uid that can access the goal.  This provides a more
  1189. complete picture of the state of security - you might deem it a
  1190. problem if several users can become root, even if u.other cannot.  
  1191. With the
  1192. .B \-k
  1193. option, it reads users that are known to be compromised (guessed
  1194. password, writeable startup files, or whatever) into a file, like:
  1195. X
  1196. u 216
  1197. u romig
  1198. df
  1199. g staff
  1200. X
  1201. and so on.  Then start kuang as "kuang -k known".  If you omit the u
  1202. or g, it defaults to uid.  You can give names or IDs for uids and
  1203. groups.  You can also list files.  This gets put on a list that is 
  1204. used to decide whether a plan is successful or not.  If a plan 
  1205. reaches an step that is in the known list, it succeeds.
  1206. X
  1207. One might adopt the view that each uid should only be accessible by
  1208. itself and root, and that each gid should be accessible only by the
  1209. members of that group and root.  One can then compare the expected
  1210. access list for a given uid or gid against the 
  1211. .B kuang
  1212. generated list to find security problems that 
  1213. .B kuang
  1214. wouldn't ordinarily tell you about.
  1215. X
  1216. The goals that 
  1217. .B kuang
  1218. use seem cryptic, but are really pretty straightforward.  Each goal
  1219. consists of a list of <action> <object> pairs.  Typical actions are
  1220. user, group, write and replace.  Typical objects are user names,
  1221. group names and file names.  The goal
  1222. "user root" (or u.root) means to have access to the root UID (0), or
  1223. in other words, to be able to run any program using that uid.  
  1224. Similarly,
  1225. "group staff" (or g.staff) means to have access to group staff.
  1226. The long goal
  1227. "user bill  group graphics replace /n/shoe/0/fred replace
  1228. /n/shoe/0/fred/.profile user fred group staff" means become
  1229. user bill, get access to the graphics group, replace the file
  1230. /n/shoe/0/fred, replace /n/shoe/0/fred/.profile, become fred,
  1231. grant access to the staff group.  The problem that allows this to
  1232. happen is that the /n/shoe/0 directory is writeable by the graphics
  1233. group, meaning that anyone in that group can replace the .profile file
  1234. for the fred user and gain access to that account and the groups it
  1235. belongs to when fred next logs in.  Ooops.
  1236. X
  1237. To do a thorough job, 
  1238. .B kuang 
  1239. really needs to be able to access all of
  1240. the controlling files of all users.  In some environments, home
  1241. directories are located in NFS mounted file systems where the client
  1242. doesn't have root access.  
  1243. X
  1244. The problem is that some home directories may be
  1245. protected so that group foo can read/write them, but OTHER can't.
  1246. .B kuang 
  1247. running as some user not in group foo won't be able to read or
  1248. search the directory, creating a blind spot that may hide security
  1249. problems (for example, if group foo can write that user's .login and
  1250. gain access to some other important priv...)  Running 
  1251. .B kuang
  1252. as root
  1253. won't help unless we are running on the server that exports that
  1254. file system, since root==nobody through NFS here.  Of course, then
  1255. you'll find other blind spots on other servers, meaning that you'll
  1256. never be able to see a complete picture of how things are from any
  1257. spot on the net.  Running 
  1258. .B kuang
  1259. on every machine might not even
  1260. help, since the blind spots might prevent them from seeing viable
  1261. paths to Success on any of the machines.  Sigh.
  1262. X
  1263. Soooo we've added a 
  1264. .B -f 
  1265. option that causes 
  1266. .B kuang 
  1267. to preload owner, group and mode information for a list of files.
  1268. Each line of the file should be of the form "type uid gid mode name".
  1269. .B type
  1270. is ignored by 
  1271. .B kuang.
  1272. .B uid 
  1273. and 
  1274. .B gid
  1275. are the user and group ID numbers, in decimal.
  1276. .B mode
  1277. is the permissions for the file, in octal.  And 
  1278. .B name
  1279. is the name of the file.  We've also added a program called
  1280. .B get-cf
  1281. that can be run as root on a server to create a file of the above form
  1282. for the control files for the user's with home directories on that
  1283. server.  Then you can run 
  1284. .B get-cf 
  1285. on every server as root, concatenate all the data together, and
  1286. preload it into Perl.  This will fix the shadow problems mentioned
  1287. above and should also speed things up since you won't need to do all
  1288. the file system references.
  1289. .B kuang -f file
  1290. will use a DBM database in place of a text file if file.dir exists.
  1291. X
  1292. .B Kuang
  1293. needs to read the entire password and group databases before it
  1294. starts, so that it has a complete idea of what users are in what groups
  1295. and so on.  This can be somewhat slow on systems using YP, since by
  1296. default 
  1297. .B kuang
  1298. uses the getpwent and getgrent routines to get the information (which
  1299. is tedious on a YP client).  
  1300. The 
  1301. .B -P
  1302. and 
  1303. .B -G
  1304. options cause 
  1305. .B kuang
  1306. to read /etc/passwd (/etc/group) and to use ypcat to read the rest of
  1307. the passwd (group) YP maps, which can be much faster.  In addition, 
  1308. the 
  1309. .B -p 
  1310. and 
  1311. .B -g 
  1312. options cause 
  1313. .B kuang 
  1314. to read the named files instead of /etc/passwd.
  1315. X
  1316. .SH "SEE ALSO"
  1317. "Rule Based Analysis of Computer Security", Robert W. Baldwin, MIT,
  1318. June 1987.
  1319. X
  1320. The README file that comes with 
  1321. .B kuang
  1322. describes many of the design considerations, problems and future
  1323. plans.
  1324. X
  1325. .SH NOTES
  1326. .LP
  1327. This version of 
  1328. .B kuang
  1329. is based on the shell script versions that Dan Farmer included with
  1330. the 
  1331. .B COPS 
  1332. security package, which in turn were based on code written by  Robert
  1333. Baldwin himself.
  1334. X
  1335. You should read the other documentation that should come with this
  1336. version and modify the rules in 
  1337. .B kuang
  1338. to suite your site.
  1339. X
  1340. .SH BUGS
  1341. .LP
  1342. Probably many.
  1343. X
  1344. The 
  1345. .B -P
  1346. and 
  1347. .B -G 
  1348. options don't work right if you use +@ constructions with YP.  They do
  1349. work right if you use a simple "+:" entry, however.
  1350. X
  1351. SHAR_EOF
  1352. chmod 0600 beta/kuang.1 ||
  1353. echo 'restore of beta/kuang.1 failed'
  1354. Wc_c="`wc -c < 'beta/kuang.1'`"
  1355. test 7253 -eq "$Wc_c" ||
  1356.     echo 'beta/kuang.1: original size 7253, current size' "$Wc_c"
  1357. rm -f _shar_wnt_.tmp
  1358. fi
  1359. # ============= beta/misc.chk ==============
  1360. if test -f 'beta/misc.chk' -a X"$1" != X"-c"; then
  1361.     echo 'x - skipping beta/misc.chk (File already exists)'
  1362.     rm -f _shar_wnt_.tmp
  1363. else
  1364. > _shar_wnt_.tmp
  1365. echo 'x - extracting beta/misc.chk (Text)'
  1366. sed 's/^X//' << 'SHAR_EOF' > 'beta/misc.chk' &&
  1367. #!/bin/sh -- need to mention perl here to avoid recursion
  1368. 'true' || eval 'exec perl -S $0 $argv:q';
  1369. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
  1370. & eval 'exec /usr/bin/perl -S $0 $argv:q'
  1371. X        if 0;
  1372. X
  1373. #
  1374. #  Usage: misc.chk.pl [-d]
  1375. #
  1376. # composer@chem.bu.edu
  1377. # based on original shell script
  1378. #
  1379. #  This shell script checks a variety of miscellaneous potential
  1380. # security problems that really don't belong anywhere else.
  1381. #
  1382. #  Right now this looks for to see if tftp & rexd are enabled,
  1383. # to check if the uudecode alias is in the mail alias file and
  1384. # not commented out, and if uudecode can create a SUID file.
  1385. #
  1386. #  Mechanism:  tftp.chk will try to get /etc/motd from the localhost.
  1387. # Not much too it; just connect and try to get it.  For rexd, just
  1388. # look in the /etc/inetd.conf file to see if it's enabled (e.g., not
  1389. # commented out).
  1390. #
  1391. #  Warning:  it may take a minute or so to complete the test, since tftp
  1392. # might take a while to get the test file, or it may take a while to time
  1393. # out the connection (which is what usually happens if the test fails.)
  1394. X
  1395. package main;
  1396. require 'chk_strings.pl';
  1397. require 'fgrep.pl';
  1398. require 'hostname.pl';
  1399. X
  1400. if ($ARGV[0] eq '-d') {
  1401. X    #$chk_strings'debug = 1;  # verbose debugging
  1402. X    $misc_chk'debug = 1;
  1403. X    shift;
  1404. }
  1405. X
  1406. die "Usage: $0 [-d]\n" if @ARGV > 0;
  1407. X
  1408. X
  1409. $TFTP="/usr/ucb/tftp" unless defined $TFTP;
  1410. $UUDECODE="/usr/bin/uudecode" unless defined $UUDECODE; 
  1411. X
  1412. package misc_chk;
  1413. X
  1414. # look for uudecode alias in $aliases
  1415. #$aliases="/usr/lib/aliases" if -f "/usr/lib/aliases";
  1416. $aliases = ( -f '/usr/lib/aliases' && '/usr/lib/aliases' )
  1417. X    || ( -f '/etc/aliases'       && '/etc/aliases' )
  1418. X    || 'BOGUS';
  1419. $uu="decode";
  1420. X
  1421. # look for rexd in $inetd; this file could be "/etc/servers", too!
  1422. if (!defined($inetd)) {
  1423. X    $inetd = ( -f '/etc/inetd.conf' && '/etc/inetd.conf') ||
  1424. X         ( -f '/etc/servers' && '/etc/servers') ||
  1425. X         'BOGUS';
  1426. X    }
  1427. $rexd="rexecd";
  1428. X
  1429. # tmp and target file (for tftp test)
  1430. $target="/etc/motd";
  1431. $tmp="./tmp.$$";
  1432. X
  1433. # should probably generalize routine for chking for pats in file at some point
  1434. X
  1435. #  Read from $inetd to see if daemons are running.
  1436. # Comments are lines starting with a "#", so ignore.
  1437. # Checking for rexd:
  1438. #
  1439. print "Checking for $rexd in $inetd\n" if $debug;
  1440. if (@matches = grep(!/^\s*#/, &'fgrep($inetd, $rexd))) {
  1441. X    print "Warning!  $rexd is enabled in $inetd!\n";
  1442. }
  1443. X
  1444. # Check to see if anything started inetd.conf is writable;
  1445. print "Checking for writable dirs in $inetd\n" if $debug;
  1446. &'chk_strings($inetd);
  1447. X
  1448. # Checking for uudecode alias:
  1449. print "Checking for $uu alias in $aliases\n" if $debug;
  1450. print "Warning!  $uu is enabled in $aliases!\n"
  1451. X    if &'fgrep($aliases, "^\s*$uu:");
  1452. X
  1453. # uucode stuff -- thanks to pete shipley...
  1454. print "Checking uudecode out\n" if $debug;
  1455. if (-x $'UUDECODE) {
  1456. X    open(UU, "| $'UUDECODE");
  1457. X    print UU <<EOD_;
  1458. begin 4755 ./foobar.$$
  1459. end
  1460. EOD_
  1461. X    close(UU);
  1462. }
  1463. X
  1464. &'is_able($'UUDECODE,'s','s');    # check if uudecode is SUID
  1465. $is_able'silent = 1;
  1466. print "Warning!  $'UUDECODE creates setuid files!\n"
  1467. X   if &'is_able("./foobar.$$",'s','s');
  1468. $is_able'silent = 0;
  1469. unlink("./foobar.$$");
  1470. X
  1471. #  The rest is all for tftp stuff:
  1472. #
  1473. #   Get the local hostname...
  1474. $hostname = &'hostname;
  1475. X
  1476. #   Do the dirty work -- check tftp for the localhost, if it was found;
  1477. # this might take a bit, since tftp might have to time out.
  1478. X
  1479. print "Checking out tftp on $hostname\n" if $debug;
  1480. if (-x $'TFTP) {
  1481. X    open(SAVOUT, ">&STDOUT");    # suppress file not found
  1482. X    open(SAVERR, ">&STDERR");    # it's not as bad as it looks..
  1483. X    open(STDOUT, ">/dev/null") || die "Can't redirect stdout: $!\n";
  1484. X    open(STDERR, ">&STDOUT") || die "Can't dup stdout: $!\n";
  1485. X    close(STDOUT); close(STDERR);
  1486. X    open(TFTP, "| $'TFTP");
  1487. print TFTP <<_XXX_;
  1488. connect $hostname
  1489. get $target $tmp
  1490. quit
  1491. _XXX_
  1492. X    close(TFTP);
  1493. X    open(STDERR, ">&SAVERR"); close(SAVERR);
  1494. X    open(STDOUT, ">&SAVOUT"); close(SAVOUT);
  1495. } # > /dev/null 2> /dev/null
  1496. X
  1497. print "Warning!  tftp is enabled on $hostname!\n" if -s $tmp;
  1498. unlink $tmp;
  1499. X
  1500. # end of script
  1501. X
  1502. 1;
  1503. SHAR_EOF
  1504. chmod 0700 beta/misc.chk ||
  1505. echo 'restore of beta/misc.chk failed'
  1506. Wc_c="`wc -c < 'beta/misc.chk'`"
  1507. test 3967 -eq "$Wc_c" ||
  1508.     echo 'beta/misc.chk: original size 3967, current size' "$Wc_c"
  1509. rm -f _shar_wnt_.tmp
  1510. fi
  1511. # ============= beta/pass.cache.pl ==============
  1512. if test -f 'beta/pass.cache.pl' -a X"$1" != X"-c"; then
  1513.     echo 'x - skipping beta/pass.cache.pl (File already exists)'
  1514.     rm -f _shar_wnt_.tmp
  1515. else
  1516. > _shar_wnt_.tmp
  1517. echo 'x - extracting beta/pass.cache.pl (Text)'
  1518. sed 's/^X//' << 'SHAR_EOF' > 'beta/pass.cache.pl' &&
  1519. #
  1520. #   Routines for reading and caching user and group information.  These
  1521. # are used in multiple programs... it caches the info once, then hopefully
  1522. # won't be used again.
  1523. #
  1524. #  Steve Romig, May 1991.
  1525. #
  1526. # Provides a bunch of routines and a bunch of arrays.  Routines 
  1527. # (and their usage):
  1528. #
  1529. #    load_passwd_info($use_getent, $file_name)
  1530. #
  1531. #    loads user information into the %uname* and %uid* arrays 
  1532. #    (see below).  
  1533. #
  1534. #    If $use_getent is non-zero:
  1535. #        get the info via repeated 'getpwent' calls.  This can be
  1536. #        *slow* on some hosts, especially if they are running as a
  1537. #        YP (NIS) client.
  1538. #    If $use_getent is 0:
  1539. #        if $file_name is "", then get the info from reading the 
  1540. #        results of "ypcat passwd" and from /etc/passwd.  Otherwise, 
  1541. #        read the named file.  The file should be in passwd(5) 
  1542. #        format.
  1543. #
  1544. #    load_group_info($use_gentent, $file_name)
  1545. #
  1546. #    is similar to load_passwd_info.
  1547. #
  1548. # Information is stored in several convenient associative arrays:
  1549. #
  1550. #   %uname2shell    Assoc array, indexed by user name, value is 
  1551. #            shell for that user name.
  1552. #
  1553. #   %uname2dir        Assoc array, indexed by user name, value is
  1554. #            home directory for that user name.
  1555. #
  1556. #   %uname2uid        Assoc array, indexed by name, value is uid for 
  1557. #            that uid.
  1558. #            
  1559. #   %uname2passwd    Assoc array, indexed by name, value is password
  1560. #            for that user name.
  1561. #
  1562. #   %uid2names        Assoc array, indexed by uid, value is list of
  1563. #            user names with that uid, in form "name name
  1564. #            name...". 
  1565. #
  1566. #   %gid2members    Assoc array, indexed by gid, value is list of
  1567. #            group members in form "name name name..."
  1568. #
  1569. #   %gname2gid        Assoc array, indexed by group name, value is
  1570. #            matching gid.
  1571. #
  1572. #   %gid2names        Assoc array, indexed by gid, value is the
  1573. #            list of group names with that gid in form 
  1574. #            "name name name...".
  1575. #
  1576. # You can also use routines named the same as the arrays - pass the index 
  1577. # as the arg, get back the value.  If you use this, get{gr|pw}{uid|gid|nam} 
  1578. # will be used to lookup entries that aren't found in the cache.
  1579. #
  1580. # To be done:
  1581. #    probably ought to add routines to deal with full names.
  1582. #    maybe there ought to be some anal-retentive checking of password 
  1583. #    and group entries.
  1584. #    probably ought to cache get{pw|gr}{nam|uid|gid} lookups also.
  1585. #    probably ought to avoid overwriting existing entries (eg, duplicate 
  1586. #       names in password file would collide in the tables that are 
  1587. #    indexed by name).
  1588. #
  1589. # Disclaimer:
  1590. #    If you use YP and you use netgroup entries such as 
  1591. #    +@servers::::::
  1592. #    +:*:::::/usr/local/utils/messages
  1593. #    then loading the password file in with &load_passwd_info(0) will get 
  1594. #    you mostly correct YP stuff *except* that it won't do the password and 
  1595. #    shell substitutions as you'd expect.  You might want to use 
  1596. #    &load_passwd_info(1) instead to use getpwent calls to do the lookups, 
  1597. #    which would be more correct.
  1598. #
  1599. X
  1600. package main;
  1601. X
  1602. $PASSWD = '/etc/passwd' unless defined $PASSWD;
  1603. X
  1604. require 'pathconf.pl';
  1605. X
  1606. %uname2shell = ();
  1607. %uname2dir = ();
  1608. %uname2uid = ();
  1609. %uname2passwd = ();
  1610. %uid2names = ();
  1611. %gid2members = ();
  1612. %gname2gid = ();
  1613. %gid2names = ();
  1614. X
  1615. $DOMAINNAME = "/bin/domainname" unless defined $DOMAINNAME;
  1616. $YPCAT = "/bin/ypcat" unless defined $YPCAT;
  1617. X
  1618. $yptmp = "./yptmp.$$";
  1619. X
  1620. $passwd_loaded = 0;        # flags to use to avoid reloading everything
  1621. $group_loaded = 0;        # unnecessarily...
  1622. X
  1623. #
  1624. # We provide routines for getting values from the data structures as well.
  1625. # These are named after the data structures they cache their data in.  Note 
  1626. # that they will all generate password and group file lookups via getpw* 
  1627. # and getgr* if they can't find info in the cache, so they will work
  1628. # "right" even if load_passwd_info and load_group_info aren't called to 
  1629. # preload the caches.
  1630. #
  1631. # I should point out, however, that if you don't call load_*_info to preload
  1632. # the cache, uid2names, gid2names and gid2members *will not* be complete, since 
  1633. # you must read the entire password and group files to get a complete picture.
  1634. # This might be acceptable in some cases, so you can skip the load_*_info
  1635. # calls if you know what you are doing...
  1636. #
  1637. sub uname2shell {
  1638. X    local($key) = @_;
  1639. X
  1640. X    if (! defined($uname2shell{$key})) {
  1641. X    &add_pw_info(getpwnam($key));
  1642. X    }
  1643. X
  1644. X    return($uname2shell{$key});
  1645. }
  1646. X
  1647. sub uname2dir {
  1648. X    local($key) = @_;
  1649. X    local(@pw_info);
  1650. X
  1651. X    if (! defined($uname2dir{$key})) {
  1652. X    &add_pw_info(getpwnam($key));
  1653. X    }
  1654. X
  1655. X    return($uname2dir{$key});
  1656. }
  1657. X
  1658. sub uname2uid {
  1659. X    local($key) = @_;
  1660. X    local(@pw_info);
  1661. X
  1662. X    if (! defined($uname2uid{$key})) {
  1663. X    &add_pw_info(getpwnam($key));
  1664. X    }
  1665. X
  1666. X    return($uname2uid{$key});
  1667. }
  1668. X
  1669. sub uname2passwd {
  1670. X    local($key) = @_;
  1671. X    local(@pw_info);
  1672. X
  1673. X    if (! defined($uname2passwd{$key})) {
  1674. X    &add_pw_info(getpwnam($key));
  1675. X    }
  1676. X
  1677. X    return($uname2passwd{$key});
  1678. }
  1679. X
  1680. sub uid2names {
  1681. X    local($key) = @_;
  1682. X    local(@pw_info);
  1683. X
  1684. X    if (! defined($uid2names{$key})) {
  1685. X    &add_pw_info(getpwuid($key));
  1686. X    }
  1687. X
  1688. X    return($uid2names{$key});
  1689. }
  1690. X
  1691. sub gid2members {
  1692. X    local($key) = @_;
  1693. X    local(@gr_info);
  1694. X
  1695. X    if (! defined($gid2members{$key})) {
  1696. X    &add_gr_info(getgrgid($key));
  1697. X    }
  1698. X
  1699. X    return($gid2members{$key});
  1700. }
  1701. X
  1702. sub gname2gid {
  1703. X    local($key) = @_;
  1704. X    local(@gr_info);
  1705. X
  1706. X    if (! defined($gname2gid{$key})) {
  1707. X    &add_gr_info(getgrnam($key));
  1708. X    }
  1709. X
  1710. X    return($gname2gid{$key});
  1711. }
  1712. X
  1713. sub gid2names {
  1714. X    local($key) = @_;
  1715. X    local(@gr_info);
  1716. X
  1717. X    if (! defined($gid2names{$key})) {
  1718. X    &add_gr_info(getgrgid($key));
  1719. X    }
  1720. X
  1721. X    return($gid2names{$key});
  1722. }
  1723. X
  1724. #
  1725. # Update user information for the user named $name.  We cache the password, 
  1726. # uid, login group, home directory and shell.
  1727. #
  1728. X
  1729. sub add_pw_info {
  1730. X    local($name, $passwd, $uid, $gid) = @_;
  1731. X    local($dir, $shell);
  1732. X
  1733. #
  1734. # Ugh!  argh...yech...sigh.  If we use getpwent, we get back 9 elts, 
  1735. # if we parse /etc/passwd directly we get 7.  Pick off the last 2 and 
  1736. # assume that they are the $directory and $shell.  
  1737. #
  1738. X    $num = ( $#_ >= 7 ? 8 : 6 );
  1739. X    $dir = $_[$num - 1];
  1740. X    $shell = $_[$num] || '/bin/sh';
  1741. X
  1742. X
  1743. X    if ($name ne "") {
  1744. X    $uname2shell{$name} = $shell;
  1745. X    $uname2dir{$name} = $dir;
  1746. X    $uname2uid{$name} = $uid;
  1747. X    $uname2passwd{$name} = $passwd;
  1748. X
  1749. X    if ($gid ne "") {
  1750. X        # fixme: should probably check for duplicates...sigh
  1751. X
  1752. X        if (defined($gid2members{$gid})) {
  1753. X        $gid2members{$gid} .= " $name";
  1754. X        } else {
  1755. X        $gid2members{$gid} = $name;
  1756. X        }
  1757. X    }
  1758. X
  1759. X    if ($uid ne "") {
  1760. X        if (defined($uid2names{$uid})) {
  1761. X        $uid2names{$uid} .= " $name";
  1762. X        } else {
  1763. X        $uid2names{$uid} = $name;
  1764. X        }
  1765. X    }
  1766. X    }
  1767. }
  1768. X
  1769. #
  1770. # Update group information for the group named $name.  We cache the gid 
  1771. # and the list of group members.
  1772. #
  1773. X
  1774. sub add_gr_info {
  1775. X    local($name, $passwd, $gid, $members) = @_;
  1776. X
  1777. X    if ($name ne "") {
  1778. X    $gname2gid{$name} = $gid;
  1779. X
  1780. X    if ($gid ne "") {
  1781. X        if (defined($gid2names{$gid})) {
  1782. X        $gid2names{$gid} .= " $name";
  1783. X        } else {
  1784. X        $gid2names{$gid} = $name;
  1785. X        }
  1786. X
  1787. X        # fixme: should probably check for duplicates
  1788. X
  1789. X        $members = join(' ', split(/[, \t]+/, $members));
  1790. X
  1791. X        if (defined($gid2members{$gid})) {
  1792. X        $gid2members{$gid} .= " " . $members;
  1793. X        } else {
  1794. X        $gid2members{$gid} = $members;
  1795. X        }
  1796. X    }
  1797. X    }
  1798. }
  1799. X
  1800. #
  1801. # We need to suck in the entire group and password files so that we can 
  1802. # make the %uid2names, %gid2members and %gid2names lists complete.  Otherwise,
  1803. # we would just read the entries as needed with getpw* and cache the results.
  1804. # Sigh.
  1805. #
  1806. # There are several ways that we might find the info.  If $use_getent is 1, 
  1807. # then we just use getpwent and getgrent calls to read the info in.
  1808. #
  1809. # That isn't real efficient if you are using YP (especially on a YP client), so
  1810. # if $use_getent is 0, we can use ypcat to get a copy of the passwd and
  1811. # group maps in a fairly efficient manner.  If we do this we have to also read
  1812. # the local /etc/{passwd,group} files to complete our information.  If we aren't 
  1813. # using YP, we just read the local pasword and group files.
  1814. #
  1815. sub load_passwd_info {
  1816. X    local($use_getent, $file_name) = @_;
  1817. X    local(@pw_info);
  1818. X
  1819. X    if ($passwd_loaded) {
  1820. X    return;
  1821. X    }
  1822. X
  1823. X    $passwd_loaded = 1;
  1824. X
  1825. X    if ($'GET_PASSWD) {
  1826. X    open(GFILE, "$'GET_PASSWD|") || die "can't $'GET_PASSWD";
  1827. X    while (<GFILE>) {
  1828. X        chop;
  1829. X        &add_pw_info(split(/:/));
  1830. X        }
  1831. X    close(GFILE);
  1832. X    }
  1833. X    else {
  1834. X
  1835. X    if ($use_getent) {
  1836. X    #
  1837. X    # Use getpwent to get the info from the system, and add_pw_info to 
  1838. X    # cache it.
  1839. X    #
  1840. X    while (@pw_info = getpwent) {
  1841. X        &add_pw_info(@pw_info);
  1842. X    }
  1843. X
  1844. X    endpwent;
  1845. X
  1846. X    return;
  1847. X    } elsif ($file_name eq "") {
  1848. X    chop($has_yp = `$DOMAINNAME`);
  1849. X    if ($has_yp) {
  1850. X        #
  1851. X        # If we have YP (NIS), then use ypcat to get the stuff from the 
  1852. X        # map.@
  1853. X        #
  1854. X        system("$YPCAT passwd > $yptmp 2> /dev/null");
  1855. X        if (-s $yptmp) {
  1856. X            open(FILE, "$YPCAT passwd|") ||
  1857. X              die "can't 'ypcat passwd'";
  1858. X            while (<FILE>) {
  1859. X            chop;
  1860. X            &add_pw_info(split(/:/));
  1861. X                }
  1862. X            }
  1863. X        close(FILE);
  1864. X    }
  1865. X
  1866. X    #
  1867. X    # We have to read /etc/passwd no matter what...
  1868. X    #
  1869. X    $file_name = "/etc/passwd";
  1870. X    }
  1871. X
  1872. X    open(FILE, $file_name) ||
  1873. X      die "can't open $file_name";
  1874. X
  1875. X    while (<FILE>) {
  1876. X    chop;
  1877. X        
  1878. X    if ($_ !~ /^\+/) {
  1879. X        &add_pw_info(split(/:/));
  1880. X    }
  1881. X
  1882. X    # fixme: if the name matches +@name, then this is a wierd 
  1883. X    # netgroup thing, and we aren't dealing with it right.  might want
  1884. X    # to warn the poor user...suggest that he use the use_getent 
  1885. X    # method instead.
  1886. X    }
  1887. X    }
  1888. X
  1889. X    close(FILE);
  1890. }
  1891. X
  1892. sub load_group_info {
  1893. X    local($use_getent, $file_name) = @_;
  1894. X    local(@gr_info);
  1895. X
  1896. X    if ($group_loaded) {
  1897. X    return;
  1898. X    }
  1899. X
  1900. X    $group_loaded = 1;
  1901. X
  1902. X    if ($use_getent) {
  1903. X    #
  1904. X    # Use getgrent to get the info from the system, and add_gr_info to 
  1905. X    # cache it.
  1906. X    #
  1907. X    while ((@gr_info = getgrent()) != 0) {
  1908. X        &add_gr_info(@gr_info);
  1909. X    }
  1910. X
  1911. X    endgrent();
  1912. X
  1913. X    return();
  1914. X    } elsif ($file_name eq "") {
  1915. X    chop($has_yp = `$DOMAINNAME`);
  1916. X    if ($has_yp) {
  1917. X        #
  1918. X        # If we have YP (NIS), then use ypcat to get the stuff from the 
  1919. X        # map.
  1920. X        #
  1921. X        system("$YPCAT passwd > $yptmp 2> /dev/null");
  1922. X        if (-s $yptmp) {
  1923. X            open(FILE, "$YPCAT group|") ||
  1924. X              die "can't 'ypcat group'";
  1925. X            while (<FILE>) {
  1926. X            chop;
  1927. X            &add_gr_info(split(/:/));
  1928. X                }
  1929. X            close(FILE);
  1930. X        }
  1931. X    }
  1932. X
  1933. X    #
  1934. X    # We have to read /etc/group no matter what...
  1935. X    #
  1936. X    $file_name = "/etc/group";
  1937. X    }
  1938. X
  1939. X    open(FILE, $file_name) ||
  1940. X      die "can't open $file_name";
  1941. X
  1942. X    while (<FILE>) {
  1943. X    chop;
  1944. X    if ($_ !~ /^\+/) {
  1945. X        &add_gr_info(split(/:/));
  1946. X    }
  1947. X
  1948. X    # fixme: if the name matches +@name, then this is a wierd 
  1949. X    # netgroup thing, and we aren't dealing with it right.  might want
  1950. X    # to warn the poor user...suggest that he use the use_getent 
  1951. X    # method instead.
  1952. X    }
  1953. X
  1954. X    close(FILE);
  1955. }
  1956. X
  1957. # Load the password stuff -- Do NOT take this out!
  1958. &'load_passwd_info(0,$PASSWD);
  1959. X
  1960. unlink $yptmp;
  1961. X
  1962. 1;
  1963. SHAR_EOF
  1964. chmod 0700 beta/pass.cache.pl ||
  1965. echo 'restore of beta/pass.cache.pl failed'
  1966. Wc_c="`wc -c < 'beta/pass.cache.pl'`"
  1967. test 10640 -eq "$Wc_c" ||
  1968.     echo 'beta/pass.cache.pl: original size 10640, current size' "$Wc_c"
  1969. rm -f _shar_wnt_.tmp
  1970. fi
  1971. # ============= beta/pass.chk ==============
  1972. if test -f 'beta/pass.chk' -a X"$1" != X"-c"; then
  1973.     echo 'x - skipping beta/pass.chk (File already exists)'
  1974.     rm -f _shar_wnt_.tmp
  1975. else
  1976. > _shar_wnt_.tmp
  1977. echo 'x - extracting beta/pass.chk (Text)'
  1978. sed 's/^X//' << 'SHAR_EOF' > 'beta/pass.chk' &&
  1979. #!/bin/sh -- need to mention perl here to avoid recursion
  1980. 'true' || eval 'exec perl -S $0 $argv:q';
  1981. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
  1982. & eval 'exec /usr/local/bin/perl -S $0 $argv:q'
  1983. X        if 0;
  1984. X
  1985. #
  1986. #  Pass.chk -- a password guesser.  Functionally equivalent to the original
  1987. # cops password guesser.  The -P option doesn't work right now (alpha release,
  1988. # don't you know :-), since we're doing funky things with password caching,
  1989. # but this will change soon.
  1990. #
  1991. #  Usage: $0 [options] dictionary
  1992. #
  1993. #    -P pfile    password file (not working)
  1994. #    -p        print found passwords (incompatible with -M)
  1995. #       -d        check prefix/suffix of digits [0-9]
  1996. #       -g        check all words in gcos, plan, project, signature files
  1997. #    -r         reverse the word tests
  1998. #    -s         check all single chars as passwords
  1999. #    -u         output the current user being checked
  2000. #    -U         try uppercase word tests
  2001. #    -v        verbose, print advisory information
  2002. #    -x         check guess+prefix/suffix with strange chars added on
  2003. #    -0 (zero)    change all o's ("oh"'s) to 0 (zeros)
  2004. #    -m         misspell words -- chop off first and last chars,
  2005. #            if > 3 chars.  E.g. "dinner" ==> "inner" and "dinne"
  2006. #
  2007. # Originally written by Tim Tessin with lots more features, etc., etc., etc.
  2008. # I ripped out all of his extra functionality that duplicated some of the
  2009. # other cops stuff, and some things that just didn't fit, and added some
  2010. # code to finish the simulation of the old checker. -- dan
  2011. X
  2012. require "getopts.pl";
  2013. require "pass.cache.pl";
  2014. $Passwd = "/etc/passwd";
  2015. @strange_things = (" ", ";", ",", ".", "!", "@", "#", "$",
  2016. X                     "%", "^", "&", "*", "(", ")", "-", "_", 
  2017. X                     "=", "+", "[", "]", "{", "}", "'", "\"",
  2018. X                     "|", "`", "~", ">", "<");
  2019. X
  2020. # can probably just do "|| &usage;" since &usage is only used once. ;-)
  2021. &Getopts("p0dgrsuUvlPm") || print STDERR "Usage: $0 -p0xdgrsuUPvm\n";
  2022. X
  2023. # sanity checks
  2024. $opt_P = $Passwd unless $opt_P;
  2025. -r $opt_P || die "Can't read passwd file $opt_P\n";
  2026. X
  2027. # unbuffer output
  2028. select (STDOUT); $| = 1;
  2029. X
  2030. $dups = 0;        # duplicate name entries
  2031. $new = 0;        # new entries
  2032. $changed = 0;        # password (and other data) changed
  2033. $deleted = 0;        # deleted entries
  2034. $updated = 0;        # data other than password changed
  2035. $nlog = 0;        # number of log entries, used for print decisions
  2036. $ntest = 0;
  2037. $ndone = 0;
  2038. X
  2039. for $uid (keys %uname2passwd) {
  2040. X    next if length($pass = $uname2passwd{$uid}) != 13;
  2041. X    next unless $pass; # shall we report null passwd's, too?  ;-)
  2042. X    if ($try = &dopwd()) {
  2043. X        $pwd = ($opt_p) ? $try : "";
  2044. X        # printf "Username: %-8s  <password guessed>  $pwd\n",$P[0];
  2045. X        printf "Warning!  $uid password Problem: Guessed: %s\t\t$pwd\n",$P[0];
  2046. X        }
  2047. X    $ndone++;
  2048. X    }
  2049. X
  2050. 1;
  2051. # end of program
  2052. X
  2053. X
  2054. ######################## Support Subroutines ###########################
  2055. # dopwd tests each password entry against several simple checks and all
  2056. # the words in the dictionaries specified.  The simple checks consists
  2057. # of trying the username as the password ('joe' accounts), words derived
  2058. # from the gecos fields (usually first and last names).
  2059. X
  2060. sub dopwd {
  2061. X    $tries = 0;
  2062. X
  2063. X    print "$uid\n" if ($opt_u);
  2064. X
  2065. X    # try user name
  2066. X    ($try = &testpwd($uid,$pass)) && return $try;
  2067. X    # try uiduid
  2068. X    if (length($uid) < 8) {
  2069. X    ($try = &testpwd($uid . $uid,$pass)) && return $try;
  2070. X    }
  2071. X
  2072. X    # do gcos field?
  2073. X    if ($opt_g) {
  2074. X    @gcos = split(/[.,& -]/,$uname2gcos{$uid});
  2075. X    foreach $i (@gcos) {
  2076. X        next unless $i;        # skip null split values
  2077. X        ($try = &testpwd($i,$pass)) && return $try;
  2078. X    }
  2079. X
  2080. X    # Try names from misc files
  2081. X    #
  2082. X    undef %words;
  2083. X    # files to check
  2084. X    @files2chk = ("/.project", "/.plan", "/.signature");
  2085. X    $home = $uname2dir{$uid};
  2086. X    for $i (@files2chk) {
  2087. X        open (FOOFILE, $home . $i);
  2088. X        while (<FOOFILE>) {
  2089. X        chop;
  2090. X        @line = split(/([.,;\s])/);
  2091. X        for $j (@line) {
  2092. X            $words{$j}=$j unless $j=~/[\s]/;
  2093. X        }
  2094. X        }
  2095. X        close FOOFILE;
  2096. X    }
  2097. X    for $k (values %words) {
  2098. X        # print "word $k\n";
  2099. X        ($try = &testpwd($k,$pass)) && return $try;
  2100. X    }
  2101. X    }
  2102. X
  2103. # do dictionaries
  2104. # save state of upper/reverse so individual dicts can temporarily
  2105. # override.
  2106. foreach $i (@ARGV) {
  2107. X    if (open (DICT,$i)) {
  2108. X        while (<DICT>) {
  2109. X            chop;
  2110. X            if ($try = &testpwd($_,$pass)) {
  2111. X                close DICT;
  2112. X                return $try;
  2113. X                }
  2114. X            }
  2115. X        close DICT;
  2116. X        }
  2117. X    }
  2118. return 0;
  2119. }
  2120. X
  2121. X
  2122. # small subroutines to help the main password cracker.  All are labeled
  2123. # p_xxx, where xxx is the identifying name.
  2124. #
  2125. X
  2126. # if leading character is upper-case, also try lower case version
  2127. sub p_lc {
  2128. X    local($try) = @_;
  2129. X    local($ntry);
  2130. X    if ( $try =~ /^[A-Z]/ ) {
  2131. X    ($ntry = $try) =~ y/A-Z/a-z/;
  2132. X    push(@total_guesses, $ntry);
  2133. X    }
  2134. }
  2135. X
  2136. # reverse check
  2137. sub p_rev {
  2138. X    local($try) = @_;
  2139. X    local($ntry);
  2140. X    $ntry = reverse $try;
  2141. X    if ($ntry ne $try) {
  2142. X    push(@total_guesses, $ntry);
  2143. X    }
  2144. }
  2145. X
  2146. # uppercase check
  2147. sub p_up {
  2148. X    local($try) = @_;
  2149. X    local($ntry);
  2150. X    ($ntry = $try) =~ y/a-z/A-Z/;
  2151. X    if ($ntry ne $try) { push(@total_guesses, $ntry); }
  2152. }
  2153. X
  2154. # testpwd checks a word to see if it matches the encrpted password
  2155. # if the word is capitalized, the lowercase version is tried as well
  2156. X
  2157. sub testpwd {
  2158. local ($try,$pass) = @_;
  2159. local (@total_guesses);
  2160. X
  2161. push(@total_guesses, $try);
  2162. X
  2163. # free (lower case) check if first letter is uppercase
  2164. &p_lc($try);
  2165. # reverse?
  2166. if ($opt_r) { &p_rev($try); }
  2167. # uppercase?
  2168. if ($opt_U) { &p_up($try); }
  2169. X
  2170. # single digit tacked on to beginning and end
  2171. if ($opt_d) {
  2172. X    if (length ($try) < 8) {
  2173. X        foreach $i ('0'..'9') {
  2174. X            $ntry = $i.$try;
  2175. X            push(@total_guesses, $ntry);
  2176. X            if ($opt_r) { &p_rev($ntry); }
  2177. X            if ($opt_U) { &p_up($ntry); }
  2178. X            }
  2179. X        foreach $i ('0'..'9') {
  2180. X            $ntry = $try.$i;
  2181. X            push(@total_guesses, $ntry);
  2182. X            if ($opt_r) { &p_rev($ntry); }
  2183. X            if ($opt_U) { &p_up($ntry); }
  2184. X            }
  2185. X        }
  2186. X    }
  2187. X
  2188. # change o's to 0's ("oh"'s to zeros)
  2189. if ($opt_0) {
  2190. X    if (($ntry = $try) =~ s/o/0/g) { push(@total_guesses, $ntry); }
  2191. X    }
  2192. X
  2193. # misspell words -- truncate first and last letter, if > 3 chars
  2194. # thanks to  William Vajk, learn@ddsw1.MCS.COM, who posted this idea.
  2195. if ($opt_m) {
  2196. X    $len = length($try);
  2197. X    if ($len > 3) {
  2198. X        ($ntry = $try) =~ s/^.//; push(@total_guesses, $ntry);
  2199. X        if ($len < 9) {
  2200. X            ($ntry = $try) =~ s/.$//; push(@total_guesses, $ntry);
  2201. X            }
  2202. X        }
  2203. X    }
  2204. X
  2205. # weird things!  Tacked on to beginning and end
  2206. if ($opt_x) {
  2207. X    if (length ($try) < 8) {
  2208. X        foreach $i (@strange_things) {
  2209. X            $ntry = $i.$try;
  2210. X            push(@total_guesses, $ntry);
  2211. X            if ($opt_r) { &p_rev($ntry); }
  2212. X            if ($opt_U) { &p_up($ntry); }
  2213. X            }
  2214. X        foreach $i (@strange_things) {
  2215. X            $ntry = $try.$i;
  2216. X            push(@total_guesses, $ntry);
  2217. X            if ($opt_r) { &p_rev($ntry); }
  2218. X            if ($opt_U) { &p_up($ntry); }
  2219. X            }
  2220. X        }
  2221. X    }
  2222. X
  2223. # do single letters, #'s, if needed
  2224. if ($opt_s && $uid ne $last_user) {
  2225. X    $last_user = $uid;
  2226. X    foreach $i (@strange_things) { push(@total_guesses,$i); }
  2227. X    foreach $i (0..9) { push(@total_guesses, $i); }
  2228. X    foreach $i (A..Z) { push(@total_guesses, $i); }
  2229. X    foreach $i (a..z) { push(@total_guesses, $i); }
  2230. X    }
  2231. X
  2232. foreach $i (@total_guesses) {
  2233. #    print "Trying \"$try\" on $uid\n" if $opt_v;
  2234. X    print "Trying \"$i\" on $uid\n" if $opt_v;
  2235. X    $epw = crypt($try,$pass);
  2236. X    ($epw eq $pass) && return $i;
  2237. X    }
  2238. undef @total_guesses;
  2239. X
  2240. return 0;
  2241. }
  2242. SHAR_EOF
  2243. chmod 0700 beta/pass.chk ||
  2244. echo 'restore of beta/pass.chk failed'
  2245. Wc_c="`wc -c < 'beta/pass.chk'`"
  2246. test 7034 -eq "$Wc_c" ||
  2247.     echo 'beta/pass.chk: original size 7034, current size' "$Wc_c"
  2248. rm -f _shar_wnt_.tmp
  2249. fi
  2250. # ============= beta/passwd ==============
  2251. if test -f 'beta/passwd' -a X"$1" != X"-c"; then
  2252.     echo 'x - skipping beta/passwd (File already exists)'
  2253.     rm -f _shar_wnt_.tmp
  2254. else
  2255. > _shar_wnt_.tmp
  2256. echo 'x - extracting beta/passwd (Text)'
  2257. sed 's/^X//' << 'SHAR_EOF' > 'beta/passwd' &&
  2258. root:/gBRyGosCTKcY:0:1:The root of all evil:/:/bin/csh
  2259. stroot:QXCAyBt4zMwoE:0:1:The root of all evil:/:/bin/csh
  2260. daemon:*:1:1::/:/bin/false
  2261. sys:*:2:2::/:/bin/false
  2262. bin:*:3:3::/bin:/bin/false
  2263. audit:*:9:9::/etc/security/audit:/bin/false
  2264. poepping:85IPml.fmT92o:74:78:Mark Poepping:/usr/users/poepping:/bin/csh
  2265. rlv:*lLHclBvJ6p6Zo:108:20:Randy Vandermolen:/usr/users/rlv:/bin/csh
  2266. georgia:nIVJdSE/TAC6U:113:78:Georgia Killcrece:/usr/users/georgia:/usr/local/bin/tcsh
  2267. SHAR_EOF
  2268. true || echo 'restore of beta/passwd failed'
  2269. fi
  2270. echo 'End of  part 2'
  2271. echo 'File beta/passwd is continued in part 3'
  2272. echo 3 > _shar_seq_.tmp
  2273. exit 0
  2274.