Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 1 Apr 1995 03:44:11 +0800 (CST)
From:      Brian Tao <taob@gate.sinica.edu.tw>
To:        FREEBSD-SECURITY-L <freebsd-security@FreeBSD.org>
Subject:   New /etc/security script for FreeBSD
Message-ID:  <Pine.BSI.3.91.950401032811.1567K-200000@aries.ibms.sinica.edu.tw>

index | next in thread | raw e-mail

[-- Attachment #1 --]
    A little while ago, I posted on freebsd-hackers that BSD/OS had a
nice /etc/security script that seems to work well on a standard
FreeBSD system.  I've asked BSDI and there isn't a problem
redistributing the file because they don't have a copyright on it.

    I've attached the script to this message and seek comments on it.
Two functional changes have been made to the script.  The first is
changing line 33 to reflect FreeBSD's 8-char username limit.  The
second is the addition of a checksum module starting at line 557.  It
uses md5(1) to calculate checksums for all files in a specified set of
directories chosen to contain system binaries.  It's purpose is to aid
in the detection of trojan horses.

    The standard FreeBSD /etc/security script is rather anemic in
comparison.  Could this be included as the standard script in future
FreeBSD distributions?  I just looked on a friend's NetBSD 1.0 machine
and this is the file they use.
-- 
Brian ("Though this be madness, yet there is method in't") Tao
taob@gate.sinica.edu.tw <-- work ........ play --> taob@io.org

[-- Attachment #2 --]
#!/bin/sh -
#
#	@(#)security	8.1 (Berkeley) 6/9/93
#

PATH=/sbin:/usr/sbin:/bin:/usr/bin

umask 077

ERR=/tmp/_secure1.$$
TMP1=/tmp/_secure2.$$
TMP2=/tmp/_secure3.$$
TMP3=/tmp/_secure4.$$
LIST=/tmp/_secure5.$$
OUTPUT=/tmp/_secure6.$$

trap 'rm -f $ERR $TMP1 $TMP2 $TMP3 $LIST $OUTPUT' 0


#
# Check the master password file syntax.
#
MP=/etc/master.passwd
awk -F: '{
	if ($0 ~ /^[	 ]*$/) {
		printf("Line %d is a blank line.\n", NR);
		next;
	}
	if (NF != 10)
		printf("Line %d has the wrong number of fields.\n", NR);
	if ($1 !~ /^[A-Za-z0-9]*$/)
		printf("Login %s has non-alphanumeric characters.\n", $1);
	if (length($1) > 8)
		printf("Login %s has more than 8 characters.\n", $1);
	if ($2 == "")
		printf("Login %s has no password.\n", $1);
	if (length($2) != 34 && ($10 ~ /.*sh$/ || $10 == ""))
		printf("Login %s is off but still has a valid shell.\n", $1);
	if ($3 == 0 && $1 != "root" && $1 != "toor")
		printf("Login %s has a user id of 0.\n", $1);
	if ($3 < 0)
		printf("Login %s has a negative user id.\n", $1);
	if ($4 < 0)
		printf("Login %s has a negative group id.\n", $1);
}' < $MP > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking the $MP file:\n"
	cat $OUTPUT
fi

awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\n$MP has duplicate user names.\n"
	column $OUTPUT
fi

awk -F: '{ print $1 " " $3 }' $MP | sort -n +1 | tee $TMP1 |
uniq -d -f 1 | awk '{ print $2 }' > $TMP2
if [ -s $TMP2 ] ; then
	printf "\n$MP has duplicate user id's.\n"
        while read uid; do
                grep -w $uid $TMP1
        done < $TMP2 | column
fi


#
# Backup the master password file; a special case, the normal backup
# mechanisms also print out file differences and we don't want to do
# that because this file has encrypted passwords in it.
#
CUR=/var/backups/`basename $MP`.current
BACK=/var/backups/`basename $MP`.backup
if [ -s $CUR ] ; then
	if cmp -s $CUR $MP; then
		:
	else
		cp -p $CUR $BACK
		cp -p $MP $CUR
		chown root.wheel $CUR
	fi
else
	cp -p $MP $CUR
	chown root.wheel $CUR
fi


#
# Check the group file syntax.
#
GRP=/etc/group
awk -F: '{
	if ($0 ~ /^[	 ]*$/) {
		printf("Line %d is a blank line.\n", NR);
		next;
	}
	if (NF != 4)
		printf("Line %d has the wrong number of fields.\n", NR);
	if ($1 !~ /^[A-za-z0-9]*$/)
		printf("Group %s has non-alphanumeric characters.\n", $1);
	if (length($1) > 8)
		printf("Group %s has more than 8 characters.\n", $1);
	if ($3 !~ /[0-9]*/)
		printf("Login %s has a negative group id.\n", $1);
}' < $GRP > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking the $GRP file:\n"
	cat $OUTPUT
