Home | History | Annotate | Line # | Download | only in etc
security revision 1.13
      1 #!/bin/sh -
      2 #
      3 #	$NetBSD: security,v 1.13 1996/01/14 00:58:25 pk Exp $
      4 #	from: @(#)security	8.1 (Berkeley) 6/9/93
      5 #
      6 
      7 PATH=/sbin:/usr/sbin:/bin:/usr/bin
      8 
      9 umask 077
     10 
     11 ERR=/tmp/_secure1.$$
     12 TMP1=/tmp/_secure2.$$
     13 TMP2=/tmp/_secure3.$$
     14 TMP3=/tmp/_secure4.$$
     15 LIST=/tmp/_secure5.$$
     16 OUTPUT=/tmp/_secure6.$$
     17 
     18 trap 'rm -f $ERR $TMP1 $TMP2 $TMP3 $LIST $OUTPUT' 0
     19 
     20 # Check the master password file syntax.
     21 MP=/etc/master.passwd
     22 awk -F: '{
     23 	if ($0 ~ /^[	 ]*$/) {
     24 		printf("Line %d is a blank line.\n", NR);
     25 		next;
     26 	}
     27 	if (NF != 10)
     28 		printf("Line %d has the wrong number of fields.\n", NR);
     29 	if ($1 !~ /^[A-Za-z0-9]*$/)
     30 		printf("Login %s has non-alphanumeric characters.\n", $1);
     31 	if (length($1) > 8)
     32 		printf("Login %s has more than 8 characters.\n", $1);
     33 	if ($2 == "")
     34 		printf("Login %s has no password.\n", $1);
     35 	if (length($2) != 13 && $2 != "" && ($10 ~ /.*sh$/ || $10 == ""))
     36 		printf("Login %s is off but still has a valid shell.\n", $1);
     37 	if ($3 == 0 && $1 != "root" && $1 != "toor")
     38 		printf("Login %s has a user id of 0.\n", $1);
     39 	if ($3 < 0)
     40 		printf("Login %s has a negative user id.\n", $1);
     41 	if ($4 < 0)
     42 		printf("Login %s has a negative group id.\n", $1);
     43 }' < $MP > $OUTPUT
     44 if [ -s $OUTPUT ] ; then
     45 	printf "\nChecking the $MP file:\n"
     46 	cat $OUTPUT
     47 fi
     48 
     49 awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
     50 if [ -s $OUTPUT ] ; then
     51 	printf "\n$MP has duplicate user names.\n"
     52 	column $OUTPUT
     53 fi
     54 
     55 awk -F: '{ print $1 " " $3 }' $MP | sort -n +1 | tee $TMP1 |
     56 uniq -d -f 1 | awk '{ print $2 }' > $TMP2
     57 if [ -s $TMP2 ] ; then
     58 	printf "\n$MP has duplicate user id's.\n"
     59         while read uid; do
     60                 grep -w $uid $TMP1
     61         done < $TMP2 | column
     62 fi
     63 
     64 # Backup the master password file; a special case, the normal backup
     65 # mechanisms also print out file differences and we don't want to do
     66 # that because this file has encrypted passwords in it.
     67 CUR=/var/backups/`basename $MP`.current
     68 BACK=/var/backups/`basename $MP`.backup
     69 if [ -s $CUR ] ; then
     70 	if cmp -s $CUR $MP; then
     71 		:
     72 	else
     73 		cp -p $CUR $BACK
     74 		cp -p $MP $CUR
     75 		chown root.wheel $CUR
     76 	fi
     77 else
     78 	cp -p $MP $CUR
     79 	chown root.wheel $CUR
     80 fi
     81 
     82 # Check the group file syntax.
     83 GRP=/etc/group
     84 awk -F: '{
     85 	if ($0 ~ /^[	 ]*$/) {
     86 		printf("Line %d is a blank line.\n", NR);
     87 		next;
     88 	}
     89 	if (NF != 4)
     90 		printf("Line %d has the wrong number of fields.\n", NR);
     91 	if ($1 !~ /^[A-za-z0-9]*$/)
     92 		printf("Group %s has non-alphanumeric characters.\n", $1);
     93 	if (length($1) > 8)
     94 		printf("Group %s has more than 8 characters.\n", $1);
     95 	if ($3 !~ /[0-9]*/)
     96 		printf("Login %s has a negative group id.\n", $1);
     97 }' < $GRP > $OUTPUT
     98 if [ -s $OUTPUT ] ; then
     99 	printf "\nChecking the $GRP file:\n"
    100 	cat $OUTPUT
    101 fi
    102 
    103 awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
    104 if [ -s $OUTPUT ] ; then
    105 	printf "\n$GRP has duplicate group names.\n"
    106 	column $OUTPUT
    107 fi
    108 
    109 # Check for root paths, umask values in startup files.
    110 # The check for the root paths is problematical -- it's likely to fail
    111 # in other environments.  Once the shells have been modified to warn
    112 # of '.' in the path, the path tests should go away.
    113 > $OUTPUT
    114 rhome=/root
    115 umaskset=no
    116 list="/etc/csh.cshrc /etc/csh.login ${rhome}/.cshrc ${rhome}/.login"
    117 for i in $list ; do
    118 	if [ -f $i ] ; then
    119 		if egrep umask $i > /dev/null ; then
    120 			umaskset=yes
    121 		fi
    122 		egrep umask $i |
    123 		awk '$2 % 100 < 20 \
    124 			{ print "Root umask is group writeable" }
    125 		     $2 % 10 < 2 \
    126 			{ print "Root umask is other writeable" }' >> $OUTPUT
    127 		/bin/csh -f -s << end-of-csh > /dev/null 2>&1
    128 			unset path
    129 			source $i
    130 			/bin/ls -ldgT \$path > $TMP1
    131 end-of-csh
    132 		awk '{
    133 			if ($10 ~ /^\.$/) {
    134 				print "The root path includes .";
    135 				next;
    136 			}
    137 		     }
    138 		     $1 ~ /^d....w/ \
    139         { print "Root path directory " $10 " is group writeable." } \
    140 		     $1 ~ /^d.......w/ \
    141         { print "Root path directory " $10 " is other writeable." }' \
    142 		< $TMP1 >> $OUTPUT
    143 	fi
    144 done
    145 if [ $umaskset = "no" -o -s $OUTPUT ] ; then
    146 	printf "\nChecking root csh paths, umask values:\n$list\n"
    147 	if [ -s $OUTPUT ]; then
    148 		cat $OUTPUT
    149 	fi
    150 	if [ $umaskset = "no" ] ; then
    151 		printf "\nRoot csh startup files do not set the umask.\n"
    152 	fi
    153 fi
    154 
    155 > $OUTPUT
    156 rhome=/root
    157 umaskset=no
    158 list="${rhome}/.profile"
    159 for i in $list; do
    160 	if [ -f $i ] ; then
    161 		if egrep umask $i > /dev/null ; then
    162 			umaskset=yes
    163 		fi
    164 		egrep umask $i |
    165 		awk '$2 % 100 < 20 \
    166 			{ print "Root umask is group writeable" } \
    167 		     $2 % 10 < 2 \
    168 			{ print "Root umask is other writeable" }' >> $OUTPUT
    169 		/bin/sh << end-of-sh > /dev/null 2>&1
    170 			PATH=
    171 			. $i
    172 			list=\`echo \$PATH | /usr/bin/sed -e 's/:/ /g'\`
    173 			/bin/ls -ldgT \$list > $TMP1
    174 end-of-sh
    175 		awk '{
    176 			if ($10 ~ /^\.$/) {
    177 				print "The root path includes .";
    178 				next;
    179 			}
    180 		     }
    181 		     $1 ~ /^d....w/ \
    182         { print "Root path directory " $10 " is group writeable." } \
    183 		     $1 ~ /^d.......w/ \
    184         { print "Root path directory " $10 " is other writeable." }' \
    185 		< $TMP1 >> $OUTPUT
    186 
    187 	fi
    188 done
    189 if [ $umaskset = "no" -o -s $OUTPUT ] ; then
    190 	printf "\nChecking root sh paths, umask values:\n$list\n"
    191 	if [ -s $OUTPUT ]; then
    192 		cat $OUTPUT
    193 	fi
    194 	if [ $umaskset = "no" ] ; then
    195 		printf "\nRoot sh startup files do not set the umask.\n"
    196 	fi
    197 fi
    198 
    199 # Root and uucp should both be in /etc/ftpusers.
    200 if egrep root /etc/ftpusers > /dev/null ; then
    201 	:
    202 else
    203 	printf "\nRoot not listed in /etc/ftpusers file.\n"
    204 fi
    205 if egrep uucp /etc/ftpusers > /dev/null ; then
    206 	:
    207 else
    208 	printf "\nUucp not listed in /etc/ftpusers file.\n"
    209 fi
    210 
    211 # Uudecode should not be in the /etc/aliases file.
    212 if egrep 'uudecode|decode' /etc/aliases; then
    213 	printf "\nThere is an entry for uudecode in the /etc/aliases file.\n"
    214 fi
    215 
    216 # Files that should not have + signs.
    217 list="/etc/hosts.equiv /etc/hosts.lpd"
    218 for f in $list ; do
    219 	if [ -f $f ] && egrep '\+' $f > /dev/null ; then
    220 		printf "\nPlus sign in $f file.\n"
    221 	fi
    222 done
    223 
    224 # Check for special users with .rhosts files.  Only root and toor should
    225 # have a .rhosts files.  Also, .rhosts files should not plus signs.
    226 awk -F: '$1 != "root" && $1 != "toor" && \
    227 	($3 < 100 || $1 == "ftp" || $1 == "uucp") \
    228 		{ print $1 " " $6 }' /etc/passwd |
    229 while read uid homedir; do
    230 	if [ -f ${homedir}/.rhosts ] ; then
    231 		rhost=`ls -ldgT ${homedir}/.rhosts`
    232 		printf "$uid: $rhost\n"
    233 	fi
    234 done > $OUTPUT
    235 if [ -s $OUTPUT ] ; then
    236 	printf "\nChecking for special users with .rhosts files.\n"
    237 	cat $OUTPUT
    238 fi
    239 
    240 awk -F: '{ print $1 " " $6 }' /etc/passwd | \
    241 while read uid homedir; do
    242 	if [ -f ${homedir}/.rhosts ] && \
    243 	    egrep '\+' ${homedir}/.rhosts > /dev/null ; then
    244 		printf "$uid: + in .rhosts file.\n"
    245 	fi
    246 done > $OUTPUT
    247 if [ -s $OUTPUT ] ; then
    248 	printf "\nChecking .rhosts files syntax.\n"
    249 	cat $OUTPUT
    250 fi
    251 
    252 # Check home directories.  Directories should not be owned by someone else
    253 # or writeable.
    254 awk -F: '{ print $1 " " $6 }' /etc/passwd | \
    255 while read uid homedir; do
    256 	if [ -d ${homedir}/ ] ; then
    257 		file=`ls -ldgT ${homedir}`
    258 		printf "$uid $file\n"
    259 	fi
    260 done |
    261 awk '$1 != $4 && $4 != "root" \
    262 	{ print "user " $1 " home directory is owned by " $4 }
    263      $2 ~ /^-....w/ \
    264 	{ print "user " $1 " home directory is group writeable" }
    265      $2 ~ /^-.......w/ \
    266 	{ print "user " $1 " home directory is other writeable" }' > $OUTPUT
    267 if [ -s $OUTPUT ] ; then
    268 	printf "\nChecking home directories.\n"
    269 	cat $OUTPUT
    270 fi
    271 
    272 # Files that should not be owned by someone else or readable.
    273 list=".netrc .rhosts"
    274 awk -F: '{ print $1 " " $6 }' /etc/passwd | \
    275 while read uid homedir; do
    276 	for f in $list ; do
    277 		file=${homedir}/${f}
    278 		if [ -f $file ] ; then
    279 			printf "$uid $f `ls -ldgT $file`\n"
    280 		fi
    281 	done
    282 done |
    283 awk '$1 != $5 && $5 != "root" \
    284 	{ print "user " $1 " " $2 " file is owned by " $5 }
    285      $3 ~ /^-...r/ \
    286 	{ print "user " $1 " " $2 " file is group readable" }
    287      $3 ~ /^-......r/ \
    288 	{ print "user " $1 " " $2 " file is other readable" }
    289      $3 ~ /^-....w/ \
    290 	{ print "user " $1 " " $2 " file is group writeable" }
    291      $3 ~ /^-.......w/ \
    292 	{ print "user " $1 " " $2 " file is other writeable" }' > $OUTPUT
    293 
    294 # Files that should not be owned by someone else or writeable.
    295 list=".bashrc .cshrc .emacs .exrc .forward .klogin .login .logout \
    296       .profile .tcshrc"
    297 awk -F: '{ print $1 " " $6 }' /etc/passwd | \
    298 while read uid homedir; do
    299 	for f in $list ; do
    300 		file=${homedir}/${f}
    301 		if [ -f $file ] ; then
    302 			printf "$uid $f `ls -ldgT $file`\n"
    303 		fi
    304 	done
    305 done |
    306 awk '$1 != $5 && $5 != "root" \
    307 	{ print "user " $1 " " $2 " file is owned by " $5 }
    308      $3 ~ /^-....w/ \
    309 	{ print "user " $1 " " $2 " file is group writeable" }
    310      $3 ~ /^-.......w/ \
    311 	{ print "user " $1 " " $2 " file is other writeable" }' >> $OUTPUT
    312 if [ -s $OUTPUT ] ; then
    313 	printf "\nChecking dot files.\n"
    314 	cat $OUTPUT
    315 fi
    316 
    317 # Mailboxes should be owned by user and unreadable.
    318 ls -l /var/mail | sed 1d | \
    319 awk '$3 != $9 \
    320 	{ print "user " $9 " mailbox is owned by " $3 }
    321      $1 != "-rw-------" \
    322 	{ print "user " $9 " mailbox is " $1 ", group " $4 }' > $OUTPUT
    323 if [ -s $OUTPUT ] ; then
    324 	printf "\nChecking mailbox ownership.\n"
    325 	cat $OUTPUT
    326 fi
    327 
    328 if [ -f /etc/exports ]; then
    329     # File systems should not be globally exported.
    330     awk '{
    331 	readonly = 0;
    332 	for (i = 2; i <= NF; ++i) {
    333 		if ($i ~ /-ro/)
    334 			readonly = 1;
    335 		else if ($i !~ /^-/)
    336 			next;
    337 	}
    338 	if (readonly)
    339 		print "File system " $1 " globally exported, read-only."
    340 	else
    341 		print "File system " $1 " globally exported, read-write."
    342     }' < /etc/exports > $OUTPUT
    343     if [ -s $OUTPUT ] ; then
    344 	printf "\nChecking for globally exported file systems.\n"
    345 	cat $OUTPUT
    346     fi
    347 fi
    348 
    349 # Display any changes in setuid files and devices.
    350 printf "\nChecking setuid files and devices:\n"
    351 (find / \( ! -fstype local -o -fstype fdesc -o -fstype kernfs \
    352 		-o -fstype procfs \) -a -prune -o \
    353     \( -perm -u+s -o -perm -g+s -o ! -type d -a ! -type f -a ! -type l -a \
    354        ! -type s \) -print | \
    355 sort | sed -e 's/^/ls -ldgT /' | sh > $LIST) 2> $OUTPUT
    356 
    357 # Display any errors that occurred during system file walk.
    358 if [ -s $OUTPUT ] ; then
    359 	printf "Setuid/device find errors:\n"
    360 	cat $OUTPUT
    361 	printf "\n"
    362 fi
    363 
    364 # Display any changes in the setuid file list.
    365 egrep -v '^[bc]' $LIST > $TMP1
    366 if [ -s $TMP1 ] ; then
    367 	# Check to make sure uudecode isn't setuid.
    368 	if grep -w uudecode $TMP1 > /dev/null ; then
    369 		printf "\nUudecode is setuid.\n"
    370 	fi
    371 
    372 	CUR=/var/backups/setuid.current
    373 	BACK=/var/backups/setuid.backup
    374 
    375 	if [ -s $CUR ] ; then
    376 		if cmp -s $CUR $TMP1 ; then
    377 			:
    378 		else
    379 			> $TMP2
    380 			join -110 -210 -v2 $CUR $TMP1 > $OUTPUT
    381 			if [ -s $OUTPUT ] ; then
    382 				printf "Setuid additions:\n"
    383 				tee -a $TMP2 < $OUTPUT
    384 				printf "\n"
    385 			fi
    386 
    387 			join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
    388 			if [ -s $OUTPUT ] ; then
    389 				printf "Setuid deletions:\n"
    390 				tee -a $TMP2 < $OUTPUT
    391 				printf "\n"
    392 			fi
    393 
    394 			sort +9 $TMP2 $CUR $TMP1 | \
    395 			    sed -e 's/[	 ][	 ]*/ /g' | uniq -u > $OUTPUT
    396 			if [ -s $OUTPUT ] ; then
    397 				printf "Setuid changes:\n"
    398 				column -t $OUTPUT
    399 				printf "\n"
    400 			fi
    401 
    402 			cp $CUR $BACK
    403 			cp $TMP1 $CUR
    404 		fi
    405 	else
    406 		printf "Setuid additions:\n"
    407 		column -t $TMP1
    408 		printf "\n"
    409 		cp $TMP1 $CUR
    410 	fi
    411 fi
    412 
    413 # Check for block and character disk devices that are readable or writeable
    414 # or not owned by root.operator.
    415 >$TMP1
    416 DISKLIST="dk fd hd hk hp jb kra ra rb rd rl rx rz sd up wd"
    417 for i in $DISKLIST; do
    418 	egrep "^b.*/${i}[0-9][0-9]*[a-h]$"  $LIST >> $TMP1
    419 	egrep "^c.*/r${i}[0-9][0-9]*[a-h]$"  $LIST >> $TMP1
    420 done
    421 
    422 awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
    423 	{ printf("Disk %s is user %s, group %s, permissions %s.\n", \
    424 	    $11, $3, $4, $1); }' < $TMP1 > $OUTPUT
    425 if [ -s $OUTPUT ] ; then
    426 	printf "\nChecking disk ownership and permissions.\n"
    427 	cat $OUTPUT
    428 	printf "\n"
    429 fi
    430 
    431 # Display any changes in the device file list.
    432 egrep '^[bc]' $LIST | sort +10 > $TMP1
    433 if [ -s $TMP1 ] ; then
    434 	CUR=/var/backups/device.current
    435 	BACK=/var/backups/device.backup
    436 
    437 	if [ -s $CUR ] ; then
    438 		if cmp -s $CUR $TMP1 ; then
    439 			:
    440 		else
    441 			> $TMP2
    442 			join -111 -211 -v2 $CUR $TMP1 > $OUTPUT
    443 			if [ -s $OUTPUT ] ; then
    444 				printf "Device additions:\n"
    445 				tee -a $TMP2 < $OUTPUT
    446 				printf "\n"
    447 			fi
    448 
    449 			join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
    450 			if [ -s $OUTPUT ] ; then
    451 				printf "Device deletions:\n"
    452 				tee -a $TMP2 < $OUTPUT
    453 				printf "\n"
    454 			fi
    455 
    456 			# Report any block device change.  Ignore character
    457 			# devices, only the name is significant.
    458 			cat $TMP2 $CUR $TMP1 | \
    459 			sed -e '/^c/d' | \
    460 			sort +10 | \
    461 			sed -e 's/[	 ][	 ]*/ /g' | \
    462 			uniq -u > $OUTPUT
    463 			if [ -s $OUTPUT ] ; then
    464 				printf "Block device changes:\n"
    465 				column -t $OUTPUT
    466 				printf "\n"
    467 			fi
    468 
    469 			cp $CUR $BACK
    470 			cp $TMP1 $CUR
    471 		fi
    472 	else
    473 		printf "Device additions:\n"
    474 		column -t $TMP1
    475 		printf "\n"
    476 		cp $TMP1 $CUR
    477 	fi
    478 fi
    479 
    480 # Check special files.
    481 # Check system binaries.
    482 #
    483 # Create the mtree tree specifications using:
    484 #
    485 #	mtree -cx -pDIR -kcksum,gid,mode,nlink,size,link,time,uid > DIR.secure
    486 #	chown root.wheel DIR.SECURE
    487 #	chmod 600 DIR.SECURE
    488 #
    489 # Note, this is not complete protection against Trojan horsed binaries, as
    490 # the hacker can modify the tree specification to match the replaced binary.
    491 # For details on really protecting yourself against modified binaries, see
    492 # the mtree(8) manual page.
    493 if cd /etc/mtree; then
    494 	mtree -e -p / -f /etc/mtree/special > $OUTPUT
    495 	if [ -s $OUTPUT ] ; then
    496 		printf "\nChecking special files and directories.\n"
    497 		cat $OUTPUT
    498 	fi
    499 
    500 	> $OUTPUT
    501 	for file in *.secure; do
    502 		[ $file = '*.secure' ] && continue
    503 		tree=`sed -n -e '3s/.* //p' -e 3q $file`
    504 		mtree -f $file -p $tree > $TMP1
    505 		if [ -s $TMP1 ]; then
    506 			printf "\nChecking $tree:\n" >> $OUTPUT
    507 			cat $TMP1 >> $OUTPUT
    508 		fi
    509 	done
    510 	if [ -s $OUTPUT ] ; then
    511 		printf "\nChecking system binaries:\n"
    512 		cat $OUTPUT
    513 	fi
    514 fi
    515 
    516 # List of files that get backed up and checked for any modifications.  Each
    517 # file is expected to have two backups, /var/backups/file.{current,backup}.
    518 # Any changes cause the files to rotate.
    519 if [ -s /etc/changelist ] ; then
    520 	for file in `cat /etc/changelist`; do
    521 		CUR=/var/backups/`basename $file`.current
    522 		BACK=/var/backups/`basename $file`.backup
    523 		if [ -s $file ]; then
    524 			if [ -s $CUR ] ; then
    525 				diff $CUR $file > $OUTPUT
    526 				if [ -s $OUTPUT ] ; then
    527 		printf "\n======\n%s diffs (OLD < > NEW)\n======\n" $file
    528 					cat $OUTPUT
    529 					cp -p $CUR $BACK
    530 					cp -p $file $CUR
    531 					chown root.wheel $CUR $BACK
    532 				fi
    533 			else
    534 				cp -p $file $CUR
    535 				chown root.wheel $CUR
    536 			fi
    537 		fi
    538 	done
    539 fi
    540