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.

571 lines
14 KiB

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