#!/usr/bin/perl ############################################################################# # CIS Solaris/Linux/HP-UX Host-Based Ruler Version 1.4.2 # by Jay Beale (jay@bastille-linux.org) # Copyright 2001, 2002, 2003 The Center for Internet Security # # This is an initial version of the host-based ruler. As time goes on, # I'll make this more expandable, so we can easily adapt it for other # Unices. # # It is fairly likely that the discovery code will become even more # robust, such that all system queries can be made through an API. Further, # we'll probably look at a much more modular approach that allows the # codebase to shrink significantly. # ############################################################################# # # Copyright 2001, 2002, 2003 The Center for Internet Security (CIS) # # Terms of Use Agreement # # 1. Grant of Permission # # Grant of permission to use the Solaris Download Package consisting # of the Solaris Benchmark, software tools for scoring and monitoring # the status of Benchmark settings at the network and system level, # plus associated documentation. # # Subject to the terms and provisions listed below, CIS grants to you # the nonexclusive and limited right to use the Solaris Download Package # components. # # You are not receiving any ownership or proprietary right, title or # interest in or to the Solaris Download Package components or the # copyrights, trademarks, or other rights related thereto. # # 2. Limitations on Use # # Receipt of the Solaris Download Package components does not permit you to: # # a. Sell the Solaris Download Package components; # # b. Lease or lend the Solaris Download Package components; # # c. Distribute the Solaris Download Package components by any means, # including, but not limited to, through the Internet or other # electronic distribution, direct mail, retail, or mail order (Certain # internal distribution rights are specifically granted to CIS # Consulting and User Members as noted in (2.e.) below); # # d. In any other manner and through any medium commercially exploit or # use the Solaris Download Package components for any commercial # purpose; # # e. Post the Benchmark, software tools, or associated documentation on # any internal or external web site. (Consulting and User Members of # CIS may distribute the Solaris Download Package components within # their own organization); # # f. Represent or claim a particular level of compliance with the # Solaris Benchmark unless the system is operated by a Consulting or # User Member of CIS and has been scored against the Benchmark criteria # by a monitoring tool obtained directly from CIS or a commercial # monitoring tool certified by CIS. &tester(); sub tester { umask 077; # Determine the operating system, so we know which directories to use. if ( -x '/bin/uname' ) { $uname_path = '/bin/uname'; } elsif ( -x '/usr/bin/uname' ) { $uname_path = '/usr/bin/uname'; } $uname_string = `$uname_path`; chomp $uname_string; if ($uname_string eq 'SunOS') { $Solaris = 1; $OS = 'Solaris'; $main_directory = '/opt/CIS'; } elsif ($uname_string eq 'Linux') { $Linux = 1; $OS = 'Linux'; $main_directory = '/usr/local/CIS'; } elsif ($uname_string eq 'HP-UX') { $HP=1; $OS='HP-UX'; $main_directory = '/var/opt/CIS'; } else { die ("Not able to determine O/S -- uname output was: $uname_string\n"); } # Get the time and create a timestamp for logging. # Add the PID, just in case someone runs this twice in one second. ($sec, $min, $hr, $day, $mon, $year) = localtime(); $mon++; $year += 1900; $TIMESTAMP = sprintf("%d%02d%02d-%02d:%02d:%02d", $year, $mon, $day, $hr, $min, $sec); $TIMESTAMP_EXTENDED = $TIMESTAMP . ".$$"; # Determine where to put the logs # Change the DIR thing to something that works... ($DIR) = $0 =~ /^(.+)\/[^\/]+$/; unless (length($DIR)) { chomp($DIR = `pwd`); $DIR = $DIR || $main_directory; } if ($HP) { $LOGFILE = "/var/opt/CIS/tester.logs/cis-ruler-log.$TIMESTAMP_EXTENDED"; $LOGDIR = '/var/opt/CIS/tester.logs'; unless ( -d '/var/opt/CIS' ) { mkdir '/var/opt/CIS/',0700; } unless ( -d '/var/opt/CIS/checkperms') { mkdir '/var/opt/CIS/checkperms',0700; } unless ( -d '/var/opt/CIS/tester.logs') { mkdir '/var/opt/CIS/tester.logs',0700; } unless ( -d '/var/opt/CIS/spc') { mkdir '/var/opt/CIS/spc',0700; } chown 0,0,'/var/opt/CIS','/var/opt/CIS/checkperms','/var/opt/CIS/tester.logs','/var/opt/CIS/spc'; } else { $LOGFILE = $DIR . "/cis-ruler-log.$TIMESTAMP_EXTENDED"; $LOGDIR = $DIR; } # Open an initial log of data found unless (open LOG, ">$LOGFILE") { if ($Solaris) { if ( -d "/var/sadm/system/logs" ) { $LOGDIR = "/var/sadm/system/logs"; } else { $LOGDIR = "/var/sadm/install_data"; } } elsif ($Linux) { $LOGDIR = "/var/log"; } elsif ($HP) { $LOGDIR = "/var/adm"; } $LOGFILE = $LOGDIR . "/cis-ruler-log.$TIMESTAMP_EXTENDED"; open LOG,">$LOGFILE" or die ("Couldn't open logfile $LOGFILE\n"); } # turn off buffered output select(LOG); $| = 1; select(STDOUT); # Produce a Last-Log symlink $link = $LOGDIR . '/cis-most-recent-log'; unlink $link; symlink "cis-ruler-log.$TIMESTAMP_EXTENDED",$link; &Log("*** CIS Ruler Run ***"); &Log("Starting at time $TIMESTAMP\n"); # Put up opening banner if ($Solaris) { $EMAIL = 'sol-scan@cisecurity.org. '; } elsif ($Linux) { $EMAIL = 'linux-scan@cisecurity.org.'; } elsif ($HP) { $EMAIL = 'hpux-scan@cisecurity.org. '; } print "\n"; print "*****************************************************************************\n"; print "******************* CIS Security Benchmark Checker v1.4.2 *******************\n"; print "* *\n"; print "* Lead Developer : Jay Beale *\n"; print "* Benchmark Coordinator and Gadfly : Hal Pomeranz *\n"; print "* *\n"; print "* Copright 2001 - 2003 The Center for Internet Security www.cisecurity.org *\n"; print "* *\n"; print "* Please send feedback to $EMAIL *\n"; print "*****************************************************************************\n\n"; # Logs are now in cis-most-recent-log #print "Placing logs in $LOGFILE\n\n"; print " Investigating system...this will take a few minutes...\n"; # Get operating system version chomp($version = `$uname_path -r`); #print "...Found $OS kernel version $version...\n"; # ***** For SuSE - I forced the DIDTRIBUTION value to be "RH" and # the version to be 7.0, this will run the script against an "inevntory" # of Red Hat 7.0 valid SUID and SGID files, this may generate some false # positives, but that is better than having the script die. if ($Linux) { $DISTRIBUTION = "RH"; $DISTRIBUTION_VERSION = "7.0"; } # To following lines were commented out to get this script # to function on SuSE # open DISTRO_RELEASE,"/etc/redhat-release" or die ("Couldn't open # # /etc/redhat-release # -- this is present on Red Hat and Mandrake systems."); # # my $release_line = ; # close DISTRO_RELEASE; # # if ($release_line =~ /^Red Hat Linux release (\d\.\d)/) { # $DISTRIBUTION = "RH"; # $DISTRIBUTION_VERSION = $1; # } # elsif ($release_line =~ /^Red Hat Linux release (\d)/) { # $DISTRIBUTION = "RH"; # $DISTRIBUTION_VERSION = $1; # } # elsif ($release_line =~ /^Red Hat Linux Advanced Server release # (\d+\.\w+)\s+/) { # $DISTRIBUTION = "RH"; # $DISTRIBUTION_VERSION = $1; # } # elsif ($release_line =~ /^Linux Mandrake release (\d\.\d)/) { # $DISTRIBUTION = "MD"; # $DISTRIBUTION_VERSION = $1; # } # elsif ($release_line =~ /^Mandrake Linux release (\d\.\d)/) { # $DISTRIBUTION = "MD"; # $DISTRIBUTION_VERSION = $1; # } # else { # die ("Couldn't determine Linux Release $release_line"); # } #} # # Read in the /etc/passwd file and set up the @GLOBAL_PASSWD_LINES array # along with the %GLOBAL_PWENT hash. # # Note: we read this stuff in once for speed. # # Further note: we don't use the perl function for this since it changed # between perl versions and varies between O/S versions. Let's not create # any version dependencies if we don't have to. #print ("...Reading and caching /etc/passwd and /etc/shadow...\n"); open PASSWD,"/etc/passwd" or die ("Couldn't read /etc/passwd\n"); my @lines = ; close PASSWD; chomp @lines; foreach $line (@lines) { push @GLOBAL_PASSWD_LINES,$line; if ($line =~ /^([^:]+):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)$/) { $user=$1; $GLOBAL_PWENT{$user}{'password'}=$2; $GLOBAL_PWENT{$user}{'uid'}=$3; $GLOBAL_PWENT{$user}{'gid'}=$4; $GLOBAL_PWENT{$user}{'gecos'}=$5; $GLOBAL_PWENT{$user}{'home'}=$6; $GLOBAL_PWENT{$user}{'shell'}=$7; if ($GLOBAL_PWENT{$user}{'shell'} eq '') { if ($Solaris or $Linux) { $GLOBAL_PWENT{$user}{'shell'}='/bin/sh'; } elsif ($HP) { $GLOBAL_PWENT{$user}{'shell'}='/usr/bin/sh'; } } # quick cleanup # if ($GLOBAL_PWENT{$user}{'home'} =~ /^(.*)\/\s*$/) { # $GLOBAL_PWENT{$user}{'home'}=$1; # } push @GLOBAL_USERS,$user; $GLOBAL_USER_PRESENT{$user} = 1; # # Build a list of the "real" human users on the system # my $min_uid; my %uid_exception; if ($Solaris) { $min_uid = 100; %uid_exception = (60001,1,60002,1,65534,1); } elsif ($Linux) { $min_uid = 500; %uid_exception = (65534,1); } elsif ($HP) { $min_uid = 100; } my $uid = $GLOBAL_PWENT{$user}{'uid'}; if ($uid >= $min_uid) { unless ($uid_exception{$uid}) { push @GLOBAL_HUMAN_USERS,$user; } } } } # Add root to the human users group if ($Linux) { push @GLOBAL_HUMAN_USERS,'root'; } # # Read in the /etc/shadow file and set up the @GLOBAL_SHADOW_LINES array # along with the %GLOBAL_SHADOW_ENT hash. # # Note: we read this stuff in once for speed. # # Further note: we don't use the perl function for this since it changed # between perl versions and varies between O/S versions. Let's not create # any version dependencies if we don't have to. if ($Solaris or $Linux) { open SHADOW,"/etc/shadow" or die ("Couldn't read /etc/shadow\n"); @lines = ; close SHADOW; chomp @lines; foreach $line (@lines) { push @GLOBAL_SHADOW_LINES,$line; my @entries = split /:/,$line; $user=$entries[0]; $GLOBAL_SHADOW_ENT{$user}{'password'}=$entries[1]; $GLOBAL_SHADOW_ENT{$user}{'min'}=$entries[3]; $GLOBAL_SHADOW_ENT{$user}{'max'}=$entries[4]; $GLOBAL_SHADOW_ENT{$user}{'warn'}=$entries[5]; $GLOBAL_SHADOW_ENT{$user}{'inactive'}=$entries[6]; } } elsif ($HP) { # First, try to get data from modprpw and /etc/passwd. Where # necessary, get it from /tcb/files/auth. if ( -d '/tcb/files/auth') { # If there's a /tcb/files/auth directory, then try to find a # password in there for each user. foreach $user (@GLOBAL_USERS) { $GLOBAL_SHADOW_ENT{$user}{'password'}=$GLOBAL_PWENT{$user}{'password'}; my $filename = '/tcb/files/auth/' . substr($user,0,1) . "/$user"; if ( -f $filename) { if (open TCBFILE,$filename) { my @lines = ; close TCBFILE; chomp @lines; my $passline = grep /^\s*\:u_pwd\s*\=\s*/,@lines; if ($passline =~ /^\s*\:u_pwd\s*\=\s*([^\:]*)\:/) { $GLOBAL_SHADOW_ENT{$user}{'password'}=$1; } } } } } else { # There's no /tcb directory -- we can get it all from /etc/passwd. foreach $user (@GLOBAL_USERS) { $GLOBAL_SHADOW_ENT{$user}{'password'}=$GLOBAL_PWENT{$user}{'password'}; } } # Now, let's try to get the aging information for each account. foreach $user (@GLOBAL_USERS) { # First, minimum days between password changes. open GETPRPW,"/usr/lbin/getprpw -m mintm $user |" or die "Can't get password aging data on HP-UX out of /usr/lbin/getprpw."; my $line = ; close GETPRPW; if ($line =~ /^mintm\=(.*)$/) { $GLOBAL_SHADOW_ENT{$user}{'min'}=$1; } # Next, let's check the maximum password lifetime in days. open GETPRPW,"/usr/lbin/getprpw -m exptm $user |" or die "Can't get password aging data on HP-UX out of /usr/lbin/getprpw."; my $line = ; close GETPRPW; if ($line =~ /^exptm\=(.*)$/) { $GLOBAL_SHADOW_ENT{$user}{'max'}=$1; } # Finally, let's check the expiration-warn time in days. open GETPRPW,"/usr/lbin/getprpw -m expwarn $user |" or die "Can't get password aging data on HP-UX out of /usr/lbin/getprpw."; my $line = ; close GETPRPW; if ($line =~ /^expwarn\=(.*)$/) { $GLOBAL_SHADOW_ENT{$user}{'warn'}=$1; } } } ## # Read in the list of shells in /etc/shells #print "...Reading and caching /etc/shells...\n"; my $return = open SHELLS,"/etc/shells"; if ($return) { @GLOBAL_VALID_SHELLS = ; close SHELLS; chomp @GLOBAL_VALID_SHELLS; } elsif ($HP) { @GLOBAL_VALID_SHELLS = ( "/sbin/sh","/usr/bin/csh","/usr/bin/ksh","/usr/bin/sh" ); } elsif ($Solaris) { @GLOBAL_VALID_SHELLS = ( '/usr/bin/sh','/usr/bin/csh','/usr/bin/ksh','/usr/bin/jsh','/bin/sh','/bin/csh','/bin/ksh','/bin/jsh','/sbin/sh','/sbin/jsh','/usr/bin/bash','/usr/bin/tcsh'); } else { @GLOBAL_VALID_SHELLS = ( "/sbin/sh","/usr/bin/csh","/usr/bin/ksh" ); } ## # Read and parse file system table if ($Solaris) { $fstab_file = '/etc/vfstab'; @main_fstypes = ('ufs'); $dev_entry = 1; $fs_entry = 2; $fstype_entry = 3; $options_entry = 6; } elsif ($Linux) { $fstab_file = '/etc/fstab'; @main_fstypes = ('ext2','ext3','xfs','reiserfs','reiserfs','jfs'); $dev_entry = 0; $fs_entry = 1; $fstype_entry = 2; $options_entry = 3; } elsif ($HP) { $fstab_file = '/etc/fstab'; @main_fstypes = ('vxfs','hfs'); $dev_entry = 0; $fs_entry = 1; $fstype_entry = 2; $options_entry = 3; } #print "...Reading and parsing $fstab_file for later use...\n"; open FSTAB,$fstab_file or die ("Couldn't read $fstab_file\n"); while () { next if /^#/; my @entries = split('\s+'); $GLOBAL_FSTAB_ENTRIES{$entries[$fs_entry]}{'device'}=$entries[$dev_entry]; $GLOBAL_FSTAB_ENTRIES{$entries[$fs_entry]}{'fstype'}=$entries[$fstype_entry]; my $main_fs=0; foreach $main_fstype (@main_fstypes) { if ($entries[$fstype_entry] eq $main_fstype) { $main_fs=1; } } # 7/22/03 almost-a-KLUDGE / not-a-BUG-but-an-annoyance # Darn it! Until now, we weren't parsing and storing mount options on # filesystems that weren't for the main filesystem. This is causing pain, # specifically on 6.2.Linux (add nosuid and nodev options to floppy and # cdrom), so we'll need to fix this. On the other hand, the next unless # line commented out here (which enforced this pain-causing condition) # was what allowed lots of items to avoid looking at non-mainfs by simply # asking for the keys to GLOBAL_FSTAB_FLAGS. # # To avoid breaking that, I'm going to store flags in the %%GLOBAL_FSTAB_ENTRIES # data structure. Only 6.2.Linux, for now, will look there for flags. #next unless ($main_fs); for (split(/,/, $entries[$options_entry])) { if ($main_fs) { $GLOBAL_FSTAB_FLAGS{$entries[$fs_entry]}{$_} = 1; } $GLOBAL_FSTAB_ENTRIES{$entries[$fs_entry]}{$_}=1; } } close(FSTAB); if ($Solaris) { # Read in /etc/system # print "...Reading and caching /etc/system...\n"; open SYSTEM,"/etc/system" or die "Couldn't read /etc/system\n"; @GLOBAL_ETC_SYSTEM_LINES = ; close SYSTEM; chomp @GLOBAL_ETC_SYSTEM_LINES; } # Read in and cache ps -ef listing #print "...Caching ps -ef process listing...\n"; $return = open PS,"ps -ef |"; if ($return) { @GLOBAL_PS_LINES = ; close PS; chomp @GLOBAL_PS_LINES; } else { &Log('ERROR: ps -ef didn\'t work properly!'); print "...ERROR: ps -ef didn't work...\n"; } # Read in and cache syslog.conf #print "...Caching /etc/syslog.conf file...\n"; $return=open SYSLOG,"/etc/syslog.conf"; if ($return) { @GLOBAL_SYSLOG_LINES = undef; while () { next if (/^\#/ || /^\s*$/); chomp; push(@GLOBAL_SYSLOG_LINES, $_); } close SYSLOG; } else { print "...ERROR: couldn't open /etc/syslog.conf...\n"; &Log("ERROR: couldn't open /etc/syslog.conf."); } # Read in inetd/xinetd configuration and build up quickly searchable list (hash) of active # services. # Leave these empty if inetd isn't running? if (@GLOBAL_PS_LINES) { if ( ( grep /\binetd\b/,@GLOBAL_PS_LINES ) or (grep /\b\/usr\/sbin\/inetd\b/,@GLOBAL_PS_LINES ) ) { # inetd is running $GLOBAL_INETD_RUNNING = 1; # print "...Parsing inetd.conf...\n"; my $inetd_conf_file; if ( -e "/etc/inetd.conf") { $inetd_conf_file = "/etc/inetd.conf"; } elsif ( -e "/etc/inet/inetd.conf" ) { $inetd_conf_file = "/etc/inet/inetd.conf"; } my $return = open INETD,$inetd_conf_file; if ($return) { @GLOBAL_INETD_CONF_LINES = ; close INETD; chomp @GLOBAL_INETD_CONF_LINES; foreach $line ( @GLOBAL_INETD_CONF_LINES ) { unless ( ($line =~ /^\s*\#/) or ($line =~ /^\s*$/) ) { if ($line =~ /^([^\s]+)\s/) { if ($HP and ($1 eq 'rpc')) { my @list = (split /\s+/,$line); my $string = $list[6] . '/' . $list[7]; $GLOBAL_ACTIVE_INETD_SERVICES{$string} = 1; } else { $GLOBAL_ACTIVE_INETD_SERVICES{$1} = 1; } } } } } } # Note that both xinetd and inetd can be running at the same time, so long as they # don't both try to bind to the same port. if ( (grep /\bxinetd\b/,@GLOBAL_PS_LINES ) or (grep /\b\/usr\/sbin\/xinetd\b/,@GLOBAL_PS_LINES ) ) { # xinetd is running $GLOBAL_XINETD_RUNNING = 1; #&Log("xinetd is running."); # print "...Parsing /etc/xinetd.d/* ... \n"; # Testers: Does this parsing work? my $return = opendir XINETDD,"/etc/xinetd.d"; if ($return) { my @xinetd_files = (readdir XINETDD); close XINETDD; chomp @xinetd_files; # Each file in /etc/xinetd.d/ represents a possibly running service. # We read each file and... foreach $file (@xinetd_files) { unless ( ($file eq '.') or ($file eq '..') ) { my $return = open XINETD_SERVICE,"/etc/xinetd.d/$file"; if ($return) { my @xinetd_service_lines = ; # ...determine if the service is activated. foreach $line (@xinetd_service_lines) { if ($line =~ /^\s*service\s+(\S+)/) { my $service = $1; unless (grep /^\s*disable\s*=\s*yes/,@xinetd_service_lines) { $GLOBAL_ACTIVE_INETD_SERVICES{$service}=1; $GLOBAL_XINETD_SVC_FILE{$service}=$file; #&Log("Diag: service $service found."); } last; } } } } } } } # We've finished parsing inetd/xinetd base configurations } else { &Log("ERROR: ps -ef didn't work properly."); } # If we're on Linux, figure out if this is an inetd or xinetd-based system. This is # important since we score inetd/xinetd access control even if inetd/xinetd isn't # running. Rationale is "patches have a way of bringing these things back." if ($Linux) { my $CHKCONFIG_BIN; foreach $dir ('/sbin','/usr/sbin','/bin','/usr/bin') { if ( -x "$dir/chkconfig" ) { $CHKCONFIG_BIN = "$dir/chkconfig"; last; } } my $inetd_results = `$CHKCONFIG_BIN --list inet 2>&1`; my $xinetd_results = `$CHKCONFIG_BIN --list xinetd 2>&1`; if ($inetd_results =~ /^inet\s+0/) { $GLOBAL_INETD_INSTALLED = 1; # print "...Detected inetd installed on system.\n"; } if ($xinetd_results =~ /^xinetd\s+0/) { $GLOBAL_XINETD_INSTALLED = 1; # print "...Detected xinetd installed on system.\n"; # If xinetd is installed, but not on....build up a # list of files that xinetd would run services out # of anyway... if (opendir XINETDD,"/etc/xinetd.d") { my @xinetd_files = (readdir XINETDD); close XINETDD; chomp @xinetd_files; foreach $file (@xinetd_files) { unless ( ($file eq '.') or ($file eq '..') ) { if (open XINETD_SERVICE,"/etc/xinetd.d/$file") { my @xinetd_service_lines = ; foreach $line (@xinetd_service_lines) { if ($line =~ /^\s*service\s+(\S+)/) { $GLOBAL_XINETD_SVC_FILE{$1}=$file; last; } } } } } } } } if ($HP) { # Catalog inetd.sec if (open INETDSEC,"/var/adm/inetd.sec") { my @lines = ; close INETDSEC; chomp @lines; foreach $line (@lines) { next if ($line =~ /^\s*\#/); next if ($line =~ /^\s*$/); my $port = (split /\s+/,$line)[0]; $INETDSEC{$port} = 1; } } } if ($Solaris) { @rc_directories = ( "/etc/rc2.d","/etc/rc3.d" ); } elsif ($Linux) { # It should be either runlevel 3 or 5, depending on the # initdefault line in /etc/inittab. open INITTAB,"/etc/inittab"; my @lines = ; close INITTAB; foreach $line (@lines) { if ($line =~ /id:(\d):initdefault:/) { $RUNLEVEL = $1; last; } } @rc_directories = ( "/etc/rc$RUNLEVEL.d" ); } elsif ($HP) { @rc_directories = ( "/sbin/rc2.d","/sbin/rc3.d" ); } # Read in the /etc/rc directories and catalog all the S__ scripts #print "...Cataloging rc scripts...\n"; foreach $dir (@rc_directories) { if (opendir RCDIR,$dir) { my @lines = (readdir RCDIR); closedir RCDIR; chomp @lines; foreach $rc_script ( sort(grep /^S/,@lines) ) { if ($rc_script =~ /^S\d+(.*)$/) { next unless ( -f "$dir/$rc_script" and -s "$dir/$rc_script" ); push @GLOBAL_ACTIVE_RC_SCRIPTS,$1; $GLOBAL_ACTIVE_RC_SCRIPTS{$1}=1; $GLOBAL_RC_SCRIPT_PATH{$1} = "$dir/$rc_script"; } } } } # Note: BTW, globs don't work with our compilation method # Read in particular /etc/rc.config.d/ files on HP-UX -- this is # another place that daemons are deactivated, so we need to check # both for the existence of the startup script and the variable name if ($HP) { # We used to create a list of /etc/rc.config.d/ files by hand-coding a list, using a perl script that climbed # through looking for uses of RC_CONFIG. Now, we just parse all of the /etc/rc.config.d/* files. #my @files = ( 'acct','apacheconf','audio','auditing','cifsclient','comsec','desktop','dfs','i4lmd','iforls','lp','mailservs','namesvrs','netconf','netdaemons','nfsconf','ns-ftrack','pd','peer.snmpd','ptydaemon','pwgr','samba','slsd','snaplus2','tps','vt','xfs' ); my @files; if (opendir CONFIGD,'/etc/rc.config.d/') { @files = (readdir CONFIGD); close CONFIGD; } else { die "Couldn't open /etc/rc.config.d/ directory to parse!"; } foreach $file (@files) { next if ($file =~ /^\.{1,2}$/); if (open FILE,"/etc/rc.config.d/$file") { my @lines = ; close FILE; chomp @lines; foreach $line (@lines) { if ($line =~ /^\s*(export)*\s*([^\#\=\s]+)\s*=\s*([^\#\s]+)/) { my $key = $2; my $value =$3; $value =~ s/\"//g; $RC_CONFIG{$file}{$key}=$value; } } } } } # Load in a list of the standard SUID programs for this OS version if ($Solaris) { $filename = $DIR . "/cis_ruler_suid_programs_sunos_" . $version; } elsif ($Linux) { # Build filename $filename = $DIR ; if ($DISTRIBUTION eq "RH") { $filename = $filename . "/cis_ruler_suid_programs_redhat_" . $DISTRIBUTION_VERSION; } elsif ($DISTRIBUTION eq "MD") { $filename = $filename . "/cis_ruler_suid_programs_mandrake_" . $DISTRIBUTION_VERSION; } } elsif ($HP) { $numeric_version =$version; $numeric_version =~ s/B\.//; $numeric_version =~ s/A\.//; $filename = $DIR . "/cis_ruler_suid_programs_hp-ux_" . $numeric_version; } if (open SUID,$filename) { my @lines = ; close SUID; chomp @lines; foreach $line (@lines) { $line =~ s/^\s*(.*)\s*$/$1/; $GLOBAL_ACCEPTABLE_SUID{$line} =1; } } else { &Log("Couldn't open $filename -- list of standard SUID programs for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION."); print "ERROR: Couldn't open $filename -- list of standard SUID programs for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION.\n"; print "NOTE: If you can generate a standard list of SUID/SGID root programs for this version, please e-mail to jay\@bastille-linux.org.\n"; $GLOBAL_NOSUID=1; } # Load in a list of the standard SGID programs for this OS version if ($Solaris) { $filename = $DIR . "/cis_ruler_sgid_programs_sunos_" . $version; } elsif ($Linux) { # Build filename $filename = $DIR ; if ($DISTRIBUTION eq "RH") { $filename = $filename . "/cis_ruler_sgid_programs_redhat_" . $DISTRIBUTION_VERSION; } elsif ($DISTRIBUTION eq "MD") { $filename = $filename . "/cis_ruler_sgid_programs_mandrake_" . $DISTRIBUTION_VERSION; } } elsif ($HP) { $filename = $DIR . "/cis_ruler_sgid_programs_hp-ux_" . $numeric_version; } if (open SGID,$filename) { @lines = ; close SGID; chomp @lines; foreach $line (@lines) { $line =~ s/^\s*(.*)\s*$/$1/; $GLOBAL_ACCEPTABLE_SGID{$line} =1; } } else { &Log("Couldn't open $filename -- list of standard SGID programs for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION."); print "ERROR: Couldn't open $filename -- list of standard SGID programs for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION.\n"; print "NOTE: If you can generate a standard list of SUID/SGID root programs for this version, please e-mail to jay\@bastille-linux.org.\n"; $GLOBAL_NOSGID = 1; } # Load in a list of the standard world-writable files for this OS version if ($Solaris) { $filename = $DIR . "/cis_ruler_world_writable_files_sunos_" . $version; } elsif ($Linux) { # Build filename $filename = $DIR ; if ($DISTRIBUTION eq "RH") { $filename = $filename . "/cis_ruler_world_writable_files_redhat_" . $DISTRIBUTION_VERSION; } elsif ($DISTRIBUTION eq "MD") { $filename = $filename . "/cis_ruler_world_writable_files_mandrake_" . $DISTRIBUTION_VERSION; } } elsif ($HP) { $filename = $DIR . "/cis_ruler_world_writable_files_hp-ux_" . $numeric_version; } if ($Solaris or $Linux or $HP) { if (open WORLDWRITE,$filename) { @lines = ; close WORLDWRITE; chomp @lines; foreach $line (@lines) { $line =~ s/^\s*(.*)\s*$/$1/; $GLOBAL_ACCEPTABLE_WORLD_WRITABLE{$line} =1; } } else { &Log("Couldn't open $filename -- list of standard world-writable files for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION."); print "ERROR: Couldn't open $filename -- list of standard world-writable files for $OS $version $DISTRIBUTION $DISTRIBUTION_VERSION.\n"; print "NOTE: If you can generate a standard list of world-writable files for this version, please e-mail to jay\@bastille-linux.org.\n"; $GLOBAL_NO_WORLD_WRITABLE = 1; } } # Cache netstat results if ($Solaris) { $netstat_tcp_command = "netstat -anP tcp |"; $netstat_udp_command = "netstat -anP udp |"; } elsif ($Linux) { $netstat_tcp_command = "netstat -ant |"; $netstat_udp_command = "netstat -anu |"; } elsif ($HP) { $netstat_tcp_command = 'netstat -an |'; $netstat_udp_command = 'netstat -an |'; } #print "...Checking listening TCP ports via netstat...\n"; open NETSTAT,$netstat_tcp_command or die "Error! Couldn't run netstat."; @NETSTAT_TCP_LINES = ; close NETSTAT; #print "...Checking listening UDP ports via netstat...\n"; open NETSTAT,$netstat_udp_command or die "Error! Couldn't run netstat."; @NETSTAT_UDP_LINES = ; close NETSTAT; # Parse netstat to generate a list of open ports @OPEN_TCP_PORTS = (); @OPEN_UDP_PORTS = (); if ($Linux or $Solaris) { foreach $line (@NETSTAT_TCP_LINES) { # This line replaced at Valdis's suggestion on 8/15/2002, to handle IPv6 machines. # foreach $pattern ('\s+[^:]+:([^\s]+)\s.*LISTEN','\s+[^\.]+\.([^\s]+)\s.*LISTEN') { # This line replaced to correct problem where ports weren't detected on RH9 -check on RH7. # foreach $pattern ('.*:([^\s]+)\s+\S+\s.*LISTEN','\s+[^\.]+\.([^\s]+)\s.*LISTEN') { foreach $pattern (':(\S+)\s+\S+\s.*LISTEN','\s+[^\.]+\.(\S+)\s.*LISTEN') { if ($line =~ $pattern) { push @OPEN_TCP_PORTS,$1; $TCP_PORT_OPEN{$1}=1; } } } foreach $line (@NETSTAT_UDP_LINES) { # This line replaced at Valdis's suggestion on 8/15/2002, to handle IPv6 machines. # foreach $pattern ('\s+[^:]+:([^\s]+)\s.*:\*','\s+[^\.]+\.([^\s]+)\s.*Idle') { # This line replaced to correct problem where ports weren't detected on RH9 - check on RH7. # foreach $pattern ('.*:([^\s]+)\s.*:\*','\s+[^\.]+\.([^\s]+)\s.*Idle') { foreach $pattern (':(\S+)\s.*:\*','\s+[^\.]+\.([^\s]+)\s.*Idle') { if ($line =~ $pattern) { push @OPEN_UDP_PORTS,$1; $UDP_PORT_OPEN{$1}=1; } } } } elsif ($HP) { # Merge this code with the Linux/Solaris code later, during a development cycle. # Right now, we just tested near-perfect on Linux and Solaris, and I don't want # to threaten that by re-writing that code too much foreach $line (@NETSTAT_UDP_LINES) { if ($line =~ /^udp\s+[^\s]+\s+[^\s]+\s+[^\.]\.(\d+)/) { push @OPEN_UDP_PORTS,$1; $UDP_PORT_OPEN{$1}=1; } elsif ($line =~ /^tcp\s+[^\s]+\s+[^\s]+\s+[^\.]\.(\d+)/) { push @OPEN_TCP_PORTS,$1; $TCP_PORT_OPEN{$1}=1; } } } # HP: Grab NDD settings out of /etc/rc.config.d/nddconf if ($HP) { open NDDCONF,"/etc/rc.config.d/nddconf"; my @lines = ; close NDDCONF; chomp @lines; # Read all the information into a temporary hash, using # this to create a more permanent global one. my %hash; foreach $line (@lines) { if ($line =~ /^\s*TRANSPORT_NAME[(\d+)]\s*=\s*(.*)$/) { $hash{$1}{'protocol'}=$2; } if ($line =~ /^\s*NDD_NAME[(\d+)]\s*=\s*(.*)$/) { $hash{$1}{'name'}=$2; } if ($line =~ /^\s*NDD_VALUE[(\d+)]\s*=\s*(.*)$/) { $hash{$1}{'value'}=$2; } } foreach $key (%hash) { my $name = $hash{$key}{'protocol'} . "." . $hash{$key}{'name'}; $NDD{$name} = $hash{$key}{'value'}; } } #print "\nBeginning system evaluation...\n"; ## # 1.1.Solaris Check if system has been patched in the last month. # 1.1.Linux # 1.1.HP Use Security Patch Check to verify that the system has been # patched recently. # # Note: we thought about checking for a reboot since last patch, but # many sites patch without reboots if they understand the patch's # function well and need seriously high uptimes. # $weight{'patch'}=1; $score{'patch'}=0; # First find the most recently modified directory here my $latest_filemod_date =0; if ($Solaris) { opendir PDIRS,"/var/sadm/patch"; @lines = readdir PDIRS; closedir PDIRS; chomp @lines; @directories = grep /^\d+-\d+$/,@lines; $score{'patch'} = 0; if (@directories) { foreach $dir (@directories) { $dir = "/var/sadm/patch/" . $dir; #&Log("1.1 Checking directory $dir mtime."); my $mtime = (stat($dir))[9]; #&Log("mtime = $mtime"); if ($mtime > $latest_filemod_date) { $latest_filemod_date = $mtime; } } # Is this date within the last month? (within the last 2592000 sec) my $current_time = time; if ( ($current_time - $latest_filemod_date) < 2592000 ) { $score{'patch'} = 1; } } if ($score{'patch'}) { &Log("Positive: 1.1 System appears to have been patched within the last month."); } else { &Log("Negative: 1.1 System appears not to have been patched within the last month."); } } elsif ($Linux) { # Linux is a different case. rpm will tell us when the latest packages were # installed, but won't do it in the same format as perl's time. Instead, # it gives us it in the same format as date. We can do subtraction... open RPM_COMMAND,"/bin/rpm --last -qa | /bin/grep -v CISscan |"; my $most_recent_package_line = ; close RPM_COMMAND; # # We need to match/parse a date that looks like this: # # Thu 06 Sep 2001 04:27:08 AM EDT # We also need to match this: # Tue Feb 18 16:31:46 2003 # # We need to do this ourselves, since our method of # makes using/relying on other modules troublesome # First, parse the rpm's install date my %months; $months{'Jan'}=0; $months{'Feb'}=1; $months{'Mar'}=2; $months{'Apr'}=3; $months{'May'}=4; $months{'Jun'}=5; $months{'Jul'}=6; $months{'Aug'}=7; $months{'Sep'}=8; $months{'Oct'}=9; $months{'Nov'}=10; $months{'Dec'}=11; my $rpm_day,$rpm_month,$rpm_year; my $rpm_seconds,$rpm_minutes,$rpm_hours; my $parsed_date = 1; if ($most_recent_package_line =~ /^\S+\s+\w{3}\s+(\d+)\s+(\w{3})\s+(\d{4})\s+(\d{2})\:(\d{2})\:(\d{2})\s+(\w+)/) { # Date format: Thu 06 Sep 2001 04:27:08 AM EDT $rpm_day = $1; $rpm_month = $months{$2}; $rpm_year = $3; $rpm_hours = $4; $rpm_minutes = $5; $rpm_seconds = $6; if ($7 eq 'PM') { $rpm_hours += 12; } } elsif ($most_recent_package_line =~ /^\S+\s+\w{3}\s+(\w{3})\s+(\d{2})\s+(\d{2})\:(\d{2})\:(\d{2})\s+(\d{4})\b/) { # Date format: Tue Feb 18 16:31:46 2003 $rpm_month = $1; $rpm_day = $2; $rpm_hours = $3; $rpm_minutes = $4; $rpm_seconds = $5; $rpm_year = $6; } else { &Log("Negative: 1.1 Couldn't parse date on most recently installed RPM. Please e-mail linux-scan\@cisecurity.org."); $parsed_date = 0; } # Next, figure out roughly how many seconds this is past the epoch: Jan 1, 1970 00:00:00 $rpm_total_seconds = ((( ($rpm_year-1970)*365.25+($rpm_month)*30.5+($rpm_day-1))*24+$rpm_hours)*60+$rpm_minutes)*60+$rpm_seconds; # Finally, do some subtraction $time_total_seconds = time; if ( ($time_total_seconds-$rpm_total_seconds) < (2592000 ) ) { &Log("Positive: 1.1 System appears to have been patched within the last month."); $score{'patch'}=1; } else { &Log("Negative: 1.1 System appears not to have been patched within the last month."); } } elsif ($HP) { $score{'patch'}=1; # # New algorithm: we're using Security Patch Check to check whether the # system's been adequately patched. # # We'll want to log the datestamp of the patch catalog. my $datestamp = 'notfound'; my $spc_dir = '/var/opt/CIS/spc'; if ($numeric_version >= 11.00) { if ( -x '/opt/sec_mgmt/spc/bin/security_patch_check') { my $catalog_good=0; # Check to see if security_catalog is available and up-to-date unless (&security_patch_catalog_good($spc_dir)) { my $got_catalog = 0; # # We'll need to download a copy # # First, set environment variable PASSIVE_FTP to get through more firewalls $ENV{'PASSIVE_FTP'} = 1; if (open GETCATALOG,"/opt/sec_mgmt/spc/bin/security_patch_check -r -c $spc_dir/security_catalog |") { my @lines = ; close GETCATALOG; } } if (&security_patch_catalog_good($spc_dir)) { if (open SPC,"/opt/sec_mgmt/spc/bin/security_patch_check -qq -m -c $spc_dir/security_catalog |") { # Get SPC output. my @lines = ; close SPC; chomp @lines; # If we get no non-blank lines, this system needs no patches. # This is specific to -qq mode, which will not give us information about # recalled patches or misc warnings. # # For now, we don't need to parse the lines. It's very simple! # Parsing the output would not be difficult. The machine-readable output is # broken up by blank lines into patch records. Each patch record begins # with a patch name on a line by itself, with a single colon terminating. # Description fields follow. if (grep !/^\s*$/,@lines) { &Log("Negative: 1.1 Security Patch Check indicates that this system requires patches. Run this command to see the report: /opt/sec_mgmt/spc/bin/security_patch_check -qq -m -c $spc_dir/security_catalog"); $score{'patch'}=0; # This is where parsing code would go. Here's a start. my $in_a_patch_record = 0; my @patches_required; foreach $line (@lines) { if ($line =~ /^\s*$/) { $in_a_patch_record = 0; } unless ($in_a_patch_record) { if ($line =~ /^([\w_]+)\:\s*$/) { $in_a_patch_record = 1; push @patches_required,$1; } } } } } else { &Log("Negative: 1.1 Security Patch Check could not be started via command: /opt/sec_mgmt/spc/bin/security_patch_check -qq -m -c $spc_dir/security_catalog.\n"); $score{'patch'}=0; } } else { &Log("Negative: 1.1 System's security catalog is either not present or older than 1 month and we could not retrieve a replacement. Use the following command to retrieve the current one from HP: /opt/sec_mgmt/spc/bin/security_patch_check -r -c $spc_dir/security_catalog"); $score{'patch'}=0; } } else { &Log("Negative: 1.1 /opt/sec_mgmt/spc/bin/security_patch_check cannot be run to check patch status."); $score{'patch'}=0; } } else { &Log("Negative: 1.1 Security Patch Check cannot be installed in HP-UX bersions before 11.00 -- please consider upgrading."); $score{'patch'}=0; } if ($score{'patch'}) { # We want to note how up-to-date the patch catalog was and note this in the log. my $age_catalog_days = ( (time() - $datestamp )/(3600*24) )+1; &Log("Positive: 1.1 This system is not more than $age_catalog_days behind on patches."); } } ## # 1.2.Solaris Is TCP Wrappers installed? # 1.2.HP # 1.2.formerly.Linux Are TCP Wrappers or xinetd configured for access control/banners? if ($Solaris or $HP) { $weight{'tcpd'}=1; $score{'tcpd'}=1; if ($GLOBAL_INETD_RUNNING) { my @lines = grep !/^\s*\#/,@GLOBAL_INETD_CONF_LINES ; if (@lines) { foreach $line (@lines) { my @entry = split /\s+/,$line; if (($entry[2] eq 'tcp') or ($entry[2] eq 'udp') or ($entry[2] eq 'tcp6') or ($entry[2] eq 'udp6')) { unless (($entry[5] =~ /\/tcpd$/) or ($HP and $INETDSEC{$entry[0]})) { # Complain about not being wrapped. my $name = $entry[0]; my $protocol = $entry[2]; if ($HP and ($name eq 'rpc')) { $name = $entry[8]; } &Log("Negative: 1.2 $protocol-protocol service $name in inetd.conf is not wrapped."); $score{'tcpd'}=0; } } } if ( $score{'tcpd'} ) { &Log("Positive: 1.2 All inetd-based services are wrapped with TCP Wrappers"); } } else { &Log("Positive: 1.2 inetd has no active lines, so tcpd isn't necessary."); } } else { &Log("Positive: 1.2 inetd is not running, so tcpd isn't necessary."); } } ## # 1.3.Solaris Is ssh on the system and configured properly? # 1.2.Linux # 1.3.HP if ($Solaris or $Linux or $HP) { $weight{'ssh'}=1; $score{'ssh'}=1; my $check_config = 1; if ($Solaris) { $number = '1.3'; if ($version <= 5.8) { $check_config = 0; } } elsif ($Linux) { $number = '1.2'; if ( ($DISTRIBUTION eq 'RH' and $DISTRIBUTION_VERSION < 7.0) or ($DISTRIBUTION eq 'MD' and $DISTRIBUTION_VERSION < 8.0) ) { $check_config = 0; } } elsif ($HP) { $number = '1.3'; if ($version < 11.00) { $check_config = 0; } } my $sshd_not_running_positive = 0; my $ssh_client_checked = 0; unless ( (grep /\bsshd\b/,@GLOBAL_PS_LINES) or ( grep /\bsshd2\b/,@GLOBAL_PS_LINES) ){ if ( ($GLOBAL_INETD_RUNNING or $GLOBAL_XINETD_RUNNING) and (not $TCP_PORT_OPEN{'22'}) ) { &Log("Negative: $number System isn't running sshd."); $score{'ssh'}=0; } else { $sshd_not_running_positive = 1; # &Log("Positive: $number System isn't running sshd, but isn't running inetd so you're probably not using any remote access/administration tool."); } } else { if ( $check_config ) { # # # On Solaris, we want to confirm that sshd is running with MaxAuthTries{,Log} set to 3 and 0, # Protocol set to 2, IgnoreRhosts set to yes, PermitRootLogin set to no, and Banner set to a non-zero-length # file. # # On Linux and HP-UX, we want to check everything listed above, with the exception of the MaxAuthTries{,Log} # directives, which were added by Sun. # # # First, define what file we're going to check. my $config_file = '/etc/ssh/sshd_config'; if ($HP) { $config_file = '/opt/ssh/etc/sshd_config'; if ( ! -f '/opt/ssh/etc/sshd_config') { if ( -f '/etc/opt/ssh/sshd_config') { $config_file = '/etc/opt/ssh/sshd_config'; &Log("Note: $number Found sshd_config in /etc/opt/ssh/ instead of HP-UX default of /opt/ssh/etc."); } elsif ( -f '/etc/opt/openssh/sshd_config') { $config_file = '/etc/opt/openssh/sshd_config'; &Log("Note: $number Found sshd_config in /etc/opt/openssh/ instead of HP-UX default of /opt/ssh/etc."); } } } my $return = open SSHCONF,$config_file; if ($return) { my @lines = ; close SSHCONF; chomp @lines; my $protocol = 'notfound'; my $tries = 'notfound'; my $log = 'notfound'; my $ignore_rhosts = 'notfound'; my $permit = 'notfound'; my $empty_passwords = 'notfound'; my $banner = 'notfound'; foreach $line (@lines) { next if ($line =~ /^\s*\#/); if ($line =~ /^\s*Protocol\s+([12\,]+)\s*$/) { $protocol=$1; } if ($line =~ /^\s*MaxAuthTries\s+(\d+)/) { $tries = $1; } elsif ($line =~ /^\s*MaxAuthTriesLog\s+(\d+)/) { $log = $1; } elsif ($line =~ /^\s*IgnoreRhosts\s+(\w+)\s*$/) { $ignore_rhosts = $1; } elsif ($line =~ /^\s*PermitRootLogin\s+(.*)/) { $permit = $1; } elsif ($line =~ /^\s*PermitEmptyPasswords\s+(.*)/) { $empty_passwords = $1; } elsif ($line =~ /^\s*Banner\s+(.*)$/) { $banner = $1; } } if ($protocol ne 'notfound') { if ($protocol ne '2') { &Log("Negative: $number sshd_config parameter Protocol, currently $protocol, should be set to 2."); $score{'ssh'}=0; } } else { # This defaults to 2,1, which is unacceptable. &Log("Negative: $number sshd_config parameter Protocol is not set."); $score{'ssh'}=0; } if ($Solaris) { # This directive was added by Sun and isn't supported on Linux. if ($tries ne 'notfound') { if ($tries > 3) { &Log("Negative: $number sshd_config parameter MaxAuthTries, currently $tries, should be set to no more than 3."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter MaxAuthTries is not set."); $score{'ssh'}=0; } } if ($Solaris) { # This directive was added by Sun and isn't supported on Linux. if ($log ne 'notfound') { if ($log > 0) { &Log("Negative: $number sshd_config parameter MaxAuthTriesLog, currently $log, should be set to 0."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter MaxAuthTriesLog is not set."); $score{'ssh'}=0; } } if ($ignore_rhosts ne 'notfound') { # If this isn't set, it defaults to yes. if ($ignore_rhosts !~ /yes/i) { &Log("Negative: $number sshd_config parameter IgnoreRhosts, currently $ignore_rhosts, should be set to yes."); $score{'ssh'}=0; } } # We don't need to check RhostsAuthentication or RhostsRSAAuthentication, as they both apply only to # protocol 1, which we're currently scoring negative for. if ($permit ne 'notfound') { unless ($permit =~ /no/i) { &Log("Negative: $number sshd_config parameter PermitRootLogin, currently $permit, should be set to no."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter PermitRootLogin is not set."); $score{'ssh'}=0; } if ($empty_passwords ne 'notfound') { unless ($empty_passwords =~ /no/i) { &Log("Negative: $number sshd_config parameter PermitEmptyPasswords, currently $empty_passwords, should be set to no."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter PermitEmptyPasswords is not set."); $score{'ssh'}=0; } ############################################################################################### # Older versions of Red Hat shipped with an SSH daemon that didn't take a Banner argument. # We can't require it for now. 7/8/03. Reconsider this in 2004. ############################################################################################### unless ($Linux) { if ($banner ne 'notfound') { if ( -f $banner ) { unless ( -s $banner ) { &Log("Negative: $number sshd_config parameter Banner, currently $banner, should be set to a file with non-zero size."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter Banner, currently $banner, is not a regular file."); $score{'ssh'}=0; } } else { &Log("Negative: $number sshd_config parameter Banner is not set."); $score{'ssh'}=0; } } } else { &Log("Negative: $number System is running sshd, but we can't find sshd_config to test settings."); $score{'ssh'}=0; } } } # We want to measure the client settings even if sshd isn't running. if ( $check_config ) { $ssh_client_checked = 1; my $config_file = '/etc/ssh/ssh_config'; if ($HP) { $config_file = '/opt/ssh/etc/ssh_config'; if ( ! -f '/opt/ssh/etc/ssh_config') { if ( -f '/etc/opt/ssh/ssh_config') { $config_file = '/etc/opt/ssh/ssh_config'; &Log("Note: $number Found ssh_config in /etc/opt/ssh/ instead of HP-UX default of /opt/ssh/etc."); } elsif ( -f '/etc/opt/openssh/ssh_config') { $config_file = '/etc/opt/openssh/ssh_config'; &Log("Note: $number Found ssh_config in /etc/opt/openssh/ instead of HP-UX default of /opt/ssh/etc."); } } } if (open SSHCONFIG,$config_file) { my @lines = ; close SSHCONFIG; chomp @lines; # Parse the ssh_config file -- look for Host blocks. # We'll only score a negative on the Host * block, but we need to warn on the others. my $host = ''; my $found_default_value = 0; foreach $line (@lines) { if ($line =~ /^\s*Host\s+(.*)/) { $host = $1; } else { # OK, so we're not looking at a host line. if ($line =~ /^\s*Protocol\s+([12\,]+)\s*$/) { # We've got a procotol line. my $value = $1; if ($host eq '*') { $found_default_value = 1; # Yes, we could make this more efficient by combing the two if ($host eq '*') blocks.... } unless ($value eq '2') { if ($host eq '*') { &Log("Negative: $number ssh_config parameter Protocol is currently set to $value in Host * block. The value should be 2."); $score{'ssh'}=0; } else { &Log("Warning: $number ssh_config parameter Protocol is currently set to $value in Host $host block. The value should be 2."); } } } } } unless ( $found_default_value ) { &Log("Negative: $number ssh_config must have 'Protocol 2' underneath Host *."); $score{'ssh'}=0; } } else { &Log("Negative: $number Can't find ssh_config."); $score{'ssh'}=0; } } # We're done. Report positive if score is intact. if ($score{'ssh'}) { if ($sshd_not_running_positive) { if ($ssh_client_checked) { &Log("Positive: $number System isn't running sshd, but inetd/xinetd isn't active to supply insecure remote administration. ssh client was configured well."); } else { &Log("Positive: $number System isn't running sshd, but inetd/xinetd isn't active to supply insecure remote administration."); } } else { if ($ssh_client_checked) { &Log("Positive: $number SSH client and server are configured well."); } else { &Log("Positive: $number System is running sshd and it's configured well."); } } } } ###################################################### # Minimize inetd network services ###################################################### ## # 2.1.Solaris Disable standard inetd-related services # 2.1.Linux # 2.1.HP if ($Solaris or $Linux or $HP) { $weight{'inetd_standard'}=1; $score{'inetd_standard'}=1; my $number = '2.1'; my @ports; if ($Solaris) { @ports = ('time','echo','discard','daytime','chargen','fs','dtspc', 'exec','comsat','talk','finger','uucp','name','xaudio', '100068/2-5','100146/1','100147/1','100150/1','100155/1','100221/1', '100232/10','100235/1','rstatd/2-4','rusersd/2-3','sprayd/1','walld/1'); } elsif ($Linux) { @ports = ('auth','bootps','cfinger','chargen','chargen-udp','comsat', 'daytime','daytime-udp','discard','dtalk','echo','echo-udp', 'exec','finger','linuxconf','netstat','ntalk','rexec','rsync', 'server','services','sgi_fam','systat','talk','time','time-udp','uucp'); } elsif ($HP) { @ports = ('echo','discard','daytime','chargen','dtspc','exec','ntalk', 'finger','uucp','ident','auth','instl_boots','registrar','recserv', '100001/2-4','100002/1-2','100008/1','100012/1','100068/2-5'); } foreach $port (@ports) { if ($GLOBAL_ACTIVE_INETD_SERVICES{$port}) { if ($Linux and $GLOBAL_XINETD_SVC_FILE{$port}) { &Log("Negative: $number xinetd service $port requires full deactivation -- you should set 'disable=yes' in $GLOBAL_XINETD_SVC_FILE{$port}."); } else { &Log("Negative: $number inetd service $port requires full deactivation -- comment out or delete its line in inetd.conf."); } $score{'inetd_standard'}=0; } } if ($score{'inetd_standard'}) { &Log("Positive: $number inetd/xinetd is not listening on any of the miscellaneous ports checked in this item."); } } ## # 2.2.Solaris Check if telnet is activated # 2.2.Linux # 2.2.HP $weight{'telnet'}=1; $score{'telnet'}=0; if ($Linux) { $number = '2.2'; } elsif ($Solaris) { $number = '2.2'; } elsif ($HP) { $number = '2.2'; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'telnet'} ) { &Log("Negative: $number telnet not deactivated."); } else { &Log("Positive: $number telnet is deactivated."); $score{'telnet'}=1; } ## # 2.3.Solaris Check if ftp is activated # 2.3.Linux # 2.3.HP $weight{'ftp'}=1; $score{'ftp'}=0; if ($Linux) { $number = '2.3'; } elsif ($Solaris) { $number = '2.3'; } elsif ($HP) { $number = '2.3'; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'ftp'} ) { &Log("Negative: $number ftp not deactivated."); } else { &Log("Positive: $number ftp is deactivated."); $score{'ftp'}=1; } ## # 2.4.Solaris Check if rsh/rcp/rlogin are activated # 2.4.Linux # 2.4.HP $weight{'rsh_rlogin'}=1; $score{'rsh_rlogin'}=1; if ($Linux) { $number = '2.4'; } elsif ($Solaris) { $number = '2.4'; } elsif ($HP) { $number = '2.4'; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'shell'} ) { &Log("Negative: $number rsh (shell) should be deactivated."); $score{'rsh_rlogin'}=0; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'login'} ) { &Log("Negative: $number rlogin (login) should be deactivated."); $score{'rsh_rlogin'}=0; } if ( $score{'rsh_rlogin'} ) { &Log("Positive: $number rsh, rcp and rlogin are deactivated."); } ## # 2.5.Solaris Check if tftp is activated # 2.5.Linux # 2.5.HP $weight{'tftp'}=1; $score{'tftp'}=0; if ($Linux) { $number = '2.5'; } elsif ($Solaris) { $number = '2.5'; } elsif ($HP) { $number = '2.5'; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'tftp'} ) { &Log("Negative: $number tftp should be deactivated."); } else { &Log("Positive: $number tftp is deactivated."); $score{'tftp'}=1; } ## # 2.6.Linux Check if IMAP is active if ($Linux) { $number = '2.6'; $weight{'imap'}=1; $score{'imap'}=1; foreach $imap ('imap','imaps','imap3') { if ( $GLOBAL_ACTIVE_INETD_SERVICES{$imap} ) { &Log("Negative: $number IMAP server is listening on port $imap -- it should be deactivated."); $score{'imap'}=0; } } if ($score{'imap'}) { &Log("Positive: $number imap is deactivated."); } } ## # 2.7.Linux Check if POP is active if ($Linux) { $number = '2.7'; $weight{'pop'}=1; $score{'pop'}=1; foreach $pop ('pop-2','pop-3','pop2','pop3','ipop2','ipop3','pop3s','postoffice','kpop') { if ( $GLOBAL_ACTIVE_INETD_SERVICES{$pop} ) { &Log("Negative: $number POP server is listening on port $pop -- it should be deactivated."); $score{'pop'}=0; } } if ($score{'pop'}) { &Log("Positive: $number POP server is deactivated."); } } if ($Solaris or $HP) { ## # 2.6.Solaris Check if this is a network printer server # 2.6.HP $weight{'network_print'}=1; $score{'network_print'}=0; if ( $Solaris and ($version < 5.6)) { &Log("Not Applicable: 2.6 BSD compatible print server not available prior to Solaris 2.6"); $weight{'network_print'}=0; } elsif ( $GLOBAL_ACTIVE_INETD_SERVICES{'printer'} ) { &Log("Negative: 2.6 BSD-compatible print server should be deactivated."); } else { &Log("Positive: 2.6 BSD-compatible print server is deactivated."); $score{'network_print'}=1; } ## # 2.7.Solaris Check if rquotad is activated # 2.7.HP $weight{'rquotad'}=1; $score{'rquotad'}=0; if ($HP) { $rquota_port = '100011/1'; } else { $rquota_port = 'rquotad/1'; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{$rquota_port} ) { &Log("Negative: 2.7 rquotad is not deactivated."); } else { &Log("Positive: 2.7 rquotad is deactivated."); $score{'rquotad'}=1; } ## # 2.8.Solaris Check if CDE-related daemons are activated # 2.8.HP $weight{'cde_daemons'}=1; $score{'cde_daemons'}=1; if ( $GLOBAL_ACTIVE_INETD_SERVICES{'100083/1'} ) { my $name; if ($HP) { $name = 'rpc.ttdbserver'; } else { $name = 'rpc.ttdbserverd'; } &Log("Negative: 2.8 CDE-related daemon $name not deactivated in inetd.conf."); $score{'cde_daemons'}=0; } if ($Solaris) { if ( $GLOBAL_ACTIVE_INETD_SERVICES{'fs'} ) { &Log("Negative: 2.8 CDE-related daemon fs.auto (port fs) not deactivated in inetd.conf."); $score{'cde_daemons'}=0; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'100221/1'} ) { &Log("Negative: 2.8 CDE-related daemon kcms_server not deactivated in inetd.conf."); $score{'cde_daemons'}=0; } } if ($score{'cde_daemons'}) { &Log("Positive: 2.8 CDE-related daemons are deactivated."); } ## # 2.9.Solaris Are the Solaris volume manager daemons activated? if ($Solaris and ($version >= 5.9)) { $weight{'disksuite_network'} = 1; $score{'disksuite_network'} = 1; my %name = ('100229/1','rpc.metad','100230/1','rpc.metamhd','100242/1','rpc.metamedd'); foreach $key (keys(%name)) { if ($GLOBAL_ACTIVE_INETD_SERVICES{$key}) { &Log("Negative: 2.9 Disksuite-related network daemon $name{$key} is active in inetd.conf."); $score{'disksuite_network'} = 0; } } if ($score{'disksuite_network'}) { &Log("Positive: 2.9 Disksuite-related network daemons are all deactivated."); } } elsif ($Solaris) { &Log("Not applicable: 2.9 Not applicable on Solaris versions prior to 9."); } ## # 2.10.Solaris Check if kerberos network daemons are activated # 2.9.HP if (($HP) or ( ($Solaris) and ($version >= 5.8) )) { $weight{'kerberos_net_daemons'}=1; $score{'kerberos_net_daemons'}=1; my $number; if ($Solaris) { $number = '2.10'; if ( $GLOBAL_ACTIVE_INETD_SERVICES{'100134/1'} ) { &Log("Negative: $number kerberos net daemon ktkt_warnd not deactivated in inetd.conf."); $score{'kerberos_net_daemons'}=0; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'100234/1'} ) { &Log("Negative: $number kerberos net daemon gssd not deactivated in inetd.conf."); $score{'kerberos_net_daemons'}=0; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'kerbd/4'} ) { &Log("Negative: $number kerberos net daemon kerbd not deactivated in inetd.conf."); $score{'kerberos_net_daemons'}=0; } } elsif ($HP) { $number = '2.9'; if ( $GLOBAL_ACTIVE_INETD_SERVICES{'kshell'} ) { &Log("Negative: $number kerberos net daemon remshd not deactivated in inetd.conf."); $score{'kerberos_net_daemons'}=0; } if ( $GLOBAL_ACTIVE_INETD_SERVICES{'klogin'} ) { &Log("Negative: $number kerberos net daemon rlogind not deactivated in inetd.conf."); $score{'kerberos_net_daemons'}=0; } } if ($score{'kerberos_net_daemons'}) { &Log("Positive: $number kerberos network daemons are deactivated."); } } elsif ($Solaris) { &Log("Not applicable: 2.10 Not applicable on Solaris versions prior to 8."); } } ## # 2.10.HP Only enable BOOTP/DHCP daemon if absolutely necessary if ($HP) { $weight{'dhcp_server'} = 1; $score{'dhcp_server'} = 1; if ($GLOBAL_ACTIVE_INETD_SERVICES{'bootps'}) { &Log("Negative: 2.10 DHCP Server (bootps) is active in inetd.conf."); $score{'dhcp_server'} = 0; } if ($score{'dhcp_server'}) { &Log("Positive: 2.10 DHCP Server (bootps) is not active in inetd.conf."); } } ########################### # Minimize boot services ########################### ## # 3.1.Solaris Is serial prompt login prompt disabled? # 3.1.HP if ($Solaris or $HP) { $weight{'serial_login_prompt'}=1; $score{'serial_login_prompt'}=0; my $pattern; if ($Solaris) { $pattern = '^[^\#]+:\s*\/usr\/lib\/saf\/sac'; } elsif ($HP) { $pattern = '^[^\#]+getty.*tty'; } my $return = open INITTAB,"/etc/inittab"; if ($return) { my @lines = ; close INITTAB; chomp @lines; my $found_match = 0; foreach $line (@lines) { next if ($line =~ /^\s*\#/); if ($line =~ $pattern) { $found_match = 1; } } if ($found_match) { &Log("Negative: 3.1 Serial login prompt not disabled."); } else { &Log("Positive: 3.1 Serial login prompt is disabled."); $score{'serial_login_prompt'}=1; } } else { &Log("Negative: 3.1 Can't open /etc/inittab to check for serial login prompt."); } } ## # 3.2.Solaris Check daemon umask # 3.1.Linux Check daemon umask if ($Solaris or $Linux) { $weight{'daemon_umask'}=1; $score{'daemon_umask'}=1; if ($Solaris) { $number = "3.2"; } elsif ($Linux) { $number = "3.1"; } my $daemon_umask = ""; my $file =""; if ( ($Solaris) and ( $version >= 5.8 ) ) { # For Solaris 8, we check that CMASK is set in /etc/default/init my $return = open INIT,"/etc/default/init"; if ($return) { my @lines = ; close INIT; chomp @lines; my $found_cmask_line = 0; foreach $line (@lines) { if ($line =~ /^\s*CMASK\s*=\s*(\d+)/) { my $mask = oct($1); $daemon_umask = $mask; $file = '/etc/default/init'; unless ( ($mask & 002) and ($mask & 020)) { my $umask = sprintf "%03lo",$mask; &Log("Negative: $number Daemon umask $umask set in /etc/default/init isn't strong enough."); $score{'daemon_umask'}=0; } $found_cmask_line = 1; } } unless ($found_cmask_line) { &Log("Negative: $number On a Solaris 8 or later system and couldn't find a CMASK line in /etc/default/init to set the daemon umask."); $score{'daemon_umask'}=0; } } else { &Log("Negative: $number On a Solaris 8 or later system and couldn't open /etc/default/init to check CMASK -- is there a good daemon umask?"); $score{'daemon_umask'}=0; } } else { # We're on a Linux system or Solaris <2.8 system # If we're on a Linux system, we can check the /etc/rc.d/init.d/functions file # as this gets loaded every time an rc-script loads and thus would be a great # place to set a umask my $found_good_functions_file = 0; if ( ($Linux) and ( -f '/etc/rc.d/init.d/functions') ) { my $umask = &Check_Umask('/etc/rc.d/init.d/functions'); $daemon_umask = $umask; $file = '/etc/rc.d/init.d/functions'; if ( ($umask & 022) == 022) { my $mask = sprintf "%03lo",$umask; # &Log("Positive: $number Good umask $mask set in $file."); $found_good_functions_file = 1; } else { &Log("Note: $number Bad or no umask set in /etc/rc.d/init.d/functions -- checking first init script now."); } } unless ($found_good_functions_file) { # Check the first rc script in /etc/rcX.d/ for # # 1) it must end in .sh to use umask command # 2) use umask command to set a umask at least of 022 my $first_script_name = $GLOBAL_ACTIVE_RC_SCRIPTS[0]; my $first_script = $GLOBAL_RC_SCRIPT_PATH{$first_script_name}; my $umask = &Check_Umask($first_script); if ($umask) { $daemon_umask = $umask; $file = $first_script; unless ( ($umask & 022) == 022 ) { my $mask = sprintf "%03lo",$umask; &Log("Negative: $number umask found in first /etc/rcX.d script $first_script, but umask ($mask) not restrictive enough."); $score{'daemon_umask'}=0; } } else { &Log("Negative: $number umask not found in first /etc/rcX.d script $first_script."); $score{'daemon_umask'}=0; } } } if ($score{'daemon_umask'}) { my $umask = sprintf "%03lo",$daemon_umask; &Log("Positive: $number Found a good daemon umask of $umask in $file."); } } ## # 3.3.Solaris Is inetd disabled? # 3.2.Linux Is inetd/xinetd disabled? # 3.2.HP if ($Solaris or $Linux or $HP) { $weight{'inetd_rc_script'}=1; $score{'inetd_rc_script'}=1; my $number; if ($Solaris) { $number = "3.3"; } elsif ($Linux) { $number = '3.2'; } elsif ($HP) { $number = "3.2"; } if ($GLOBAL_INETD_RUNNING) { &Log("Negative: $number inetd is still active."); $score{'inetd_rc_script'}=0; } if ($GLOBAL_XINETD_RUNNING) { &Log("Negative: $number xinetd is still active."); $score{'inetd_rc_script'}=0; } if ($score{'inetd_rc_script'}) { &Log("Positive: $number inetd has been deactivated."); } } ## # 3.3.HP Is this machine running NIS-related processes? if ($HP) { $weight{'nis'}=1; $score{'nis'}=1; if ($GLOBAL_ACTIVE_RC_SCRIPTS{'nis.server'}) { if (($RC_CONFIG{'namesvrs'}{'NIS_MASTER_SERVER'} ==1) or ($RC_CONFIG{'namesvrs'}{'NIS_SLAVE_SERVER'} ==1)) { &Log("Negative: 3.3 NIS server script nis.server not deactivated."); $score{'nis'}=0; } } if ($GLOBAL_ACTIVE_RC_SCRIPTS{'nisplus.server'}) { if ($RC_CONFIG{'namesvrs'}{'NISPLUS_SERVER'} == 1) { &Log("Negative: 3.3 NIS server script nisplus.server not deactivated."); $score{'nis'}=0; } } if ($GLOBAL_ACTIVE_RC_SCRIPTS{'nis.client'}) { if ($RC_CONFIG{'namesvrs'}{'NIS_CLIENT'} == 1) { &Log("Negative: 3.3 NIS client script nis.client not deactivated."); $score{'nis'}=0; } } if ($GLOBAL_ACTIVE_RC_SCRIPTS{'nisplus.client'}) { if ($RC_CONFIG{'namesvrs'}{'NISPLUS_CLIENT'} == 1) { &Log("Negative: 3.3 NIS client script nisplus.client not deactivated."); $score{'nis'}=0; } } if ($GLOBAL_ACTIVE_RC_SCRIPTS{'pwgr'}) { if (($RC_CONFIG{'pwgr'}{'PWGR'} == 1) and ( ($RC_CONFIG{'namesvrs'}{'NISPLUS_CLIENT'} == 0) or ($RC_CONFIG{'pwgr'}{'PWGRD_WITH_NISPLUS'} == 1 ) ) ) { &Log("Negative: 3.3 NIS-related script pwgr not deactivated."); $score{'nis'}=0; } } if ( $score{'nis'} ) { &Log("Positive: 3.3 NIS-related processes are deactivated."); } } ## # 3.4.Solaris Is syslogd listening to the network? if ($Solaris and ($version >= 5.8)) { $weight{'syslogd_listening'}=1; $score{'syslogd_listening'}=0; my @syslog_lines = grep /syslogd\W*/,@GLOBAL_PS_LINES; unless (@syslog_lines) { $score{'syslogd_listening'}=1; &Log("Positive: 3.4 System is not running syslogd, thus syslogd is not listening to the network."); } else { foreach $line (@syslog_lines) { # Should we be parsing this line more carefully, in case the config file begins in -t? if ($line =~ /syslogd.*\s+-t/) { $score{'syslogd_listening'}=1; &Log("Positive: 3.4 syslogd has the -t switch and is thus not listening to the network."); } } unless ($score{'syslogd_listening'}) { &Log("Negative: 3.4 System is running syslogd without the -t switch, accepting remote logging."); } } } elsif ($Solaris) { &Log("Not applicable: 3.4 Not applicable on Solaris versions prior to 8."); } ## # 3.4.HP Are printer daemons deactivated? if ($HP) { $weight{'printer_init_script'}=1; $score{'printer_init_script'}=1; if ($GLOBAL_ACTIVE_RC_SCRIPTS{'tps.rc'} and ($RC_CONFIG{'tps'}{'XPRINTSERVERS'} !~ /^[\"\'\s]*$/)) { &Log("Negative: 3.4 printer-related script tps.rc not deactivated."); $score{'printer_init_script'}=0; } if ($GLOBAL_ACTIVE_RC_SCRIPTS{'lp'} and ($RC_CONFIG{'lp'}{'LP'} == 1)) { &Log("Negative: 3.4 printer daemon script lp not deactivated."); $score{'printer_init_script'}=0; } if (($GLOBAL_ACTIVE_RC_SCRIPTS{'pd'}) and ($RC_CONFIG{'pd'}{'PD_CLIENT'} == 1)) { &Log("Negative: 3.4 printer-related script pd not deactivated."); $score{'printer_init_script'}=0; } if ($score{'printer_init_script'}) { &Log("Positive: 3.4 The printer init scripts are deactivated."); } } ## # 3.5.HP Is GUI Login disabled? if ($HP) { $weight{'gui_login'}=1; $score{'gui_login'}=1; if ($GLOBAL_ACTIVE_RC_SCRIPTS{'dtlogin.rc'} and ($RC_CONFIG{'desktop'}{'DESKTOP'} ne '')) { &Log("Negative: 3.5 Graphical login not deactivated."); $score{'gui_login'}=0; } # Additionally, check permissions on some important files. my @files = ('/usr/dt/bin/dtaction','/usr/dt/bin/dtappgather', '/usr/dt/bin/dtprintinfo','/usr/dt/bin/dtsession'); foreach $file (@files) { my $mode = (stat($file))[2]; if ($mode & 002) { &Log("Negative: 3.5 $file should not be world-writable."); $score{'gui_login'}=0; } if ($mode & 020) { &Log("Negative: 3.5 $file should not be group-writable."); $score{'gui_login'}=0; } if ($mode & 04000) { &Log("Negative: 3.5 $file should not be Set-UID."); $score{'gui_login'}=0; } if ($mode & 02000) { &Log("Negative: 3.5 $file should not be Set-GID."); $score{'gui_login'}=0; } } if ($score{'gui_login'}) { &Log("Positive: 3.5 Graphical login is deactivated."); } } ## # 3.5.Solaris Is sendmail receiving network connections? # 3.3.Linux # 3.6.HP if ($Solaris or $Linux or $HP) { ## JJB: Note that this code is identical to 3.9.HP. This was only broken ## up because of divergent renumbering. $weight{'mail_daemon'}=1; $score{'mail_daemon'}=1; my $pattern; my $number; if ($Solaris) { $number = "3.5"; } elsif ($Linux) { $number = "3.3"; } elsif ($HP) { $number= '3.6'; } if ( $TCP_PORT_OPEN{'25'} ) { $score{'mail_daemon'}=0; &Log("Negative: $number Mail daemon is still listening on TCP 25."); } else { &Log("Positive: $number Mail daemon is not listening on TCP 25."); # Check anyway -- is sendmail running in -bd mode? if (@GLOBAL_PS_LINES) { if ( ($Solaris or $Linux) and (grep /\bsendmail\b.*\b-bd\b/,@GLOBAL_PS_LINES )) { &Log("$number Sendmail appears to be running in daemon mode?"); } elsif (($HP) and (grep /\bsendmail\:\saccepting/,@GLOBAL_PS_LINES)) { &Log("$number Sendmail appears to be running in daemon mode?"); } } else { &Log("$number Can't check ps -ef for Sendmail, since it didn't work."); } } } ## # 3.4.Linux Is GUI Login active? if ($Linux) { $weight{'gui_login'}=1; $score{'gui_login'}=1; # JJB: Like no-stack-exec items, this one requires a reboot to score # properly, or at least a telinit/init/shutdown.... if ($RUNLEVEL == 5) { &Log("Negative: 3.4 Graphical login not deactivated."); $score{'gui_login'}=0; } else { &Log("Positive: 3.4 Graphical login is deactivated."); } } ## # 3.5.Linux Is X Font Server active? if ($Linux) { $weight{'xfs'}=1; $score{'xfs'}=1; my $number = '3.5'; if ($GLOBAL_ACTIVE_RC_SCRIPTS{'xfs'}) { &Log("Negative: $number X Font Server (xfs) script has not been deactivated"); $score{'xfs'}=0; } else { &Log("Positive: $number X Font Server (xfs) script has been deactivated"); } } ## # 3.6.Solaris Are boot services disabled? if ($Solaris) { $weight{'boot_services'}=1; $score{'boot_services'}=1; my $number = '3.6'; if ($version >= 5.9) { if ($GLOBAL_ACTIVE_RC_SCRIPTS{'boot.server'}) { &Log("Negative: $number boot.server script has not been deactivated"); $score{'boot_services'}=0; } else { &Log("Positive: $number boot.server script has been deactivated"); } } else { if ($GLOBAL_ACTIVE_RC_SCRIPTS{'nfs.server'}) { if (open SCRIPT,'/etc/rc3.d/S15nfs.server') { my @lines =