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