(anonymous) ftp checker, cops, etc.
Dan Farmer
df at death.cert.sei.cmu.edu
Wed Nov 21 09:47:08 AEST 1990
Thought I'd toss this out to the sharks. This is a shar file with a shell
script and an C program that attempts to check for proper anonymous ftp
setup. Comments are encouraged; after a bit, I'll put out another patch to
COPS with this and other stuff in it (to forstall questions, cops is a
security checking thingee that is available via anon-ftp from cert.sei.cmu.edu,
and uunet.uu.net, or other fine archive sites near you.)
-- dan
df at cert.sei.cmu.edu
(More or less what the man page will say:)
This shell script checks to see if you've set up (mainly anonymous)
ftp correctly. The "-a" option checks your anon-ftp setup; without that,
this script doesn't do a whole lot -- just check to see if your ftpusers
file doesn't have any root accounts in it.
There is no "right" way to set up ftp, but there are lots of wrong
ways :-) I suggest everything be owned by either root or ftp, and nothing
be world writable, with the exception of a ~ftp/incoming directory or
something like that. You can change the owners via the $primary and
$secondary variables (default root & ftp, respectively), and the publically
writable directory is $incoming (default ~ftp/incoming). Do not make
~ftp/pub world writable, if you are storing data or programs for people
to use; you're inviting intruders to write all over the files and programs,
and leave all kinds of nasties...
Here are the assumptions I made for anon-ftp:
o User "ftp" should have a non-valid password ("*", whatever) and a invalid
shell, but a valid home directory -- this is where all the anonymous
stuff gets stashed. This checks for the passwd and valid home dir only.
I would suggest a .rhosts file of 0 size, owned by root, but that's
personal preference. This will complain if a .rhosts file exists, and
is either non-0 or non-root owned.
o All root equivalent accounts (uid=0) with valid passwords should be in
/etc/ftpusers
o The home dir for ftp is in /etc/passwd, should be a valid directory, and
should not be "/" (if the dir is invalid, ftpd should choke.)
o The ~ftp/etc/{passwd|group} files should be different than their
counterparts in /etc (don't want password files available via anon-ftp.)
In addition, it seems as though the entries in ~ftp/etc/{passwd|group}
files don't do a whole lot -- you might see something like:
With the entries:
drwxr-xr-x 8 cert ftp 512 Nov 7 16:56 pub/
Without:
drwxr-xr-x 8 8001 105 512 Nov 7 16:56 pub/
Some versions of ftpd allow you to leave the files off entirely; that
is the preferred method, IMHO; else, you might try putting a null file
there. Experiment...
o ~ftp, ~ftp/bin, ~/ftp/etc should all be non-world-writeable, and owned
by either root or ftp. The ls command should be mode 111, the password
and group files 444.
Finally, the shar:
================Cut here with pointy sharp things=============================
#!/bin/sh
# This is a shell archive (shar 3.10)
# made 11/20/1990 20:09 UTC by df at death.cert.sei.cmu.edu
# Source directory /tmp/junk
#
# existing files WILL be overwritten
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 5930 -rwx------ ftp.chk
# 4196 -rw------- is_able.c
#
touch 2>&1 | fgrep '[-amc]' > /tmp/s3_touch$$
if [ -s /tmp/s3_touch$$ ]
then
TOUCH=can
else
TOUCH=cannot
fi
rm -f /tmp/s3_touch$$
# ============= ftp.chk ==============
sed 's/^X//' << 'SHAR_EOF' > ftp.chk &&
X:
X#!/bin/sh
X#
X# Usage: ftp.chk [-a]
X#
X# This shell script checks to see if you've set up (mainly anonymous)
X# ftp correctly. The "-a" option checks your anon-ftp setup; without that,
X# this script doesn't do a whole lot -- just check to see if your ftpusers
X# file doesn't have any root accounts in it.
X#
X# See the man page for a more detailed description, but here's what this
X# checks for:
X#
X# - User ftp exists in the password file.
X# - root (or all root equivalents) are in ftpusers file.
X# - Home directory for ftp should exist, and not be /
X# - The ~ftp/etc/{passwd|group} should not be the same as the real ones.
X# - Various critical files/directories should exist, and have correct
X# permissions and owners:
X#
X# File/Dir Perms Owner Other
X# ========= ====== ====== ======
X# ~ftp 555 ftp
X# or
X# ~ftp non-w.w. root
X#
X# ~ftp/bin non-w.w. root/ftp
X# ~ftp/bin/ls 111 root/ftp
X# ~ftp/etc non-w.w. root/ftp non-w.w. root 0 size, is optional
X# ~ftp/* non-w.w. other dirs/files in ~ftp
X#
X
X# Where is everyone?
XECHO=/bin/echo
XTEST=/bin/test
XAWK=/bin/awk
XGREP=/bin/grep
XLS=/bin/ls
XCMP=/bin/cmp
X
X# If an argument is present, it should be an "a"
Xif $TEST $# -gt 1 ; then
X $ECHO Usage: $0 [-a]
X exit 1
X fi
Xif $TEST $# -eq 1 ; then
X if $TEST $1 = "-a" ; then
X anonymous=yes
X else
X $ECHO Usage: $0 [-a]
X exit 1
X fi
X fi
X
X#
X# some might have this as ftpd; is the account in /etc/passwd
Xftpuid=ftp
X
X# system files
Xftpusers=/etc/ftpusers
Xpasswd=/etc/passwd
Xgroup=/etc/group
X
X# ftp's files:
Xftproot=`$AWK -F: '/^'"$ftpuid"':/{print $6}' $passwd`
X
X# if the user ftp doesn't exist, no-anon stuff....
Xif $TEST -z $ftproot -a "$anonymous" = "yes" ; then
X $ECHO Warning! Need user $ftp for anonymous ftp to work!
X exit
X fi
X
Xftprhosts=$ftproot/.rhosts
Xftpbin=$ftproot"/bin"
Xftpls=$ftpbin"/ls"
Xftpetc=$ftproot"/etc"
Xftppasswd=$ftpetc"/passwd"
Xftpgroup=$ftpetc"/group"
X
X# the pub/incoming stuff; by default, pub is *not* world writable, incoming
X# is; if you want pub to be world writable, just change incoming to "pub"
Xincoming=incoming
Xftpincoming=$ftproot"/"$incoming
Xftppub=$ftproot"/pub"
X
Xcrit_files="$ftpgroup $ftppasswd $ftpls"
X
X# primary and secondary owners...
Xprimary=root
Xsecondary=ftp
X
Xif $TEST -s $ftpusers
X then
X # check to see if root (or root equivalents) is in ftpusers file
X all_roots=`$AWK -F: '{if ($3==0 && length($2)==13) printf("%s ", $1)}' $passwd`
X if $TEST -n "$all_roots" ; then
X for i in $all_roots
X do
X if $TEST ! "`$GREP $i $ftpusers`"
X then
X $ECHO Warning! $i should be in $ftpusers!
X fi
X done
X fi
X fi
X
X# do the anonymous ftp checking stuff now
Xif $TEST -n "$anonymous" ; then
X #
X # ftp's home dir checking
X if $TEST ! -d "$ftproot" -o -z "$ftproot"; then
X $ECHO Warning! Home directory for ftp doesn\'t exist!
X fi
X if $TEST "$ftproot" = "/" ; then
X $ECHO Warning! $ftproot ftp\'s home directory should not be \"/\"!
X fi
X #
X # Don't want the passwd and group files to be the real ones!
X if $TEST "`$CMP $passwd $ftppasswd 2> /dev/null`" ; then
X :
X else $ECHO Warning! $ftppasswd and $passwd are the same!
X fi
X if $TEST "`$CMP $group $ftpgroup 2> /dev/null`" ; then
X :
X else $ECHO Warning! $ftpgroup and $group are the same!
X fi
X
X # want to check all the critical files and directories for correct
X # ownership.
X #
X # This is what a "/bin/ls -l" of a file should look like:
X # ---x--x--x 1 root 81920 Dec 31 1999 /bin/ls
X # So in awk, $3 is the owner, $1 is the permission.
X #
X crit_files=$crit_files" "$ftpbin" "$ftpetc
X for i in $crit_files
X do
X if $TEST ! -f $i -a ! -d $i; then
X $ECHO Warning! File $i is missing!
X fi
X
X owner=`$LS -ld $i | $AWK '{print $3}'`
X if $TEST "$owner" = "$primary" -o "$owner" = "$secondary" ; then
X :
X else
X $ECHO Warning! $i should be owned by $primary or $secondary!
X fi
X done
X
X # ftproot is special; if owned by root; should be !world writable;
X # if owned by ftp, should be mode 555
X owner=`$LS -ld $ftproot | $AWK '{print $3}'`
X perms=`$LS -ld $ftproot | $AWK '{print $1}'`
X if $TEST "$owner" = "$primary" -o "$owner" = "$secondary" ; then
X :
X else
X $ECHO Warning! $i should be owned by $primary or $secondary!
X fi
X
X if $TEST "$owner" = "$primary" ; then
X ./is_able $ftproot w w
X elif $TEST "$owner" != "$secondary" ; then
X $ECHO Warning! $ftproot should be owned by $primary or $secondary!
X elif $TEST "$perms" != "dr-xr-xr-x" ; then
X $ECHO Warning! $ftproot should be mode 555!
X fi
X
X #
X # check the .rhosts file:
X if $TEST -f $ftprhosts ; then
X if $TEST -s $ftprhosts ; then
X $ECHO Warning! $ftprhosts should be be empty!
X fi
X owner=`$LS -ld $ftprhosts | $AWK '{print $3}'`
X if $TEST "$owner" = "$primary" -o "$owner" = "$secondary" ; then
X :
X else
X $ECHO Warning! $ftprhosts should be owned by $primary or $secondary!
X fi
X fi
X
X #
X # finally, some permissions of miscellaneous files:
X perms=`$LS -ld $ftpls | $AWK '{print $1}'`
X if $TEST "$perms" != "---x--x--x" ; then
X $ECHO Warning! Incorrect permissions on \"ls\" in $ftpbin!
X fi
X
X perms=`$LS -ld $ftppasswd | $AWK '{print $1}'`
X if $TEST "$perms" != "-r--r--r--" ; then
X $ECHO Warning! Incorrect permissions on \"passwd\" in $ftpetc!
X fi
X
X perms=`$LS -ld $ftpgroup | $AWK '{print $1}'`
X if $TEST "$perms" != "-r--r--r--" ; then
X $ECHO Warning! Incorrect permissions on \"group\" in $ftpetc!
X fi
X
X # Finally, the ~ftp/{pub|incoming|whatever} stuff; don't want the
X # the number of blocks, or the "." and ".." entries:
X all_dirs=`$LS -al $ftproot | $AWK '{if (NF >= 8) if ($NF!="." && $NF!="..") print $NF}'`
X for i in $all_dirs
X do
X if $TEST -n "`is_able $ftproot/$i w w`" -a $i != "$ftpincoming" ; then
X $ECHO Warning! Anon-ftp directory $i is World Writable!
X fi
X done
X fi
X
X# end of script
SHAR_EOF
chmod 0700 ftp.chk || echo "restore of ftp.chk fails"
if [ $TOUCH = can ]
then
touch -am 1120145290 ftp.chk
fi
# ============= is_able.c ==============
sed 's/^X//' << 'SHAR_EOF' > is_able.c &&
X/*
X Usage: is_able filename {W|w|G|g} {R|r|W|w|B|b}
X world/group read/write/both
X
X This checks determines whether a file is (group or world)
X writable or readable, and prints out a short message to
X that effect.
X
X Permissions bits: vvv--- Permission bits
X 1 = execute 00000
X 2 = writable ^
X 4 = readable + Setuid bits
X
X Setuid bits:
X 1 = sticky
X 2 = set group id
X 4 = set user od
X
X Pete Shipley (shipley at mica.berkeley.edu) gutted my original code,
X made in cleaner and smarter, and combined everything into one compact
X file. What a deal, huh? Then I came along and beat up his code and
X made it look ugly again (I changed the is_writeable option to return
X true if _any_ parent directories are writable, not just the target. So
X you can blame me if you want. Better yet, just send me a patch if I
X blew it.)
X
X*/
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <ctype.h>
X#include <stdio.h>
X
X#define G_READ_TEST 00044 /* group (or world) readable */
X#define W_READ_TEST 00004 /* world readable */
X#define G_READ_STRING "Warning! %s is group readable!\n"
X#define W_READ_STRING "Warning! %s is _World_ readable!\n"
X
X#define G_WRITE_TEST 00022 /* group (or world) writable */
X#define W_WRITE_TEST 00002 /* world writable */
X#define G_WRITE_STRING "Warning! %s is group writable!\n"
X#define W_WRITE_STRING "Warning! %s is _World_ writable!\n"
X
Xmain(argc,argv)
Xint argc;
Xchar **argv;
X{
Xchar file[256], wg, rwb, gstring[35],wstring[35];
Xregister int group, read, write, both, verbose, xmode;
Xstatic struct stat statb;
X
Xgroup=read=write=both=verbose=xmode=0;
X
X/* check out arguments */
Xif (argc != 4) {
X fprintf(stderr, "Usage: %s file {W|w|G|g} {R|r|W|w|B|b}\n",argv[0]);
X exit(1);
X }
X
X/* parse arguments */
Xstrcpy(file, argv[1]);
X
X/* get stats on file in question -- if doesn't exist, exit */
Xif (stat(file,&statb) < 0) {
X perror(file);
X exit(2);
X }
X
Xif (isupper(argv[2][0]))
X wg = tolower(argv[2][0]); /* world or group */
Xelse wg = argv[2][0]; /* world or group */
X
Xif (isupper(argv[3][0]))
X rwb = tolower(argv[3][0]); /* read/write/both */
Xelse rwb = argv[3][0]; /* read/write/both */
X
X/* set the report string and some flags */
Xif (wg == 'g') group = 1;
X
Xif (rwb == 'r') {
X if (group) strcpy(gstring, G_READ_STRING);
X else strcpy(wstring, W_READ_STRING);
X read = 1;
X }
Xelse if (rwb == 'w') {
X if (group) strcpy(gstring, G_WRITE_STRING);
X else strcpy(wstring, W_WRITE_STRING);
X write = 1;
X }
Xelse if (rwb == 'b') {
X /* do the write first, then read check */
X if (group) strcpy(gstring, G_WRITE_STRING);
X else strcpy(wstring, W_WRITE_STRING);
X both = read = write = 1;
X }
X
X/*
X * the write stuff, so to speak...
X * What I'm doing in this mess is to parse the file in question, check out
X * whole path; 'cause if anything is world writable, you can compromise.
X * For instance, if /usr is world writable, then /usr/spool/mail is
X * compromisable, no matter what its permissions are.
X *
X*/
Xif (write) {
X /* 256 levels of dirs, max len each 256 chars */
X char foo_dirs[256][256];
X char *foo_file;
X int i = 0, j;
X
X foo_file = file;
X strcpy(foo_dirs[i++], foo_file);
X
X j=strlen(foo_file) - 1;
X do {
X if (foo_file[j] == '/')
X strncpy(foo_dirs[i++], foo_file, j);
X } while (--j > 0);
X
X for (j = 0; j < i; j++) {
X if (stat(foo_dirs[j],&statb) < 0)
X continue;
X xmode=statb.st_mode;
X if (!group) {
X if (xmode & W_WRITE_TEST) {
X printf( wstring, file);
X if (both) goto bboth;
X exit(!xmode);
X }
X }
X else if (xmode & G_WRITE_TEST) {
X printf(gstring, file);
X if (both) goto bboth;
X exit(!xmode);
X }
X }
X
Xif (!both) exit(!xmode);
X}
X
Xbboth: if (rwb == 'b') {
X /* do the read now */
X if (group) strcpy(gstring, G_READ_STRING);
X else strcpy(wstring, W_READ_STRING);
X }
X
X/*
X * For the rest -- only flag if the file/dir in question passes test; you
X * don't care about the rest of it's tree
X *
X*/
X
X/* test premissions on file in question */
Xif (group)
X xmode = statb.st_mode & G_READ_TEST;
Xelse
X xmode = statb.st_mode & W_READ_TEST;
X
X/* report finding */
Xif (xmode) printf( (group ? gstring : wstring), file);
X
Xexit(!xmode);
X}
SHAR_EOF
chmod 0600 is_able.c || echo "restore of is_able.c fails"
if [ $TOUCH = can ]
then
touch -am 1120144490 is_able.c
fi
exit 0
--
-- dan
df at cert.sei.cmu.edu
More information about the Alt.sources
mailing list