Source code pulled from OpenBSD for OpenNTPD. The place to contribute to this code is via the OpenBSD CVS tree.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

683 lines
18 KiB

#!/bin/sh -
#
# $OpenBSD: security,v 1.53 2002/07/23 18:26:35 pvalchev Exp $
# from: @(#)security 8.1 (Berkeley) 6/9/93
#
PATH=/sbin:/usr/sbin:/bin:/usr/bin
umask 077
DIR=`mktemp -d /tmp/_secure.XXXXXX` || exit 1
ERR=$DIR/_secure1
TMP1=$DIR/_secure2
TMP2=$DIR/_secure3
TMP3=$DIR/_secure4
LIST=$DIR/_secure5
OUTPUT=$DIR/_secure6
trap 'rm -rf $DIR; exit 1' 0 1 2 3 13 15
# 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%s\n", NR, $0);
if ($1 ~ /^[+-]/)
next;
if ($1 == "")
printf("Line %d has an empty login field:\n%s\n", NR, $0);
else if ($1 !~ /^[A-Za-z0-9_][A-Za-z0-9_-]*$/)
printf("Login %s has non-alphanumeric characters.\n", $1);
if (length($1) > 31)
printf("Login %s has more than 31 characters.\n", $1);
if ($2 == "")
printf("Login %s has no password.\n", $1);
if ($2 != "" && length($2) != 13 && ($10 ~ /.*sh$/ || $10 == "") &&
($2 !~ /^\$[0-9a-f]+\$/) && ($2 != "skey")) {
if (system("test -s /etc/skey/"$1"") == 0)
printf("Login %s is off but still has a valid shell and an entry in /etc/skey.\n", $1);
if (system("test -d "$9" -a ! -r "$9"") == 0)
printf("Login %s is off but still has valid shell and home directory is unreadable\n\t by root; cannot check for existence of alternate access files.\n", $1);
else if (system("for file in .ssh .rhosts .shosts .klogin; do if test -e "$9"/$file; then if ((ls -ld "$9"/$file | cut -b 2-10 | grep -q r) && (test ! -O "$9"/$file)) ; then exit 1; fi; fi; done"))
printf("Login %s is off but still has a valid shell and alternate access files in\n\t home directory are still readable.\n",$1);
}
if ($3 == 0 && $1 != "root")
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);
if ($7 != 0 && system("test "$7" -lt `date +%s`") == 0)
printf("Login %s has expired.\n", $1);
}' < $MP > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking the ${MP} file:"
cat $OUTPUT
fi
awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\n${MP} has duplicate user names."
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
echo "\n${MP} has duplicate user ID's."
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.
if [ ! -d /var/backups ] ; then
mkdir /var/backups
chmod 700 /var/backups
fi
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 ($1 ~ /^[+-].*$/)
next;
if (NF != 4)
printf("Line %d has the wrong number of fields:\n%s\n", NR, $0);
if ($1 !~ /^[A-za-z0-9][A-za-z0-9_-]*$/)
printf("Group %s has non-alphanumeric characters.\n", $1);
if (length($1) > 31)
printf("Group %s has more than 31 characters.\n", $1);
if ($3 !~ /[0-9]*/)
printf("Login %s has a negative group ID.\n", $1);
}' < $GRP > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking the ${GRP} file:"
cat $OUTPUT
fi
awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\n${GRP} has duplicate group names."
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 [ -s $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
SAVE_PATH=$PATH
unset PATH
/bin/csh -f -s << end-of-csh > /dev/null 2>&1
source $i
if (\$?path) then
/bin/ls -ldgT \$path > $TMP1
else
cat /dev/null > $TMP1
endif
end-of-csh
PATH=$SAVE_PATH
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
echo "\nChecking root csh paths, umask values:\n${list}"
if [ -s $OUTPUT ] ; then
cat $OUTPUT
fi
if [ $umaskset = "no" ] ; then
echo "\nRoot csh startup files do not set the umask."
fi
fi
> $OUTPUT
> $TMP2
rhome=/root
umaskset=no
list="/etc/profile ${rhome}/.profile"
for i in $list; do
if [ -s $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
SAVE_PATH=$PATH
SAVE_ENV=$ENV
unset PATH ENV
/bin/sh << end-of-sh > /dev/null 2>&1
. $i
if [ X"\$PATH" != "X" ]; then
list=\`echo \$PATH | /usr/bin/sed -e 's/:/ /g'\`
/bin/ls -ldgT \$list > $TMP1
else
> $TMP1
fi
echo \$ENV >> $TMP2
end-of-sh
PATH=$SAVE_PATH
ENV=$SAVE_ENV
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
echo "\nChecking root sh paths, umask values:\n${list}"
if [ -s $OUTPUT ] ; then
cat $OUTPUT
fi
if [ $umaskset = "no" ] ; then
echo "\nRoot sh startup files do not set the umask."
fi
fi
# A good .kshrc will not have a umask or path, that being set in .profile
# check anyway.
> $OUTPUT
rhome=/root
list="/etc/ksh.kshrc `cat $TMP2`"
(cd $rhome
for i in $list; do
if [ -s $i ] ; then
egrep umask $i |
awk '$2 % 100 < 20 \
{ print "Root umask is group writeable" } \
$2 % 10 < 2 \
{ print "Root umask is other writeable" }' >> $OUTPUT
if egrep PATH= $i > /dev/null ; then
SAVE_PATH=$PATH
unset PATH
/bin/ksh << end-of-sh > /dev/null 2>&1
. $i
if [ X"\$PATH" != "X" ]; then
list=\`echo \$PATH | /usr/bin/sed -e 's/:/ /g'\`
/bin/ls -ldgT \$list > $TMP1
else
> $TMP1
fi
end-of-sh
PATH=$SAVE_PATH
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
fi
done
)
if [ -s $OUTPUT ] ; then
echo "\nChecking root ksh paths, umask values:\n${list}"
cat $OUTPUT
fi
# Root and uucp should both be in /etc/ftpusers.
if egrep root /etc/ftpusers > /dev/null ; then
:
else
echo "\nRoot not listed in /etc/ftpusers file."
fi
if egrep uucp /etc/ftpusers > /dev/null ; then
:
else
echo "\nUucp not listed in /etc/ftpusers file."
fi
# Uudecode should not be in the /etc/mail/aliases file.
if egrep 'uudecode|decode' /etc/mail/aliases; then
echo "\nThere is an entry for uudecode in the /etc/mail/aliases file."
fi
# Files that should not have + signs.
list="/etc/hosts.equiv /etc/shosts.equiv /etc/hosts.lpd"
for f in $list ; do
if [ -s $f ] ; then
awk '{
if ($0 ~ /^\+@.*$/)
next;
if ($0 ~ /^\+.*$/)
printf("\nPlus sign in %s file.\n", FILENAME);
}' $f
fi
done
# Check for special users with .rhosts/.shosts files. Only root
# should have .rhosts/.shosts files. Also, .rhosts/.shosts
# files should not have plus signs.
awk -F: '$1 != "root" && $1 !~ /^[+-]/ && \
($3 < 100 || $1 == "ftp" || $1 == "uucp") \
{ print $1 " " $6 }' /etc/passwd |
while read uid homedir; do
for j in .rhosts .shosts; do
# Root owned .rhosts/.shosts files are ok.
if [ -s ${homedir}/$j -a ! -O ${homedir}/$j ] ; then
rhost=`ls -ldgT ${homedir}/$j`
echo "${uid}: ${rhost}"
fi
done
done > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking for special users with .rhosts/.shosts files."
cat $OUTPUT
fi
awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
for j in .rhosts .shosts; do
if [ -s ${homedir}/$j ] ; then
awk '{
if ($0 ~ /^+@.*$/ )
next;
if ($0 ~ /^\+[ ]*$/ )
printf("%s has + sign in it.\n",
FILENAME);
}' ${homedir}/$j
fi
done
done > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking .rhosts/.shosts files syntax."
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}`
echo "${uid} ${file}"
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
echo "\nChecking home directories."
cat $OUTPUT
fi
# Files that should not be owned by someone else or readable.
list=".netrc .rhosts .gnupg/secring.gpg .gnupg/random_seed \
.pgp/secring.pgp .shosts .ssh/identity .ssh/id_dsa .ssh/id_rsa"
awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
for f in $list ; do
file=${homedir}/${f}
if [ -f $file ] ; then
echo "${uid} ${f} `ls -ldgT ${file}`"
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 .bash_profile .bash_login .bash_logout .cshrc \
.emacs .exrc .forward .fvwmrc .inputrc .klogin .kshrc .login \
.logout .nexrc .profile .screenrc .ssh .ssh/config \
.ssh/authorized_keys .ssh/authorized_keys2 .ssh/environment \
.ssh/known_hosts .ssh/rc .tcshrc .twmrc .xsession .xinitrc \
.Xdefaults .Xauthority"
awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
while read uid homedir; do
for f in $list ; do
file=${homedir}/${f}
if [ -f $file ] ; then
echo "${uid} ${f} `ls -ldgT ${file}`"
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
echo "\nChecking dot files."
cat $OUTPUT
fi
# Mailboxes should be owned by user and unreadable.
ls -l /var/mail | sed 1d | \
awk '$3 != $9 \
{ print "user " $9 " mailbox is owned by " $3 }
$1 != "-rw-------" \
{ print "user " $9 " mailbox is " $1 ", group " $4 }' > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking mailbox ownership."
cat $OUTPUT
fi
# File systems should not be globally exported.
if [ -s /etc/exports ] ; then
awk '{
if (($1 ~ /^#/) || ($1 ~ /^$/))
next;
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
echo "\nChecking for globally exported file systems."
cat $OUTPUT
fi
fi
# Display any changes in setuid/setgid files and devices.
pending="\nChecking setuid/setgid files and devices:\n"
(find / \( ! -fstype local -o -fstype fdesc -o -fstype kernfs \
-o -fstype procfs \) -a -prune -o \
-type f -a \( -perm -u+s -o -perm -g+s \) -print0 -o \
! -type d -a ! -type f -a ! -type l -a ! -type s -a ! -type p \
-print0 | xargs -0 ls -ldgT | sort +9 > $LIST) 2> $OUTPUT
# Display any errors that occurred during system file walk.
if [ -s $OUTPUT ] ; then
echo "${pending}Setuid/device find errors:"
pending=
cat $OUTPUT
echo ""
fi
# Display any changes in the setuid/setgid 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
echo "${pending}\nUudecode is setuid."
pending=
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
echo "${pending}Setuid additions:"
pending=
tee -a $TMP2 < $OUTPUT
echo ""
fi
join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "${pending}Setuid deletions:"
pending=
tee -a $TMP2 < $OUTPUT
echo ""
fi
sort +9 $TMP2 $CUR $TMP1 | \
sed -e 's/[ ][ ]*/ /g' | uniq -u > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "${pending}Setuid changes:"
pending=
column -t $OUTPUT
echo ""
fi
cp $CUR $BACK
cp $TMP1 $CUR
fi
else
echo "${pending}Setuid additions:"
pending=
column -t $TMP1
echo ""
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="ccd dk fd hd hk hp jb kra ra rb rd rl rx rz sd up vnd wd xd"
for i in $DISKLIST; do
egrep "^b.*/${i}[0-9][0-9]*[B-H]?[a-p]$" $LIST >> $TMP1
egrep "^c.*/r${i}[0-9][0-9]*[B-H]?[a-p]$" $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
echo "\nChecking disk ownership and permissions."
cat $OUTPUT
echo ""
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
echo "Device additions:"
tee -a $TMP2 < $OUTPUT
echo ""
fi
join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "Device deletions:"
tee -a $TMP2 < $OUTPUT
echo ""
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
echo "Block device changes:"
column -t $OUTPUT
echo ""
fi
cp $CUR $BACK
cp $TMP1 $CUR
fi
else
echo "Device additions:"
column -t $TMP1
echo ""
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 [ -d /etc/mtree ] ; then
cd /etc/mtree
mtree -e -l -p / -f /etc/mtree/special > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\nChecking special files and directories."
echo "Output format is:\n\tfilename:"
echo "\t\tcriteria (shouldbe, reallyis)"
cat $OUTPUT
fi
> $OUTPUT
for file in *.secure; do
[ $file = '*.secure' ] && continue
tree=`sed -n -e '3s/.* //p' -e 3q $file`
mtree -f $file -p $tree > $TMP1
if [ -s $TMP1 ] ; then
echo "\nChecking ${tree}:" >> $OUTPUT
cat $TMP1 >> $OUTPUT
fi
done
if [ -s $OUTPUT ] ; then
echo "\nChecking system binaries:"
cat $OUTPUT
fi
else
echo /etc/mtree is missing
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.
_fnchg() {
echo "$1" | sed 's/^\///;s/\//_/g'
}
if [ -s /etc/changelist ] ; then
for file in `egrep -v "^(#|\+|$MP)" /etc/changelist`; do
CUR=/var/backups/$(_fnchg "$file").current
BACK=/var/backups/$(_fnchg "$file").backup
if [ -s $file -a ! -d $file ] ; then
if [ -s $CUR ] ; then
diff -u $CUR $file > $OUTPUT
if [ -s $OUTPUT ] ; then
echo "\n======\n${file} diffs (-OLD +NEW)\n======"
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
for file in `egrep "^\+" /etc/changelist`; do
file="${file#+}"
CUR=/var/backups/$(_fnchg "$file").current.md5
BACK=/var/backups/$(_fnchg "$file").backup.md5
if [ -s $file -a ! -d $file ] ; then
MD5_NEW=`md5 $file | sed 's/^.* //'`
if [ -s $CUR ] ; then
MD5_OLD="`cat $CUR`"
if [ "$MD5_NEW" != "$MD5_OLD" ]; then
echo "\n======\n${file} MD5 checksums\n======"
echo "OLD: $MD5_OLD"
echo "NEW: $MD5_NEW"
cp -p $CUR $BACK
echo $MD5_NEW > $CUR
chown root.wheel $CUR $BACK
chmod 600 $CUR
fi
else
echo $MD5_NEW > $CUR
chown root.wheel $CUR
chmod 600 $CUR
fi
fi
done
fi