fi

awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\n$GRP has duplicate group names.\n"
	column $OUTPUT
fi


#
# Check for root paths, umask values in startup files.
# The check for the root paths is problematical -- it's likely to fail
# in other environments.  Once the shells have been modified to warn
# of '.' in the path, the path tests should go away.
#
> $OUTPUT
rhome=/root
umaskset=no
list="/etc/csh.cshrc /etc/csh.login ${rhome}/.cshrc ${rhome}/.login"
for i in $list ; do
	if [ -f $i ] ; then
		if egrep umask $i > /dev/null ; then
			umaskset=yes
		fi
		egrep umask $i |
		awk '$2 % 100 < 20 \
			{ print "Root umask is group writeable" }
		     $2 % 10 < 2 \
			{ print "Root umask is other writeable" }' >> $OUTPUT
		/bin/csh -f -s << end-of-csh > /dev/null 2>&1
			unset path
			source $i
			/bin/ls -ldgT \$path > $TMP1
end-of-csh
		awk '{
			if ($10 ~ /^\.$/) {
				print "The root path includes .";
				next;
			}
		     }
		     $1 ~ /^d....w/ \
        { print "Root path directory " $10 " is group writeable." } \
		     $1 ~ /^d.......w/ \
        { print "Root path directory " $10 " is other writeable." }' \
		< $TMP1 >> $OUTPUT
	fi
done
if [ $umaskset = "no" -o -s $OUTPUT ] ; then
	printf "\nChecking root csh paths, umask values:\n$list\n"
	if [ -s $OUTPUT ]; then
		cat $OUTPUT
	fi
	if [ $umaskset = "no" ] ; then
		printf "\nRoot csh startup files do not set the umask.\n"
	fi
fi

