#---------------------------------------------------------------------------- # # syscheck.pl - Nightly security checks # # Copyright (c) 1998, SaberNet.net, All rights reserved. # # This software may be freely copied, modified and redistributed without # fee for non-commercial purposes provided that this copyright notice is # preserved intact on all copies. # # There is no warranty or other guarantee of fitness of this software. It # is provided solely "as is". The author disclaims all responsibility and # liability with respect to this software's usage or its effect upon # hardware or computer systems. # # # Platform: Solaris 2.6 # Perl version: 5.004_04 # # Installation: # Add a line simular to the follwing to the root crontab: # 0 0 * * * //syscheck.pl # # When the program is run for the first time, a number of WARNING # messages will be listed in the report. Although the script will # automatically generate these files, their content should be # verified as they will be used as the baseline for successive runs. # # Note: # Disable /usr/lib/newsyslog or equivalent in the root crontab when # using the new_syslog module. # # Modules: # new_syslog : Archive system logs (new log each day) # new_fw1log : Archive FireWall-1 log (new log each day) # new_xferlog : Archive FTP logs (new log each day) # expire_audits : Expire old audit logs # check_passwd : List changes in the password file # check_group : List changes in the group file # check_setuid : List changes in setuid files and devices # check_services : List changes in services # check_logins : List active and dormant accounts # check_loginlog : Check for /var/adm/loginlog # check_disks : Print disk usage information # # Revision History: # 12-Mar-1999 1.5 Added uptime # 10-Feb-1999 1.4 Added expiration for audit logs # 18-Jan-1999 1.3 Added support for FW-1 accounting logs # 22-Dec-1998 1.2 Added new_xferlog # 01-Dec-1998 1.1 Added check_loginlog # 31-Oct-1998 1.0 Script completed # #---------------------------------------------------------------------------- # # EMAIL_ADDRESS : Address to email report to # SYSLOG_EXPIRE : Number of days to keep syslog files # FW1_EXPIRE : Number of days to keep FW-1 log files # AUDIT_EXPIRE : Number of days to keep audit log files # SETUID_LOG : Path to setuid log (created by script) # SRVCS_UDP : Path to the udp port log (created by script) # SRVCS_TCP : Path to the tcp port log (created by script) # # EMAIL_CMD : Path to sendmail program # ZIP_CMD : Path to compression program # SYSLOG_RESTART : Command to restart syslog # SYSLOG_TEST : Command to send a message to the syslog # SYSLOG_RECOVER : Command to stop and start the syslog daemon # SYSLOG_M_DIR : Directory of the messages file # SYSLOG_M_LOG : Name of the messages file # SYSLOG_L_DIR : Directory of the syslog file # SYSLOG_L_LOG : Name of the syslog file # AUDIT_DIR : Path to the audit logs (see audit_control(4)) # FW1_DIR : Path to the FireWall-1 log directory # FW1_BIN : Path to the FireWall-1 bin directory # FW1_LOG : Prefix for FireWall-1 log files # FW1_ACC : Prefix for FireWall-1 accounting files # PASSWD_FILE : Path to the passwd file # GROUP_FILE : Path to the group file # DISK_CMD : Disk usage command # NETSTAT_UDP : Netstat command for UDP port information # NETSTAT_TCP : Netstat command for TCP port information # LISTEN_UDP : UDP listen state # LISTEN_TCP : TCP listen state # XFERLOG_DIR : Path to the FTP log file directory # XFERLOG_LOG : FTP transfer log file # # Operational settings $EMAIL_ADDRESS = "root"; $SYSLOG_EXPIRE = 30; $FW1_EXPIRE = 30; $AUDIT_EXPIRE = 30; $XFERLOG_EXPIRE = 30; $SETUID_LOG = "/var/adm/setuid"; $SRVCS_UDP = "/var/adm/services_udp"; $SRVCS_TCP = "/var/adm/services_tcp"; $LOGIN_LOG = "/var/adm/loginlog"; # SunOS 5.x $EMAIL_CMD = "/usr/lib/sendmail"; $ZIP_CMD = "/usr/local/bin/gzip"; $SYSLOG_RESTART = "/bin/kill -HUP `/bin/cat /etc/syslog.pid`"; $SYSLOG_TEST = "/usr/bin/logger -t syslogd restarted"; $SYSLOG_RECOVER = "/etc/init.d/syslog stop;sleep 1;/etc/init.d/syslog start"; $SYSLOG_M_DIR = "/var/adm"; $SYSLOG_M_LOG = "messages"; $SYSLOG_L_DIR = "/var/log"; $SYSLOG_L_LOG = "syslog"; $AUDIT_DIR = "/var/audit"; $FW1_DIR = "/etc/fw/log"; $FW1_LOG = "fw1-log"; $FW1_ACC = "fw1-acc"; $FW1_BIN = "/etc/fw/bin"; $PASSWD_FILE = "/etc/passwd"; $SHADOW_FILE = "/etc/shadow"; $GROUP_FILE = "/etc/group"; $DISK_CMD = "/bin/df -k -l"; $UPTIME_CMD = "/bin/uptime"; $NETSTAT_UDP = "/bin/netstat -a -P udp"; $NETSTAT_TCP = "/bin/netstat -a -P tcp"; $LISTEN_UDP = "Idle"; $LISTEN_TCP = "LISTEN"; #$XFERLOG_DIR = "/var/log"; #$XFERLOG_LOG = "xferlog"; # Global $TIMESTAMP = get_timestamp(); # # main # { my(@UID) = getpwuid($<); if ($UID[2] != 0) { print STDERR "Script requires root privledges!\n"; exit 0; } my($hostname) = `hostname`; chop($hostname); new_syslog(); new_fw1log(); new_xferlog(); expire_audits(); check_passwd(); check_group(); check_setuid(); check_services(); check_logins(); check_loginlog(); check_disks(); open(EMAIL, "| $EMAIL_CMD $EMAIL_ADDRESS"); $_ = `$UPTIME_CMD`; print EMAIL "Subject: Security Check : $hostname\n\n$_\n$REPORT"; close(EMAIL); exit 1; } # # check_logins : List active and dormant accounts # sub check_logins { my($user, $pass, $users, @keys); my($begin) = `/bin/last | /bin/tail -1`; open(PASSWD, "<$PASSWD_FILE"); while() { $_ =~ /^([^:]+):([^:]+)/; $user = $1; $pass = $2; if ($SHADOW_FILE ne '') { $_ = `/bin/grep \"^$user\" $SHADOW_FILE`; $_ =~ /^([^:]+):([^:]+)/; $pass = $2; } if ($pass ne 'NP') { $user =~ s/^\+//; $users{$user} = `/bin/last | /bin/grep \"^$user\" | /bin/head -1`; } } close(PASSWD); @keys = sort(keys(%users)); $REPORT .= "\nActive Accounts\n---------------\n"; foreach $user (@keys) { if ($users{$user} ne '') { $REPORT .= $users{$user}; } } $REPORT .= "\nDormant Accounts\n----------------\n"; foreach $user (@keys) { if ($users{$user} eq '') { $REPORT .= sprintf("%-20s %s", $user, $begin); } } } # # check_loginlog : Check for /var/adm/loginlog # sub check_loginlog { if (! -f $LOGIN_LOG ) { $REPORT .= "\nWARNING: $LOGIN_LOG missing!\n"; system("cp /dev/null $LOGIN_LOG;chmod 0600 $LOGIN_LOG;" . "chown root $LOGIN_LOG;chgrp sys $LOGIN_LOG"); } } # # check_services : List changes in services # sub check_services { get_ports($SRVCS_UDP, $NETSTAT_UDP, $LISTEN_UDP); get_ports($SRVCS_TCP, $NETSTAT_TCP, $LISTEN_TCP); check_file($SRVCS_UDP, "UDP Service Differences\n-----------------------"); check_file($SRVCS_TCP, "TCP Service Differences\n-----------------------"); } # # get_ports : List listening ports # sub get_ports { my($file, $netstat, $listen) = @_; my($port, $ports); open(NETSTAT, "$netstat |"); while() { if ($_ =~ /$listen$/) { # local address if ($_ =~ /([\S]+)/) { # port number/name if ($1 =~ /([^\.]+)$/) { $ports{$1} = 1; } } } } close(NETSTAT); system("cp /dev/null $file; chmod 600 $file"); open(SRVCS, ">$file"); foreach $port (sort(keys(%ports))) { print SRVCS "$port\n"; } close(SRVCS); } # # check_disks : Print disk usage information # sub check_disks { $REPORT .= "\nDisk Usage\n----------\n"; $REPORT .= `$DISK_CMD`; } # # check_setuid : List changes in setuid files and devices # sub check_setuid { system("cp /dev/null $SETUID_LOG; chmod 600 $SETUID_LOG"); system("/bin/find / -local \\( -perm -4000 -o -perm -2000 -o -name ps -o -name ifconfig -o -name find -o -name df -o -name ls \\) -local -type f -exec /bin/ls -l {} \\; >> $SETUID_LOG 2>/dev/null"); check_file($SETUID_LOG, "Setuid Differences\n-----------------"); } # # check_group : List changes in the group file # sub check_group { check_file($GROUP_FILE, "Group Differences\n----------------"); } # # check_passwd : List changes in the password file # sub check_passwd { check_file($PASSWD_FILE, "Account Differences\n-------------------"); } # # check_file : List changes in the specified file # sub check_file { my($file, $name) = @_; my($diff) = 0; if (-f "$file.1day") { open(DIFF, "/bin/diff $file $file.1day |"); while() { if ( ($_ =~ s/^/removed:/) ) { if (!$diff) { $REPORT .= "\n$name\n"; $diff = 1; } $REPORT .= $_; } } close(DIFF); if (-f "$file.2day") { system("/bin/rm -f $file.3day;/bin/mv $file.2day $file.3day"); } system("/bin/rm -f $file.2day;/bin/mv $file.1day $file.2day"); } else { $REPORT .= "\nWARNING: $file.1day missing!\n"; } system("/bin/cp $file $file.1day; chmod 400 $file.1day"); } # # new_xferlog : Archive FTP logs # sub new_xferlog { if ( ($XFERLOG_DIR ne '') && (-d $XFERLOG_DIR) ) { if (archive_log($XFERLOG_DIR, $XFERLOG_LOG) != 1) { $REPORT .= "\nWARNING: $XFERLOG_DIR/$XFERLOG_LOG missing!\n"; } compress_log($XFERLOG_DIR, $XFERLOG_LOG); expire_log($XFERLOG_DIR, $XFERLOG_LOG, $XFERLOG_EXPIRE); } } # # new_fw1log : Archive FireWall-1 logs # sub new_fw1log { if ( ($FW1_BIN ne '') && (-d $FW1_DIR) ) { system("$FW1_BIN/fw logswitch old > /dev/null 2>&1"); system("$FW1_BIN/fw logexport -i $FW1_DIR/old.log " . "-o $FW1_DIR/$FW1_LOG.$TIMESTAMP -n > /dev/null 2>&1"); system("$FW1_BIN/fw logexport -i $FW1_DIR/old.alog " . "-o $FW1_DIR/$FW1_ACC.$TIMESTAMP -n > /dev/null 2>&1"); system("/bin/rm -f $FW1_DIR/old.log*ptr*"); compress_log($FW1_DIR, $FW1_LOG); compress_log($FW1_DIR, $FW1_ACC); expire_log($FW1_DIR, $FW1_LOG, $FW1_EXPIRE); expire_log($FW1_DIR, $FW1_ACC, $FW1_EXPIRE); } } # # new_syslog : Archive system logs # sub new_syslog { if (archive_log($SYSLOG_M_DIR, $SYSLOG_M_LOG) != 1) { $REPORT .= "\nWARNING: $SYSLOG_M_DIR/$SYSLOG_M_LOG missing!\n"; } if (archive_log($SYSLOG_L_DIR, $SYSLOG_L_LOG) != 1) { $REPORT .= "\nWARNING: $SYSLOG_L_DIR/$SYSLOG_L_LOG missing!\n"; } system("sleep 40; $SYSLOG_RESTART"); system($SYSLOG_TEST); system($SYSLOG_RECOVER) if ( -z "$SYSLOG_M_DIR/$SYSLOG_M_LOG"); compress_log($SYSLOG_M_DIR, $SYSLOG_M_LOG); compress_log($SYSLOG_L_DIR, $SYSLOG_L_LOG); expire_log($SYSLOG_M_DIR, $SYSLOG_M_LOG, $SYSLOG_EXPIRE); expire_log($SYSLOG_L_DIR, $SYSLOG_L_LOG, $SYSLOG_EXPIRE); } # # get_timestamp : Returns a timestamp in the format 21-Dec-1998 # sub get_timestamp { my($monthlist) = "JanFebMarAprMayJunJulAugSepOctNovDec"; my(%days) = (0,Sun,1,Mon,2,Tue,3,Wed,4,Thu,5,Fri,6,Sat); my($now) = time - 86400; my($sec,$min,$hours,$mday,$mon,$year,$wday,$yday,$dsflag) = localtime($now); my(@yesterday) = ($mday,$mon+1,$year+1900,$days{wday}, substr($monthlist,($mon)*3,3)); sprintf("%02d%s%s", $yesterday[0],$yesterday[4], $yesterday[2]); } # # archive_log : Archives a log file with a timestamp # sub archive_log { my($dir, $log) = @_; my($rval) = 0; if ( ($dir eq '') || ($log eq '') ) { return($rval); } if (-f "$dir/$log") { system("/bin/mv $dir/$log $dir/$log.$TIMESTAMP"); system("/bin/chmod 400 $dir/$log.$TIMESTAMP"); $rval = 1; } system("/bin/cp /dev/null $dir/$log"); system("/bin/chmod 644 $dir/$log"); return($rval); } # # expire_audits : Remove old audit logs # sub expire_audits { if ($AUDIT_DIR ne '') { system("/usr/sbin/audit -n > /dev/null 2>&1"); system("cd $AUDIT_DIR;/bin/find . -type f -mtime +$AUDIT_EXPIRE -exec rm {} \\; > /dev/null 2>&1"); } } # # expire_log : Removes old log files # sub expire_log { my($dir, $log, $exp) = @_; if ( ($exp > 0) && (-d $dir) && ($log ne '') ) { system("cd $dir;/bin/find . -name \"$log*\" -mtime +$exp -exec rm {} \\;"); } } # # compress_log : Compresses archived log files # sub compress_log { my($dir, $log) = @_; if ( (-f $ZIP_CMD) && (-f "$dir/$log.$TIMESTAMP") ) { system("$ZIP_CMD $dir/$log.$TIMESTAMP"); } }