#!/usr/bin/perl # # ViperDB v0.9.1 # # ViperDB was created as a smaller & faster option to Tripwire. # Tripwire while being a great product leaves something to be # desired in the speed department and also, by default tripwire # generates a report everytime it runs and directs that report # to an email address. This hinders most people from running # Tripwire every few minutes to do a system check. ViperDB # however is the answer to this problem. ViperDB does not use # a fancy all-in-one database to keep records instead, I opted # to keep it fast and hence decided to go with a plaintext db # which is stored in each "watched" directory. By using this # there is no real one attack point for a attacker to focus his # attention on. This coupled with the running of ViperDB every # 5 minutes (via cron root job) decreases that likelyhood that # an attacker will be able to modify your "watched" filesystem # .while ViperDB is monitoring your system # # NOTES: # # # PLANNED UPGRADES: # - Adding of a more complex "system status" function which when # a change is detected, would grab info that might be helpful # in determining what caused the change (ie. processes running, # users logged in, last few lines from logfiles, etc) # # THANKS TO: # whitetrash, punkis, & rooster, and those of you who # have sent in your suggestions. # # Special thanks to Anthony D. Urso for # finding the nasty race conditions which prompted me to recode # all file access properly instead of being lame and using system() # for every file access # # VERSION HISTORY # 0.1 - 0.5 - Wrote CreateDB.pl which generates the DBs # - Wrote CheckDB.pl which used diff to find changes # - Re-Coded to use a "distributed database" instead of # one centralized DB. # - Re-Coded to use a config file (ViperDB.ini) # - Changed to use Assoc. Arrays to speed up processing # - Added capability to detect additions & deletions of # files to "watched" directories # 0.6 - Merged CreateDB.pl & CheckDB.pl into one # - Cleaned out debugging code and commented more # 0.7 - Changed logging mechanism from logging to an # individual file to logging to the standard # logging facility (calls on 'logger') # - Added '-checkstrict' functionality which changes # permissions back to what they were before the # change was made to the file. # - Added exception(s) to '-checkstrict' which removes # all permissions from the changed file if the file # originally was SUID/GUID # - Changed way changes were seen by admin, now a change # only sends an alert to the logs once instead of repeatedly. # 0.8 - Added Email notification option which will send email to # specified email address # - Updated to make "database(s)" immutable and undeleteable # so it is hard for the database(s) to be changed in between # runs even if someone busts root. # 0.9 - Added ignore file functionality which allows user to # specify files to ignore # - Updated code to work better on solaris, ie updated ls # options to -lAcr for solaris instead of standard -laAs # 0.9.1 - Fixed some nasty race conditions # - changed almost all system() calls to perl equivolents # - cleaned up -checkstrict code to handle changing perms. # owner,group back to original.. now use UID/GID instead # of the name of the uid/gid # - Simplfied reporting (lumped all perms together and # uid/gid together) # - cleaned up a bug with chattr and -init runtype # # These are the only things you should need to set $configfile='/usr/local/etc/viperdb.conf'; $ignorefile='/usr/local/etc/viperdb.ignore'; $notifymail='Y'; $notify_email='j-dog@resentment.org'; $notify_subject="ViperDB Changes Detected!\n"; chomp($os_type=lc(qx[uname])); # Detect what command line switches were passed and act accordingly if (@ARGV[0] eq '-init'){ print "Init Detected. Creating Databases...\n"; &InitDB; } elsif (@ARGV[0] eq '-check'||@ARGV[0] eq '-checkstrict'){ print "Check Detected: Now Checking File Sanity...\n"; &SysCheck; } else { print "\n\nViperDB v0.9\n"; print "ERROR: Unrecognized option or none given.\n"; print "usage: ViperDB -init -check\n"; print " -init Initializes the ViperDB Databases\n"; print " -check Runs a system file sanity check\n"; print " -checkstrict Runs a system file sanity check (protective)\n"; } sub InitDB { $runtype='init'; &CreateDB; } sub SysCheck { $runtype='check'; &CreateDB; if (@ARGV[0] eq '-checkstrict'){ $strictmode="Y"; } &Compare; &Cleanup; # At this point I re-init the databases to stop # changes from constantly being displayed. We have # displayed changes, if any, and we are now going # to re-create the database with the new perms. $runtype='init'; print "Creating New Databases...\n"; &CreateDB; } sub CreateDB { # Parse in the Ignore file open (IGNORE, "< $ignorefile"); %ignorefiles; $ignorecount=0; while(){ chomp; $ignorefiles{$ignorecount} = $_; $ignorecount+=1; }#while close(IGNORE); #Parse in the Config file open (CONFIG, "< $configfile"); STARTCONFIG: $configline=; chomp $configline; while (defined($configline)){ if ($configline =~ /:/){ goto STARTCONFIG; } else { $wd=$configline; # Set some vars up for runtype if ($runtype eq 'init'){ $ViperDB=$wd . '.ViperDB'; # Solaris be goofy and we can't do this if ($os_type ne 'sunos'){ system("chattr -iu $ViperDB 2>/dev/null 1>/dev/null"); }#if } else { $ViperDB=$wd . '.ViperDB.tmp'; }#if...else # Read in the current dir and build the DB open (VIPERDB, "> $ViperDB"); opendir(BINLIST, $wd) || die "can't opendir $wd: $!"; @files = readdir(BINLIST); $filecount=@files; $count=0; RESTARTLOOP: while($count ne $filecount){ foreach $ignorecount (sort keys %ignorefiles){ if ($files[$count] eq $ignorefiles{$ignorecount}){ $count+=1; goto RESTARTLOOP; } } #foreach $bname=$files[$count]; $file="$wd/$bname"; ($dev, $ino, $mode, $nlink, $uid, $gid,$rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks)= stat($file); print VIPERDB "$bname,$size,$mode,$uid,$gid,$mtime,$ctime\n"; $count+=1; } #while closedir(BINLIST); close(VIPERDB); # Change the permissions to only allow root to read... chmod(0400, "$ViperDB"); if ($runtype eq 'init') { if ($os_type ne 'sunos') { system("chattr +iu $ViperDB 2>/dev/null 1>/dev/null"); } } $configline=; chomp $configline; } # else...if } close (CONFIG); } sub Compare { open (DIRLIST, "< $configfile"); open (LOG, "|logger -t ViperDB"); my $trouble=0; READDIRLIST: $dirlistline=; chomp $dirlistline; while(defined($dirlistline)){ if ($dirlistline =~ /:/){ goto READDIRLIST; } else { $mypath=$dirlistline; } $RealDB=$mypath . '.ViperDB'; $ChkDB=$mypath . '.ViperDB.tmp'; # Init some Assoc. Arrays %valid = (); %check = (); # Read the RealDB into an Assoc. Array open(A, $RealDB); while(){ ($bname,$junk) = split /,/,$_; chomp $bname; if(defined($bname)){ if(!defined($valid{$bname})){ $valid{$bname} = $_; } # if } # if } # while close (A); # Read the CheckDB into an Assoc. Array open(B, $ChkDB); while(){ ($bname,$junk) = split /,/,$_; chomp $bname; if(defined($bname)){ if(! defined($check{$bname})){ $check{$bname} = $_; } # if } # if } # while close (B); foreach $bname (sort keys %valid){ $fileinfoa=$valid{$bname}; $fileinfob=$check{$bname}; chomp $fileinfoa; chomp $fileinfob; if($fileinfoa ne $fileinfob){ ($a_bname,$a_size,$a_mode,$a_uid,$a_gid,$a_mtime,$a_ctime) = split/,/,$fileinfoa; ($b_bname,$b_size,$b_mode,$b_uid,$b_gid,$b_mtime,$b_ctime) = split/,/,$fileinfob; $myfile=$wd.$a_bname; if(! defined($b_bname)){ print LOG "Alert - FILE DELETED: $wd$a_bname\n"; $errorsummary .= "Alert - FILE DELETED: $wd$a_bname\n"; $trouble++; } else { print LOG "Alert - CHANGES TO FILE: $wd$a_bname\n"; $errorsummary .= "Alert - CHANGES TO FILE: $wd$a_bname\n"; $trouble++; if($a_size ne $b_size){ print LOG "Alert - SIZE: was $a_size now $b_size\n"; $errorsummary .= "Alert - SIZE: was $a_size now $b_size\n"; } if($a_uid ne $b_uid||$a_gid ne $b_gid){ print LOG "Alert - UID/GID: was $a_uid:$a_gid now $b_uid:$b_gid\n"; $errorsummary .= "Alert - UID/GID: was $a_uid:$a_gid now $b_uid:$b_gid\n"; if($strictmode eq 'Y'){ print LOG "Alert - UID/GID: Changing uid/gid of $a_bname back to $a_uid:$a_gid\n"; $errorsummary .= "Alert - UID/GID: Changing uid/gid of $a_bname back to $a_uid:$a_gid"; chown $a_uid, $a_gid, $myfile; } } if($a_perms ne $b_perms){ print LOG "Alert - PERMS: was $a_perms now $b_perms\n"; $errorsummary .= "Alert - PERMS: was $a_perms now $b_perms"; if($strictmode eq 'Y'){ print LOG "Alert - PERMS: Changing perms on $a_bname back to $a_perms\n"; $errorsummary .= "Alert - PERMS: Changing perms on $a_bname back to $a_perms\n"; chmod $a_perms, $myfile; } # if } # if if($a_mtime ne $b_mtime){ ($a_seconds, $a_minutes, $a_hours, $a_dom, $a_month, $a_year, $a_wday, $a_yday, $a_isdst) = localtime($a_mtime); ($b_seconds, $b_minutes, $b_hours, $b_dom, $b_month, $b_year, $b_wday, $b_yday, $b_isdst) = localtime($b_mtime); print LOG "Alert - MTIME: was $a_month/$a_dom/$a_year $a_hours:$a_minutes:$a_seconds now $b_month/$b_dom/$b_year $b_hours:$b_minutes:$b_seconds\n"; $errorsummary .= "Alert -MTIME: was $a_month/$a_dom/$a_year $a_hours:$a_minutes:$a_seconds now $b_month/$b_dom/$b_year $b_hours:$b_minutes:$b_seconds\n"; } # if if($a_ctime ne $b_ctime){ ($a_seconds, $a_minutes, $a_hours, $a_dom, $a_month, $a_year, $a_wday, $a_yday, $a_isdst) = localtime($a_ctime); ($b_seconds, $b_minutes, $b_hours, $b_dom, $b_month, $b_year, $b_wday, $b_yday, $b_isdst) = localtime($b_ctime); print LOG "Alert - CTIME: was $a_month/$a_dom/$a_year $a_hours:$a_minutes:$a_seconds now $b_month/$b_dom/$b_year $b_hours:$b_minutes:$b_seconds\n"; $errorsummary .= "Alert - CTIME: was $a_month/$a_dom/$a_year $a_hours:$a_minutes:$a_seconds now $b_month/$b_dom/$b_year $b_hours:$b_minutes:$b_seconds\n"; } # if } # if ... else } # if } # foreach foreach $bname ( sort keys %check ) { $fileinfoa=$valid{$bname}; $fileinfob=$check{$bname}; ($a_bname,$junk) = split/,/,$fileinfoa; ($b_bname,$junk) = split/,/,$fileinfob; if (! defined($a_bname) ) { print LOG "Alert - NEW FILE: $wd$b_bname\n"; $errorsummary .= "Alert - NEW FILE: $wd$b_bname\n"; $trouble=$trouble+1; } # if } # foreach $dirlistline=; chomp $dirlistline; } # While if ($trouble!=0) { print LOG "Info - END RUN - $trouble changes detected."; $errorsummary .= "Info - END RUN - $trouble changes detected."; } # if close (LOG); if ($notifymail eq 'Y' && $trouble != 0) { open (MAIL, "|mail -s '$notify_subject' $notify_email"); print MAIL "$errorsummary"; close(MAIL); } #if } # sub sub Cleanup { open (CONF, "< $configfile"); STARTCONF: $confline=; chomp $confline; while ( defined($confline) ) { if ( $confline =~ /:/) { goto STARTCONF; } else { $rmdir=$confline; } # if ... else $tmpDB=$rmdir . '.ViperDB.tmp'; #unlink("$tmpDB"); $confline=; chomp $confline; } # While close (CONF); } # sub