> $OUTPUT
rhome=/root
umaskset=no
list="${rhome}/.profile"
for i in $list; do
	if [ -f $i ] ; then
		if egrep umask $i > /dev/null ; then
			umaskset=yes
		fi
		egrep umask $i |
		awk '$2 % 100 < 20 \
			{ print "Root umask is group writeable" } \
		     $2 % 10 < 2 \
			{ print "Root umask is other writeable" }' >> $OUTPUT
		/bin/sh << end-of-sh > /dev/null 2>&1
			PATH=
			. $i
			list=\`echo \$PATH | /usr/bin/sed -e 's/:/ /g'\`
			/bin/ls -ldgT \$list > $TMP1
end-of-sh
		awk '{
			if ($10 ~ /^\.$/) {
				print "The root path includes .";
				next;
			}
		     }
		     $1 ~ /^d....w/ \
        { print "Root path directory " $10 " is group writeable." } \
		     $1 ~ /^d.......w/ \
        { print "Root path directory " $10 " is other writeable." }' \
		< $TMP1 >> $OUTPUT

	fi
done
if [ $umaskset = "no" -o -s $OUTPUT ] ; then
	printf "\nChecking root sh paths, umask values:\n$list\n"
	if [ -s $OUTPUT ]; then
		cat $OUTPUT
	fi
	if [ $umaskset = "no" ] ; then
		printf "\nRoot sh startup files do not set the umask.\n"
	fi
fi


#
# Root and uucp should both be in /etc/ftpusers.
#
if egrep root /etc/ftpusers > /dev/null ; then
	:
else
	printf "\nRoot not listed in /etc/ftpusers file.\n"
fi
if egrep uucp /etc/ftpusers > /dev/null ; then
	:
else
	printf "\nUucp not listed in /etc/ftpusers file.\n"
fi

# Uudecode should not be in the /etc/aliases file.
if egrep 'uudecode:.*\||decode:.*\|' /etc/aliases; then
        printf "\nProgram entry for uudecode exists in the /etc/aliases file.\n"
fi

# Files that should not have + signs.
list="/etc/hosts.equiv /etc/hosts.lpd"
for f in $list ; do
	if egrep '\+' $f > /dev/null ; then
		printf "\nPlus sign in $f file.\n"
	fi
done


#
# Check for special users with .rhosts files.  Only root and toor should
# have a .rhosts files.  Also, .rhosts files should not have plus signs.
#
awk -F: '$1 != "root" && $1 != "toor" && \
	($3 < 100 || $1 == "ftp" || $1 == "uucp") \
		{ print $1 " " $6 }' /etc/passwd |
while read uid homedir; do
	if [ -f ${homedir}/.rhosts ] ; then
		rhost=`ls -ldgT ${homedir}/.rhosts`
		printf "$uid: $rhost\n"
	fi
done > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking for special users with .rhosts files:\n"
	cat $OUTPUT
fi

awk -F: '{ print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
	if [ -f ${homedir}/.rhosts ] && \
	    egrep '\+' ${homedir}/.rhosts > /dev/null 2>&1; then
		printf "$uid: + in .rhosts file.\n"
	fi
done > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking .rhosts files syntax:\n"
	cat $OUTPUT
fi


#
# Check home directories.  Directories should not be owned by someone else
# or writeable.
#
awk -F: '{ print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
	if [ -d ${homedir}/ ] ; then
		file=`ls -ldgT ${homedir}`
		printf "$uid $file\n"
	fi
done |
awk '$1 != $4 && $4 != "root" \
	{ print "user " $1 " home directory is owned by " $4 }
     $2 ~ /^-....w/ \
	{ print "user " $1 " home directory is group writeable" }
     $2 ~ /^-.......w/ \
	{ print "user " $1 " home directory is other writeable" }' > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking home directories:\n"
	cat $OUTPUT
fi


#
# Files that should not be owned by someone else or readable.
#
list=".netrc .rhosts"
awk -F: '{ print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
	for f in $list ; do
		file=${homedir}/${f}
		if [ -f $file ] ; then
			printf "$uid $f `ls -ldgT $file`\n"
		fi
	done
done |
awk '$1 != $5 && $5 != "root" \
	{ print "user " $1 " " $2 " file is owned by " $5 }
     $3 ~ /^-...r/ \
	{ print "user " $1 " " $2 " file is group readable" }
     $3 ~ /^-......r/ \
	{ print "user " $1 " " $2 " file is other readable" }
     $3 ~ /^-....w/ \
	{ print "user " $1 " " $2 " file is group writeable" }
     $3 ~ /^-.......w/ \
	{ print "user " $1 " " $2 " file is other writeable" }' > $OUTPUT


#
# Files that should not be owned by someone else or writeable.
#
list=".bashrc .cshrc .emacsrc .exrc .forward .klogin .login .logout \
      .profile .tcshrc"
awk -F: '{ print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
	for f in $list ; do
		file=${homedir}/${f}
		if [ -f $file ] ; then
			printf "$uid $f `ls -ldgT $file`\n"
		fi
	done
done |
awk '$1 != $5 && $5 != "root" \
	{ print "user " $1 " " $2 " file is owned by " $5 }
     $3 ~ /^-....w/ \
	{ print "user " $1 " " $2 " file is group writeable" }
     $3 ~ /^-.......w/ \
	{ print "user " $1 " " $2 " file is other writeable" }' >> $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking dot files:\n"
	cat $OUTPUT
fi


#
# Mailboxes should be owned by user and unreadable.
#
ls -l /var/mail | sed 1d | \
awk '$3 != $9 && $9 != "."$3".pop"\
	{ print "user " $9 " mailbox is owned by " $3 }
     $1 != "-rw-------" \
	{ print "user " $9 " mailbox is " $1 ", group " $4 }' > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking mailbox ownership:\n"
	cat $OUTPUT
fi


#
# File systems should not be globally exported.
#
awk '{
	readonly = 0;
	for (i = 2; i <= NF; ++i) {
		if ($i ~ /-ro/)
			readonly = 1;
		else if ($i !~ /^-/)
			next;
	}
	if (readonly)
		print "File system " $1 " globally exported, read-only."
	else
		print "File system " $1 " globally exported, read-write."
}' < /etc/exports > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking for globally exported file systems:\n"
	cat $OUTPUT
fi


#
# Display any changes in setuid files and devices.
#
printf "\nChecking setuid files and devices:\n"
(find / ! -fstype local -a -prune -o \
    \( -perm -u+s -o -perm -g+s -o ! -type d -a ! -type f -a ! -type l -a \
       ! -type s \) | \
sort | sed -e 's/^/ls -ldgT /' | sh > $LIST) 2> $OUTPUT

# Display any errors that occurred during system file walk.
if [ -s $OUTPUT ] ; then
	printf "Setuid/device find errors:\n"
	cat $OUTPUT
	printf "\n"
fi

# Display any changes in the setuid file list.
egrep -v '^[bc]' $LIST > $TMP1
if [ -s $TMP1 ] ; then
	# Check to make sure uudecode isn't setuid.
	if grep -w uudecode $TMP1 > /dev/null ; then
		printf "\nUudecode is setuid.\n"
	fi

	CUR=/var/backups/setuid.current
	BACK=/var/backups/setuid.backup

	if [ -s $CUR ] ; then
		if cmp -s $CUR $TMP1 ; then
			:
		else
			> $TMP2
			join -110 -210 -v2 $CUR $TMP1 > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Setuid additions:\n"
				tee -a $TMP2 < $OUTPUT
				printf "\n"
			fi

			join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Setuid deletions:\n"
				tee -a $TMP2 < $OUTPUT
				printf "\n"
			fi

			sort +9 $TMP2 $CUR $TMP1 | \
			    sed -e 's/[	 ][	 ]*/ /g' | uniq -u > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Setuid changes:\n"
				column -t $OUTPUT
				printf "\n"
			fi

			cp $CUR $BACK
			cp $TMP1 $CUR
		fi
	else
		printf "Setuid additions:\n"
		column -t $TMP1
		printf "\n"
		cp $TMP1 $CUR
	fi
fi


#
# Check for block and character disk devices that are readable or writeable
# or not owned by root.operator.
#
>$TMP1
DISKLIST="dk hd hk hp jb kra ra rb rd rl rx rz sd up wd"
for i in $DISKLIST; do
	egrep "^b.*/${i}[0-9][0-9]*[a-h]$"  $LIST >> $TMP1
	egrep "^c.*/r${i}[0-9][0-9]*[a-h]$"  $LIST >> $TMP1
done

awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
	{ printf("Disk %s is user %s, group %s, permissions %s.\n", \
	    $11, $3, $4, $1); }' < $TMP1 > $OUTPUT
if [ -s $OUTPUT ] ; then
	printf "\nChecking disk ownership and permissions:\n"
	cat $OUTPUT
	printf "\n"
fi


#
# Display any changes in the device file list.
#
egrep '^[bc]' $LIST | sort +10 > $TMP1
if [ -s $TMP1 ] ; then
	CUR=/var/backups/device.current
	BACK=/var/backups/device.backup

	if [ -s $CUR ] ; then
		if cmp -s $CUR $TMP1 ; then
			:
		else
			> $TMP2
			join -111 -211 -v2 $CUR $TMP1 > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Device additions:\n"
				tee -a $TMP2 < $OUTPUT
				printf "\n"
			fi

			join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Device deletions:\n"
				tee -a $TMP2 < $OUTPUT
				printf "\n"
			fi

			# Report any block device change.  Ignore character
			# devices, only the name is significant.
			cat $TMP2 $CUR $TMP1 | \
			sed -e '/^c/d' | \
			sort +10 | \
			sed -e 's/[	 ][	 ]*/ /g' | \
			uniq -u > $OUTPUT
			if [ -s $OUTPUT ] ; then
				printf "Block device changes:\n"
				column -t $OUTPUT
				printf "\n"
			fi

			cp $CUR $BACK
			cp $TMP1 $CUR
		fi
	else
		printf "Device additions:\n"
		column -t $TMP1
		printf "\n"
		cp $TMP1 $CUR
	fi
fi


#
# Check special files.
# Check system binaries.
#
# Create the mtree tree specifications using:
#
#	mtree -cx -pDIR -kcksum,gid,mode,nlink,size,link,time,uid > DIR.secure
#	chown root.wheel DIR.SECURE
#	chmod 600 DIR.SECURE
#
# Note, this is not complete protection against Trojan horsed binaries, as
# the hacker can modify the tree specification to match the replaced binary.
# For details on really protecting yourself against modified binaries, see
# the mtree(8) manual page.
#
if cd /etc/mtree; then
	mtree -e -p / -f /etc/mtree/special > $OUTPUT
	if [ -s $OUTPUT ] ; then
		printf "\nChecking special files and directories:\n"
		cat $OUTPUT
	fi

	> $OUTPUT
	for file in *.secure; do
		tree=`sed -n -e '3s/.* //p' -e 3q $file 2>/dev/null`
		mtree -f $file -p $tree > $TMP1 2>/dev/null
		if [ -s $TMP1 ]; then
			printf "\nChecking $tree:\n" >> $OUTPUT
			cat $TMP1 >> $OUTPUT
		fi
	done
	if [ -s $OUTPUT ] ; then
		printf "\nChecking system binaries:\n"
		cat $OUTPUT
	fi
fi


# 
# Checksum system binaries and look for differences.  Although
# discrepancies flagged in this section may indicate a trojan horse
# binary, *no* discrepancy does *not* mean you are in the clear!
# Keep a copy of the checksums on a secure, remote host for better
# protection.
#

rm $TMP1
for dir in /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/X11/bin ; do
	if [ -d $dir ] ; then
		cd $dir
		ls -1 | xargs md5 >> $TMP1
	fi
done

CUR="/var/backups/md5.current"
BACK="/var/backups/md5.backup"
if [ -s $CUR ] ; then
	diff $CUR $TMP1 > $OUTPUT
	if [ -s $OUTPUT ] ; then
	printf "\n======\nMD5 diffs (OLD < > NEW)\n======\n"
		cat $OUTPUT
		cp -p $CUR $BACK
		cp -p $TMP1 $CUR
		chown root.wheel $CUR $BACK
		chmod 600 $CUR $BACK
	fi
else
	cp -p $TMP1 $CUR
	chown root.wheel $CUR
	chmod 600 $CUR
fi


#
# List of files that get backed up and checked for any modifications.  Each
# file is expected to have two backups, /var/backups/file.{current,backup}.
# Any changes cause the files to rotate.
#
if [ -s /etc/changelist ] ; then
	for file in `cat /etc/changelist`; do
		CUR=/var/backups/`basename $file`.current
		BACK=/var/backups/`basename $file`.backup
		if [ -s $file ]; then
			if [ -s $CUR ] ; then
				diff $CUR $file > $OUTPUT
				if [ -s $OUTPUT ] ; then
		printf "\n======\n%s diffs (OLD < > NEW)\n======\n" $file
					cat $OUTPUT
					cp -p $CUR $BACK
					cp -p $file $CUR
					chown root.wheel $CUR $BACK
				fi
			else
				cp -p $file $CUR
				chown root.wheel $CUR
			fi
		fi
	done
fi
help

Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.BSI.3.91.950401032811.1567K-200000>