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