security revision 1.19
1#!/bin/sh -
2#
3#	$NetBSD: security,v 1.19 1997/04/21 11:14:41 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 "\nEntry for uudecode in /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	sort -k2 |
259	while read uid homedir; do
260		if [ -f ${homedir}/.rhosts ] ; then
261			rhost=`ls -ldgT ${homedir}/.rhosts`
262			printf "$uid: $rhost\n"
263		fi
264	done > $OUTPUT
265	if [ -s $OUTPUT ] ; then
266		printf "\nChecking for special users with .rhosts files.\n"
267		cat $OUTPUT
268	fi
269
270	awk -F: '{ print $1 " " $9 }' /etc/master.passwd |
271	sort -k2 |
272	while read uid homedir; do
273		if [ -f ${homedir}/.rhosts ] && \
274		    egrep '\+' ${homedir}/.rhosts > /dev/null ; then
275			printf "$uid: + in .rhosts file.\n"
276		fi
277	done > $OUTPUT
278	if [ -s $OUTPUT ] ; then
279		printf "\nChecking .rhosts files syntax.\n"
280		cat $OUTPUT
281	fi
282fi
283
284# Check home directories.  Directories should not be owned by someone else
285# or writeable.
286if [ "$check_homes" = YES ]; then
287	awk -F: '{ print $1 " " $9 }' /etc/master.passwd |
288	sort -k2 |
289	while read uid homedir; do
290		if [ -d ${homedir}/ ] ; then
291			file=`ls -ldgT ${homedir}`
292			printf "$uid $file\n"
293		fi
294	done |
295	awk '$1 != $4 && $4 != "root" \
296		{ print "user " $1 " home directory is owned by " $4 }
297	     $2 ~ /^-....w/ \
298		{ print "user " $1 " home directory is group writeable" }
299	     $2 ~ /^-.......w/ \
300		{ print "user " $1 " home directory is other writeable" }' > $OUTPUT
301	if [ -s $OUTPUT ] ; then
302		printf "\nChecking home directories.\n"
303		cat $OUTPUT
304	fi
305
306	# Files that should not be owned by someone else or readable.
307	list=".Xauthority .netrc .rhosts"
308	awk -F: '{ print $1 " " $9 }' /etc/master.passwd |
309	sort -k2 |
310	while read uid homedir; do
311		for f in $list ; do
312			file=${homedir}/${f}
313			if [ -f $file ] ; then
314				printf "$uid $f `ls -ldgT $file`\n"
315			fi
316		done
317	done |
318	awk '$1 != $5 && $5 != "root" \
319		{ print "user " $1 " " $2 " file is owned by " $5 }
320	     $3 ~ /^-...r/ \
321		{ print "user " $1 " " $2 " file is group readable" }
322	     $3 ~ /^-......r/ \
323		{ print "user " $1 " " $2 " file is other readable" }
324	     $3 ~ /^-....w/ \
325		{ print "user " $1 " " $2 " file is group writeable" }
326	     $3 ~ /^-.......w/ \
327		{ print "user " $1 " " $2 " file is other writeable" }' > $OUTPUT
328
329	# Files that should not be owned by someone else or writeable.
330	list=".bash_history .bash_login .bash_logout .bash_profile .bashrc \
331	      .cshrc .emacs .exrc .forward .history .klogin .login .logout \
332	      .profile .qmail .rc_history .tcshrc .twmrc .xinitrc .xsession"
333	awk -F: '{ print $1 " " $9 }' /etc/master.passwd |
334	sort -k2 |
335	while read uid homedir; do
336		for f in $list ; do
337			file=${homedir}/${f}
338			if [ -f $file ] ; then
339				printf "$uid $f `ls -ldgT $file`\n"
340			fi
341		done
342	done |
343	awk '$1 != $5 && $5 != "root" \
344		{ print "user " $1 " " $2 " file is owned by " $5 }
345	     $3 ~ /^-....w/ \
346		{ print "user " $1 " " $2 " file is group writeable" }
347	     $3 ~ /^-.......w/ \
348		{ print "user " $1 " " $2 " file is other writeable" }' >> $OUTPUT
349	if [ -s $OUTPUT ] ; then
350		printf "\nChecking dot files.\n"
351		cat $OUTPUT
352	fi
353fi
354
355# Mailboxes should be owned by user and unreadable.
356if [ "$check_varmail" = YES ]; then
357	ls -l /var/mail | sed 1d | \
358	awk '$3 != $9 \
359		{ print "user " $9 " mailbox is owned by " $3 }
360	     $1 != "-rw-------" \
361		{ print "user " $9 " mailbox is " $1 ", group " $4 }' > $OUTPUT
362	if [ -s $OUTPUT ] ; then
363		printf "\nChecking mailbox ownership.\n"
364		cat $OUTPUT
365	fi
366fi
367
368if [ "$check_nfs" = YES ]; then
369	if [ -f /etc/exports ]; then
370	    # File systems should not be globally exported.
371	    awk '{
372		readonly = 0;
373		for (i = 2; i <= NF; ++i) {
374			if ($i ~ /-ro/)
375				readonly = 1;
376			else if ($i !~ /^-/)
377				next;
378		}
379		if (readonly)
380			print "File system " $1 " globally exported, read-only."
381		else
382			print "File system " $1 " globally exported, read-write."
383	    }' < /etc/exports > $OUTPUT
384	    if [ -s $OUTPUT ] ; then
385		printf "\nChecking for globally exported file systems.\n"
386		cat $OUTPUT
387	    fi
388	fi
389fi
390
391# Display any changes in setuid files and devices.
392if [ "$check_devices" = YES ]; then
393	printf "\nChecking setuid files and devices:\n"
394	(find / \( ! -fstype local -o -fstype fdesc -o -fstype kernfs \
395			-o -fstype procfs \) -a -prune -o \
396	    \( -perm -u+s -o \( -perm -g+s -a ! -type d \) -o \
397	       ! -type d -a ! -type f -a ! -type l -a \
398	       ! -type s \) -print | \
399	sort | sed -e 's/^/ls -ldgT /' | sh > $LIST) 2> $OUTPUT
400
401	# Display any errors that occurred during system file walk.
402	if [ -s $OUTPUT ] ; then
403		printf "Setuid/device find errors:\n"
404		cat $OUTPUT
405		printf "\n"
406	fi
407
408	# Display any changes in the setuid file list.
409	egrep -v '^[bc]' $LIST > $TMP1
410	if [ -s $TMP1 ] ; then
411		# Check to make sure uudecode isn't setuid.
412		if grep -w uudecode $TMP1 > /dev/null ; then
413			printf "\nUudecode is setuid.\n"
414		fi
415
416		CUR=/var/backups/setuid.current
417		BACK=/var/backups/setuid.backup
418
419		if [ -s $CUR ] ; then
420			if cmp -s $CUR $TMP1 ; then
421				:
422			else
423				> $TMP2
424				join -110 -210 -v2 $CUR $TMP1 > $OUTPUT
425				if [ -s $OUTPUT ] ; then
426					printf "Setuid additions:\n"
427					tee -a $TMP2 < $OUTPUT
428					printf "\n"
429				fi
430
431				join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
432				if [ -s $OUTPUT ] ; then
433					printf "Setuid deletions:\n"
434					tee -a $TMP2 < $OUTPUT
435					printf "\n"
436				fi
437
438				sort +9 $TMP2 $CUR $TMP1 | \
439				    sed -e 's/[	 ][	 ]*/ /g' | uniq -u > $OUTPUT
440				if [ -s $OUTPUT ] ; then
441					printf "Setuid changes:\n"
442					column -t $OUTPUT
443					printf "\n"
444				fi
445
446				cp $CUR $BACK
447				cp $TMP1 $CUR
448			fi
449		else
450			printf "Setuid additions:\n"
451			column -t $TMP1
452			printf "\n"
453			cp $TMP1 $CUR
454		fi
455	fi
456
457	# Check for block and character disk devices that are readable or writeable
458	# or not owned by root.operator.
459	>$TMP1
460	DISKLIST="dk fd hd hk hp jb kra ra rb rd rl rx rz sd up wd xd xy"
461	for i in $DISKLIST; do
462		egrep "^b.*/${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
463		egrep "^c.*/r${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
464	done
465
466	awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
467		{ printf("Disk %s is user %s, group %s, permissions %s.\n", \
468		    $11, $3, $4, $1); }' < $TMP1 > $OUTPUT
469	if [ -s $OUTPUT ] ; then
470		printf "\nChecking disk ownership and permissions.\n"
471		cat $OUTPUT
472		printf "\n"
473	fi
474
475	# Display any changes in the device file list.
476	egrep '^[bc]' $LIST | sort +10 > $TMP1
477	if [ -s $TMP1 ] ; then
478		CUR=/var/backups/device.current
479		BACK=/var/backups/device.backup
480
481		if [ -s $CUR ] ; then
482			if cmp -s $CUR $TMP1 ; then
483				:
484			else
485				> $TMP2
486				join -111 -211 -v2 $CUR $TMP1 > $OUTPUT
487				if [ -s $OUTPUT ] ; then
488					printf "Device additions:\n"
489					tee -a $TMP2 < $OUTPUT
490					printf "\n"
491				fi
492
493				join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
494				if [ -s $OUTPUT ] ; then
495					printf "Device deletions:\n"
496					tee -a $TMP2 < $OUTPUT
497					printf "\n"
498				fi
499
500				# Report any block device change.  Ignore character
501				# devices, only the name is significant.
502				cat $TMP2 $CUR $TMP1 | \
503				sed -e '/^c/d' | \
504				sort +10 | \
505				sed -e 's/[	 ][	 ]*/ /g' | \
506				uniq -u > $OUTPUT
507				if [ -s $OUTPUT ] ; then
508					printf "Block device changes:\n"
509					column -t $OUTPUT
510					printf "\n"
511				fi
512
513				cp $CUR $BACK
514				cp $TMP1 $CUR
515			fi
516		else
517			printf "Device additions:\n"
518			column -t $TMP1
519			printf "\n"
520			cp $TMP1 $CUR
521		fi
522	fi
523fi
524
525# Check special files.
526# Check system binaries.
527#
528# Create the mtree tree specifications using:
529#
530#	mtree -cx -pDIR -kcksum,gid,mode,nlink,size,link,time,uid > DIR.secure
531#	chown root.wheel DIR.secure
532#	chmod 600 DIR.secure
533#
534# Note, this is not complete protection against Trojan horsed binaries, as
535# the hacker can modify the tree specification to match the replaced binary.
536# For details on really protecting yourself against modified binaries, see
537# the mtree(8) manual page.
538if [ "$check_mtree" = YES ]; then
539	mtree -e -p / -f /etc/mtree/special > $OUTPUT
540	if [ -s $OUTPUT ]; then
541		printf "\nChecking special files and directories.\n"
542		cat $OUTPUT
543	fi
544
545	> $OUTPUT
546	for file in /etc/mtree/*.secure; do
547		[ $file = '/etc/mtree/*.secure' ] && continue
548		tree=`sed -n -e '3s/.* //p' -e 3q $file`
549		mtree -f $file -p $tree > $TMP1
550		if [ -s $TMP1 ]; then
551			printf "\nChecking $tree:\n" >> $OUTPUT
552			cat $TMP1 >> $OUTPUT
553		fi
554	done
555	if [ -s $OUTPUT ]; then
556		printf "\nChecking system binaries:\n"
557		cat $OUTPUT
558	fi
559fi
560
561# List of files that get backed up and checked for any modifications.  Each
562# file is expected to have two backups, /var/backups/file.{current,backup}.
563# Any changes cause the files to rotate.
564if [ "$check_changelist" = YES ] && [ -s /etc/changelist ] ; then
565	for file in `cat /etc/changelist`; do
566		CUR=/var/backups/`basename $file`.current
567		BACK=/var/backups/`basename $file`.backup
568		if [ -s $file ]; then
569			if [ -s $CUR ] ; then
570				diff $CUR $file > $OUTPUT
571				if [ -s $OUTPUT ] ; then
572		printf "\n======\n%s diffs (OLD < > NEW)\n======\n" $file
573					cat $OUTPUT
574					cp -p $CUR $BACK
575					cp -p $file $CUR
576					chown root.wheel $CUR $BACK
577				fi
578			else
579				cp -p $file $CUR
580				chown root.wheel $CUR
581			fi
582		fi
583	done
584fi
585