Source code pulled from OpenBSD for OpenNTPD. The place to contribute to this code is via the OpenBSD CVS tree.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

583 lines
15 KiB

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