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