#!/usr/bin/perl # # ViperDB v0.8 # # 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, wrlwnd, punkis, & rooster, and those of you who # have sent in your suggestions. # # 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. # These are the only things you should need to set $configfile='/usr/local/etc/viperdb.conf'; $notifymail='Y'; $notify_email='j-dog@resentment.org'; # You shouldn't have to touch anything below here # 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.6\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'; $recreating='Y'; print "Creating New Databases...\n"; &CreateDB; } sub CreateDB { open (CONFIG, "< $configfile"); STARTCONFIG: $configline=; chomp $configline; while ( defined($configline) ) { if ( $configline =~ /:/) { goto STARTCONFIG; } else { $wd=$configline; #Set some Var's based on wether we are initing or checking if ($runtype eq 'init') { $ViperDB=$wd . '.ViperDB'; $tmpfile='/tmp/.ViperDB'; if ($recreating eq 'Y') { system("chattr -iu $ViperDB"); } } else { $ViperDB=$wd . '.ViperDB.tmp'; $tmpfile='/tmp/.ViperDB.tmp'; } # Get a dump of all the current files in the dir and system("ls -laAS $wd|tr -s ' '|grep -v total|grep -v ViperDB>>$tmpfile"); open (VIPERDB, "> $ViperDB"); open (BINLIST, "< $tmpfile"); $line=; chomp $line; while ( defined($line) ) { ($perms,$junk,$uid,$gid,$size,$month,$day,$yearortime,$bname) = split/ /,$line; # I couldn't figure out a way to just do 3 chops and then reverse the string stored in the variables so... # I am doing it this way... SHADDDUP... itz not lame.. itz.. just ... just so kewl you don't know it... $aa = (chop $perms); $ab = (chop $perms); $ac = (chop $perms); $ba = (chop $perms); $bb = (chop $perms); $bc = (chop $perms); $ca = (chop $perms); $cb = (chop $perms); $cc = (chop $perms); $aperms = $ac . $ab . $aa; $gperms = $bc . $bb . $ba; $operms = $cc . $cb . $ca; $filetype = (chop $perms); # Misc Debuggin Shit # print "Binary Name: $bname\n"; # print " File Type: $filetype\n"; # print " File Size: $size\n"; # print " File Owner: $uid\n"; # print " File Group: $gid\n"; # print "Owner Perms: $operms\n"; # print "Group Perms: $gperms\n"; # print "Other Perms: $aperms\n"; print VIPERDB "$wd$bname,$size,$filetype,$uid,$operms,$gid,$gperms,$aperms,$month,$day,$yearortime\n"; $line=; chomp $line; } # While close (BINLIST); #rm the tmp db system("rm -rf $tmpfile"); close(VIPERDB); # Change the permissions to only allow root to read... system("chmod 400 $ViperDB"); if ($runtype eq 'init') { system("chattr +iu $ViperDB"); } $configline=; chomp $configline; } # else...if } close (CONFIG); } sub Compare { open (DIRLIST, "< $configfile"); open (LOG, "|logger -t ViperDB"); # print LOG "Info - START RUN $startrun\n"; 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}) ) { # print "ERROR:RealDB: Duplicate entry found for $bname.\n"; } else { $valid{$bname} = $_; } # if ... else } # 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}) ) { # print "ERROR:CheckDB: Duplicate entry found for $bname.\n"; } else { $check{$bname} = $_; } # if ... else } # if } # while close (B); foreach $bname ( sort keys %valid ) { $fileinfoa=$valid{$bname}; $fileinfob=$check{$bname}; if($fileinfoa ne $fileinfob) { ($binnamea,$sizea,$filetypea,$uida,$opermsa,$gida,$gpermsa,$apermsa,$montha,$daya,$yearortimea) = split/,/,$fileinfoa; ($binnameb,$sizeb,$filetypeb,$uidb,$opermsb,$gidb,$gpermsb,$apermsb,$monthb,$dayb,$yearortimeb) = split/,/,$fileinfob; chomp $yearortimea; chomp $yearortimeb; if( ! defined($binnameb) ){ print LOG "Alert - FILE DELETED: $binnamea\n"; $errorsummary .= "Alert - FILE DELETED: $binnamea\n"; $trouble++; } else { print LOG "Alert - CHANGES TO FILE: $bname\n"; $errorsummary .= "Alert - CHANGES TO FILE: $bname\n"; $trouble++; if($sizeb ne $sizea) { print LOG "Alert - SIZE: was $sizea now $sizeb\n"; $errorsummary .= "Alert - SIZE: was $sizea now $sizeb\n"; } if($filetypeb ne $filetypea) { print LOG "Alert - TYPE: was $filetypea now $filetypeb\n"; $errorsummary .= "Alert - TYPE: was $filetypea now $filetypeb\n"; } if($uidb ne $uida) { print LOG "Alert - OWNER: was $uida now $uidb\n"; $errorsummary .= "Alert - OWNER: was $uida now $uidb\n"; if($strictmode eq 'Y'){ print LOG "Alert - OWNER: Changing owner of $bname back to $uida\n"; $errorsummary .= "Alert - OWNER: Changing owner of $bname back to $uida"; system("chown $uida $bname"); } } if($opermsb ne $opermsa) { print LOG "Alert - OWNER PERMS: was $opermsa now $opermsb\n"; $errorsummary .= "Alert - OWNER PERMS: was $opermsa now $opermsb"; if($strictmode eq 'Y'){ if ($opermsa =~ /s/){ print LOG "Alert - OWNER PERMS: Change to a SUID file detected - Removing all permissions\n"; $errorsummary .= "Alert - OWNER PERMS: Change to a SUID file detected - Removing all permissions"; system("chmod 000 $bname"); } else { print LOG "Alert - OWNER PERMS: Changing owner perms on $bnamea back to $opermsa\n"; $errorsummary .= "Alert - OWNER PERMS: Changing owner perms on $bnamea back to $opermsa\n"; $setperms=""; if ($opermsa =~ /r/){ $setperms = $setperms . "r"; } # if if ($opermsa =~ /w/){ $setperms = $setperms . "w"; } # if if ($opermsa =~ /x/){ $setperms = $setperms . "x"; } # if system("chmod u-rwx $bname"); system("chmod u+$setperms $bname"); } # if ... else } # if } # if if($gidb ne $gida) { print LOG "Alert - GROUP: was $gida now $gidb\n"; $errorsummary .= "Alert - GROUP: was $gida now $gidb\n"; if($strictmode eq 'Y'){ print LOG "Alert - GROUP: Changing group of $bname back to $gida\n"; $errorsummary .= "Alert - GROUP: Changing group of $bname back to $gida\n"; system("chgrp $gida $bname"); } # if } # if if($gpermsb ne $gpermsa) { print LOG "Alert - GROUP PERMS: was $gpermsa now $gpermsb\n"; $errorsummary .= "Alert - GROUP PERMS: was $gpermsa now $gpermsb\n"; if($strictmode eq 'Y'){ if ($opermsa =~ /s/){ print LOG "Alert - GROUP PERMS: Change to a SGID file detected - Removing all permissions\n"; $errorsummary .= "Alert - GROUP PERMS: Change to a SGID file detected - Removing all permissions\n"; system("chmod 000 $bname"); } else { print LOG "Alert - GROUP PERMS: Changing group perms on $bnamea back to $gpermsa\n"; $errorsummary .= "Alert - GROUP PERMS: Changing group perms on $bnamea back to $gpermsa\n"; $setperms=""; if ($gpermsa =~ /r/){ $setperms = $setperms . "r"; } # if if ($gpermsa =~ /w/){ $setperms = $setperms . "w"; } # if if ($gpermsa =~ /x/){ $setperms = $setperms . "x"; } # if system("chmod g-rwx $bname"); system("chmod g+$setperms $bname"); } # if ... else } # if } # if if($apermsb ne $apermsa) { print LOG "Alert - ALL PERMS: was $apermsa now $apermsb\n"; $errorsummary .= "Alert - ALL PERMS: was $apermsa now $apermsb\n"; if($strictmode eq 'Y'){ print LOG "Alert - ALL PERMS: Changing ALL perms on $bnamea back to $apermsa\n"; $errorsummary .= "Alert - ALL PERMS: Changing ALL perms on $bnamea back to $apermsa\n"; $setperms=""; if ($apermsa =~ /r/){ $setperms = $setperms . "r"; } if ($apermsa =~ /w/){ $setperms = $setperms . "w"; } if ($apermsa =~ /x/){ $setperms = $setperms . "x"; } system("chmod o-rwx $bname"); system("chmod o+$setperms $bname"); } # if } # if if($monthb ne $montha || $dayb ne $daya || $yearortimeb ne $yearortimea) { print LOG "Alert - TIMESTAMP: was $montha $daya $yearortimea now $monthb $dayb $yearortimeb\n"; $errorsummary .= "Alert - TIMESTAMP: was $montha $daya $yearortimea now $monthb $dayb $yearortimeb\n"; } # if } # if ... else } # if } # foreach foreach $bname ( sort keys %check ) { $fileinfoa=$valid{$bname}; $fileinfob=$check{$bname}; ($binnamea,$junk) = split/,/,$fileinfoa; ($binnameb,$junk) = split/,/,$fileinfob; if (! defined($binnamea) ) { print LOG "Alert - NEW FILE: $binnameb\n"; $errorsummary .= "Alert - NEW FILE: $binnameb\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 $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'; system("rm -f $tmpDB"); $confline=; chomp $confline; } # While close (CONF); } # sub