#!/usr/bin/perl # # name : audit2.pl # # FUNCTION: Search for SUID, worldwriteable, trust, hacker, # sensitive and weird files. # It's very fast but can take 15 mins on a big box. # If you don't have perl on the target system, check out # 'audit3.sh' as an alternative. # # USAGE: Edit SETTINGS section below, then: # nice perl audit2.pl # There can also be a first argument, which is interpreted as ONE # filesystem only to be analysed. We use 'nice' to reduce the effect # on system performance (especially important on production systems) # $VERSION="audit2.pl/19.Jan.04"; # History : # <13> 19.Jan.04 sb Update comments # --- 2003 --- # <12> 10.Mar.03 sb Check for prefs.js. # <11> 02.Jan.03 sb Improve $get_fs_cmd for Linux, fix weirds. # --- 2002 --- # <10> 03.Oct.02 sb Fix regexp added in <3> :-) # <9> 14.Sep.02 sb Detect apache worm (/tmp/.bugtraq) # <8> 16.Aug.02 sb Fix to $exclude # <7> 23.Jul.02 sb print file counts even if 0, so we remind what # has actually been audited. # 06.Jun.02 sb Fix Joel Kergozou: ifconfig improvements. # 05.May.02 sb Ignore "$" files, too many java classes as FPs. # --- 2001 --- # 05.sep.01 sb Adapt for HP-UX 11, put '&' in front of functions for less # forgiving perl versions. Allow '!' is files (don't detect # as 'weird' (reduce false positives). Add '$' to # 'veryweirds'. Ignore sgid directories. Detect KOH worm. # Fix pruning, ignore filesystems in $exclude. Suse 7.1. # 06.Aug.01 sb Default output file in current dir. 'ls' errors to log, # not stderr. # 19.May.01 sb Detect cheese worm # <6> 09.Apr.01 sb Detect dmi hack kit, Adore worm, ARK rootkit. Reorganise # $weirds. $mode correct for group write files. # <5> 23.Mar.01 sb Detect Lion/t0rn('.lib's). Use Disksuite disks(P.Bayle). # <4> 15.Feb.01 sb Detect Ramen, histories, netscape. Count files. # --- 2000 --- # <3> 21.Dec.00 sb Improve regexps, emailing results is now optional # Only report GID if not SUID. Use variable for regexps. # Only report world write if not group write. Detect John. # Detect Veritas filesystems & analyse. # <2> 25.Aug.00 sb Rewrite as auditing script. # <1> V1.1 (Sean Boran) 1995 # # TESTED ON: # Perl4 doesn't work: only tested once on HPUX # Perl5 + SunOS 5.5/6/7/8/9, OpenBSD 2.6, RedHat7, Suse7.1, HP-UX11 # (Does NOT work on RH 6.0 Linux- broken "uname" in perl) # # LICENSE: # This script was developed by Sean Boran, http://www.boran.com. # It can be distributed for free as long as these headers are included. # Please send any bug fixes or improvements to sean AT boran.com. # # To Do: # - list binaries changed within the last 6 months # - command line options for results file, email & debug options # - config known file lists for worldwrite and suid, to report size # - md5? # - on sensitive files only report if size >0 ################################################################### # =======> SETTINGS: edit these variables if needed <=============== $exclude =''; # Don't examine these filesystems #$exclude ='/backup|/dir2'; # Don't examine these filesystems $debug =''; # '1'=debug (useful), ''=no debug (quiet) $debug2='0'; # '1'=print results to terminal also # (default is to write to file only) $email_results='0'; # '0'=write to file, don't email $user='root'; # send email to him/her $aggressive = '0'; # '1'=Immediately DELETE baddies # DO NOT DO THIS unless you # have reviewed these sources and tested on a # non-production machine first.. # ======== end of SETTINGS ============== # == more variables $hostname=`uname -n`; chop($hostname); $tmpfile = "$hostname.audit2.$$.log"; # put results here # == Regexps (for experts only, be v.careful) <3> =========== # Preamble: "veryweird" files that contain '\' '"' or '$' # are not examine at all, since we can't handle them # see <##> below # a) files that could be critical $configs='^\.exrc$|^\..*history$|^\.htaccess$|tomcat-passwd.xml|htpasswd$|^admpw|^magnus.conf|^admin.config|^adminacl|^.dbxrc|^.dbxinit|^.netrc'; # b) Trust files $trusts='^\.[sr]hosts$|authorized_keys|ssh_auth|hosts.equiv$'; # c) Signs of hacker tools and weird files/directories # Patterns are on separate line to ease reading. # Detect: ramen, john the ripper, solaris kernel module trojan, Linux LKM rootkit, Lion, dmi hack kit, Adore worm, ARK #$weirds='^\.\..+|;|\'|!|' . $weirds='^\.\..+|;|\'|' . '.poop|' . '^john.(ini|pot)|' . 'ramen.tgz|' . '^sklm|' . '^sitf0|' . 'xlogin$|' . '^ld.so.hash$|' . '^unhack.pl$|' . '^crtz.o$|' . '^\.t0rn$|' . #'^\.config$|' . '^ava$|' . '^ex3.c$|' . '^li0n\.(tgz|sh)|' . '^stealth.c$|' . '^rh6kit.tar.gz$|' . #'^\.lib|' . '^\/dev\/pts\/01$|' . '^\/usr\/lib\/lib$|' . '^\/usr\/bin\/adore$|' . '^\/tmp\/\.cheese|' . '^\/dev\/ptyxx\/\.(log|file|proc)|'. '^\/usr\/bin\/no$|' . '^\/var\/\.(prc|kls|adr)$|' . '^prefs\.js$|' . '^\/tmp\/\.bugtraq' ; #print $weirds; # ====================== end of SETTINGS ======================== require "find.pl"; require "ctime.pl"; # --- perl security precautions --- $ENV{'PATH'} = '/usr/bin:/usr/sbin:/bin:/sbin:/usr/etc'; $ENV{'SHELL'} = '/bin/sh'; $ENV{'IFS'} = ''; umask(077); # -rw------- chop ($day = &ctime(time)); $day =~ s/^\w+ (\w+ +\d+) .*/\1/; # get date in "Oct 5" format ## Set system specific commands $os=`uname -r`; # Get OS revision $os_name=`uname -s`; # Get OS name #print "uname -s returns $os_name\n" if $debug; ## The $get_fs_cmd is important and must return a list of local filesystems, ## excluding cdrom, floppies, loopbacks and remote NFS, since there' no point ## in analysing those, is there? if ($os =~ /^4\.1\.\d/) { # It's SunOS 4.1.x print "OS = Sun 4.1.x\n" if $debug; $mail='/usr/ucb/mail'; $get_fs_cmd ="/usr/etc/mount | egrep '/dev/sd' | cut -d' ' -f3"; $ifconfig_cmd ="/usr/etc/ifconfig -a 2>&1 | fgrep UP | fgrep -v lo0"; $ifconfig_list ="ifconfig -a|grep inet|grep -v '127\.0\.0\.1'"; } elsif (($os_name = "SunOS") && ($os =~ /^5\.\d/)) { # Solaris 2.x print "OS = Sun 5.x\n" if $debug; $mail='/usr/bin/mailx'; $get_fs_cmd ="/usr/sbin/mount |egrep -v '/vol/dev/dsk' |egrep '/dev/dsk/|/dev/vx/dsk|/dev/md/dsk' |cut -d' ' -f1"; $ifconfig_cmd ="/sbin/ifconfig -a 2>&1 | fgrep UP | fgrep -v LOOPBACK"; $ifconfig_list ="/sbin/ifconfig -a|grep inet|grep -v '127\.0\.0\.1'"; } elsif (($os_name = "Linux") && ($os =~ /^2\.\d\.\d/)) { print "OS = Linux 2.x.x\n" if $debug; $mail='/bin/mail'; #$get_fs_cmd ="/bin/mount | grep '/dev' | cut -d' ' -f3"; # <11> $get_fs_cmd ="/bin/mount |grep '/dev' |egrep -v 'type (shm|devpts|iso9660)' |cut -d' ' -f3"; #$ifconfig_cmd ="/sbin/ifconfig -a 2>&1 | fgrep UP | fgrep -v lo0"; #$ifconfig_list ="ifconfig -a|grep inet"; $ifconfig_cmd ="/sbin/ifconfig -a 2>&1 | fgrep UP | fgrep -v LOOPBACK"; $ifconfig_list ="/sbin/ifconfig -a|grep inet|grep -v '127\.0\.0\.1'"; } elsif ($os_name = "HP-UX") { print "OS = HP-UX\n" if $debug; $mail='/usr/bin/mailx'; $get_fs_cmd ="/sbin/mount | egrep '/dev/vg00/|/dev/vg2ufs|/dev/vg1ufs' | cut -d' ' -f1"; ## TBD: detect main interface #$ifconfig_cmd ="/sbin/ifconfig lan0 2>&1 | fgrep UP | fgrep -v lo0"; $ifconfig_cmd ="/sbin/ifconfig lan1 2>&1 | fgrep UP | fgrep -v lo0"; $ifconfig_list ="lanscan|grep ETHER"; } elsif (($os_name = "OpenBSD") && ($os =~ /^2\.\d/)) { print "OS = OpenBSD 2.x\n" if $debug; $mail='/usr/bin/mailx'; $get_fs_cmd ="/sbin/mount | egrep '/dev' | cut -d' ' -f3"; $ifconfig_cmd ="/sbin/ifconfig -a 2>&1 | fgrep UP | fgrep -v lo0"; $ifconfig_list ="ifconfig -a|grep inet"; } else { # Unknown OS print "Operating system $os_name $os unknown, but lets have a go anyway!"; $mail='/bin/mail'; $get_fs_cmd ="/bin/mount | egrep '/dev' | cut -d' ' -f3"; $ifconfig_cmd ="/sbin/ifconfig -a 2>&1 | fgrep UP | fgrep -v lo0"; $ifconfig_list ="ifconfig -a|grep inet"; } ######### main ################## &perror("Start: " . `date`); # Document system name, IP and starting time: #&perror(`uname -a; ifconfig -a|grep inet; date`); #&perror(`uname -a; lanscan|grep ETHER; date`); &perror(`uname -a; $ifconfig_list; date`); # --- is ethernet/TR in promiscous mode? -- &check_network_interface(); ## Single filesystem (as argument) or check all local filesystems? if ( scalar(@ARGV) > 0 ) { # we have arguments $target=$ARGV[0]; &perror("\n\nChecking only $target...\n"); &find("$target") } else { if (length($exclude)) { # <8> @filesys = `$get_fs_cmd | egrep -v "$exclude"`; # fill array with fs names chop(@filesys); print("Analysing @filesys, Excluding: $exclude \n") if $debug; &perror("Analysing @filesys, Excluding: $exclude \n"); } else { @filesys = `$get_fs_cmd`; # fill array with fs names chop(@filesys); print("Analysing @filesys, no exclusions\n") if $debug; &perror("Analysing @filesys, no exclusions\n"); } while (@filesys) { ## Get $topdev, device name, for use in &wanted ($Main::topdev,$ino_1,$mode_1,$nlink_1,$uid_1,$gid_1) = stat(@filesys[$#filesys]); print("Analysing @filesys[$#filesys]...\n") if $debug; &find("@filesys[$#filesys]"); # see &wanted() #&find(\&wanted, "@filesys[$#filesys]"); # see &wanted() pop @filesys; } } &perror("Analysis done, now generate report: " . `date`); &print_results; &perror("\nDone: " . `date`); ### Mail results & clean temporary file if ( -e $tmpfile ) { ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat("$tmpfile.Z"); if ($size > 10000) { # compress if bigger than 10k system "compress $tmpfile"; ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat("$tmpfile.Z"); if (($email_results) && ($size > 1500000)) { #don't email if > 1.5MB $msg="The audit file was too big to send via Email, " . "it is stored in $tmpfile.Z on $hostname. Please delete " . "this file when you have uploaded it."; system "echo $msg |$mail -s '$hostname: $0 results' $user"; } elsif ($email_results) { system "uuencode $tmpfile.Z $hostname.audit2.Z |$mail -s '$hostname: $0 results' $user"; } else { # Not emailing, just tell user where results are on stdout print "The script $0 has finished. Results are stored in $tmpfile.Z\n"; } } elsif ($email_results) { system "$mail -s '$hostname: $0 results' $user <$tmpfile"; } else { # Not emailing, just tell user where results are on stdout print "The script $0 has finished. Results are stored in $tmpfile\n"; } # Don't wipe, keep a trail #unlink $tmpfile; } else { print "No mail output! \n" if $debug; } exit; # ------------- end of main ----------- # ------------- functions ----------- sub print_results { # <7> #if (@veryweird) { &perror("\n\n"); &perror('>>>> ' . scalar($#veryweird+1) .' Difficult files containing <"> or <\\> or <$> in their name. These files have NOT been analysed in the categories below....'); &perror("\n"); foreach $f (@veryweird) { &perror("$f\n"); } #} #if (@weird) { &perror("\n>>>> " . scalar($#weird+1) ." strange or possible root kit files....\n"); foreach $f (@weird) { &perror("$f\n"); &perror(`ls -ald "$f" 2>&1`); #perror(`ls -ald "$f" `); } #} #if (@config) { &perror("\n>>>> " . scalar($#config+1) ." config/possibly sensitive files....\n"); #foreach $f (@config) { &perror(`ls -ald "$f" 2>&1`); } foreach $f (@config) { &perror(`ls -ald "$f"`); } #} #if (@trust) { &perror("\n>>>> " . scalar($#trust+1) ." trust files....\n"); foreach $f (@trust) { #perror(`ls -ald "$f" 2>&1`); &perror(`ls -ald "$f"`); @contents = `cat "$f"|egrep -v '^#' `; chop(@contents); &perror("contains: <@contents>\n"); unlink("$f") if $aggressive; } #} #if (@suid) { &perror("\n>>>> " . scalar($#suid+1) ." suid files....\n"); #foreach $f (@suid) { &perror(`ls -ald "$f" 2>&1`); } foreach $f (@suid) { &perror(`ls -ald "$f"`); } #} #if (@guid) { &perror("\n>>>> " . scalar($#guid+1) ." sgid (only) group files....\n"); foreach $f (@guid) { &perror(`ls -ald "$f"`); } #foreach $f (@guid) { &perror(`ls -ald "$f 2>&1"`); } #foreach $f (@guid) { &perror(`ls -ald "$f" 2>&1`); } #} #if (@worldwrite) { &perror("\n>>>> " . scalar($#worldwrite+1) ." World writeable files....\n"); foreach $f (@worldwrite) { #print "$f \n"; &perror(`ls -ald "$f" 2>&1`); } #} #if (@groupwrite) { &perror("\n>>>> " . scalar($#groupwrite+1) ." groupwriteable files....\n"); foreach $f (@groupwrite) { &perror(`ls -ald "$f" 2>&1`); } #} } sub wanted { # called by &find() # $dir= path $_= filename $name= $dir/$_ ($dev,$ino,$mode,$nlink,$uid,$gid) = stat($_); ## Check if the current file is on a device other that the ## the original filesystem, i.e. if a FS is mounted below ## another, we need to detect this and stop searching. ## By setting $prune=1, the &find function should stop ## descending the tree ion question. ## Notes: On Solaris this seems to work fine (e.g. searching root ## is fast). On HP, it seemed to ignore the prune and continue ## descending all trees in root. #$prune=1 if ($dev != topdev); if (($dev != 0) && ($dev != $Main::topdev)) { $prune=1; $Main::prune=1; # If $prune is set to 1 ==> the search tree is to be pruned print "Prune: Topdev=$Main::topdev, dev=$dev leaving $_\n" if $debug; return; } ## very weird files <##> #if (/\\|\"|\$/) { # we can't even do an ls on these files if (/\\|\"/) { # we can't even do an ls on these files push @veryweird,"$name"; } elsif (readlink($name)) { next; # ignore symbolic links } else { if (/$weirds/) { # potentially dangerous push @weird,"$name"; #unlink("$name") if $aggressive; `mv $name $dir/STRANGE.$_` if $aggressive; } elsif (/$configs/) { push @config,$name; } elsif (/$trusts/) { # trusts push @trust,$name; #unlink("$name") if $aggressive; `mv $name $dir/STRANGE.$_` if $aggressive; } # Check for SUID, check for GID if not already SUID <3> # and if it's a file (we don't care about suid directories) if (-u $_) { push @suid,$name; } elsif ((-g $_) && (-f $_)) { push @guid,$name; } #push @suid,$name if (-u $_); #push @guid,$name if (-g $_); # Check for world write, and group write if not world write <3> # see mknod(2) for description of $mode if ($mode & 002) { # avoid worldwrite devices; check for file or dir push @worldwrite,$name if ((-f $_) || (-d $_)); #push @worldwrite,$name; } elsif ($mode & 020) { # avoid groupwriteable devices; check for file or dir push @groupwrite,$name if ((-f $_) || (-d $_)); } #push @worldwrite,$name if ($mode & 002); #push @groupwrite,$name if ($mode & 020); } } sub check_network_interface { @result = `$ifconfig_cmd`; if ($result[0] =~ /PROMISC/) { # Note that most OS won't report promis. mode with ifconfig &perror("WARNING! network interface is promiscous:\n@result"); } if (@result > 1) { ## Example: some hosts are allowed several interfaces if ($hostname !~ /$multiple_interfaces_ok/) { &perror("WARNING! more than one network interface is " ."active:\n@result"); } } } sub perror { open(OUT, ">>$tmpfile") || die "Cannot append tmp file $tmpfile.\n"; print OUT @_; print @_ if $debug2; close(OUT); }