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