#!/usr/bin/perl -I.. ######################################################################################### # Tweetyard - Leon Ward - 2009 # leon@rm-rf.co.uk # # Additions to Jason's Barnyard-style perl unified tool to do extra "stuff" # # Rather than processing unified files and adding details to a DB, Tweetyard does the # following: # # - Tweets event details to alert users via Twitter # - Extracts the complete "attack" session from syn->fin # # It also has the following enhancements over the original # - Fixed unified filename selection # - Creates a waldo-file for record number tracking # TODO # - Test alert file-rollover @128MB with record number waldo: I've not tested but I # think this may break. # ######################################################################################### # Copyright (c) 2007 Jason Brvenik. # A Perl module to make it easy to work with snort unified files. # http://www.snort.org # # Basic barnyard like functionality ######################################################################################### # # # The intellectual property rights in this program are owned by # Jason Brvenik. This program may be copied, distributed and/or # modified only in accordance with the terms and conditions of # Version 2 of the GNU General Public License (dated June 1991). By # accessing, using, modifying and/or copying this program, you are # agreeing to be bound by the terms and conditions of Version 2 of # the GNU General Public License (dated June 1991). # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301 USA # # # ######################################################################################### use SnortUnified qw(:DEFAULT :meta_handlers); use Sys::Hostname; use Data::Dumper; use Net::Twitter; use Socket; $UF_Data = {}; $record = {}; # Configure these bits. my $verbose=0; # Enable verbose for debug my $disableTweets=0; # Disable tweeting $sids = get_snort_sids("/etc/snort/sid-msg.map.leon", "/etc/snort/gen-msg.map"); $class = get_snort_classifications("/etc/snort/classification.config"); my $twitname="MY_TWITTER_USERNAME"; # Twitter Username my $twitpass="MY_TWITTER_PASSWORD"; # Twitter Password my $file="/var/log/snort/snort.alert"; # Base filename my $waldo="/var/tmp/tweetyard.waldo"; # Waldo file for record number tracking my $myip="ip.add.re.ss"; # Optional IP to censor in tweets my $fakeip="Censored"; # IP to fake $myip with my $extract=1; # Set to 1 to enable pcap extraction my $buffer_filename = "buffer.pcap"; # Base filename if we have a tcpdump ringbuffer running my $buffer_location = "/var/log/snort/"; my $save_location = "/var/log/snort/save"; # Where to save extracted pcap sessions to # ---------- Nothing to do below here ------------- my $startup=localtime(); my $uf_file = undef; my $old_uf_file = undef; my @fields; my $twit = Net::Twitter->new({username=>"$twitname", password=>"$twitpass" }); my $recnum=0; $uf_file = get_latest_file($file) || die "no files to get"; die unless $UF_Data = openSnortUnified($uf_file); if ($verbose) { print "Working on unified file $uf_file\n"; } open WALDO, "$waldo" or $recnum=0 ; while (my $line = ){ if ( $line =~ m/(^FILE=)(.*)/ ) { $currentfile=$2; chomp $currentfile; if ($verbose) { print "Waldo file shows curent file as $currentfile\n" } } if ( $line =~ m/(^RECORD=)(.*)/ ) { $lastrec=$2; chomp $lastrec; if ($verbose) { print "Waldo file shows last record as $lastrec\n" } } } if ("$currentfile" eq "$uf_file") { if ($verbose){ print " Working on same file, skippint to $lastrec\n"; } } else { if ($verbose){ print "Working on new file, resetting last record to 0\n"; } $lastrec=0; } tweet("Snort Unified -> Twitter: Starting at $startup." ); while (1) { $old_uf_file = $uf_file; $uf_file = get_latest_file($file) || die "no files to get"; if ( $old_uf_file ne $uf_file ) { closeSnortUnified(); $UF_Data = openSnortUnified($uf_file) || die "cannot open $uf_file"; } read_records(); } sub read_records() { while ( $record = readSnortUnifiedRecord() ) { if ( $UF_Data->{'TYPE'} eq 'LOG' ) { die("Sorry - Need alert data for tweets. This looks like a unified logfile"); } else { $recnum++; if ($lastrec >= $recnum) { if ($verbose) { print "Skipping record $recnum \n"; } } else { if ($verbose){ print "----------------------------------------------------\n"; print "Record: $uf_file: $recnum\n"; } my $bpf=0; my $src_addr = inet_ntoa(pack('N', $record->{'sip'})); my $dst_addr = inet_ntoa(pack('N', $record->{'dip'})); my $src_port = $record->{'sp'}; my $dst_port = $record->{'dp'}; my $proto = $record->{'protocol'}; my $msg = get_msg($sids,$record->{'sig_gen'},$record->{'sig_id'},$record->{'sig_rev'}); my $classtype = get_class($class,$record->{'class'}); my $timestamp = $record->{'tv_sec'}; my $sid = $record->{'sid'}; $timestamp = localtime("$timestamp"); my $filename = "$src_addr-$src_port--$dst_addr-$dst_port.pcap"; if ($extract) { $bpf="host $src_addr and host $src_addr and port $src_port and port $dst_port"; if ($verbose){ print "Built a BPF $bpf \n"; } extract("$bpf", "$filename"); } # This twitter integration is more for fun than anything else so I want to allow other people to follow. # However someone may be tempted to make their own alerts, so lets censor my IP. # Raising the bar 3.2 mm above skiddie tall. if ( "$src_addr" eq "$myip" ) { $src_addr = $fakeip; } if ( "$dst_addr" eq "$myip") { $dst_addr = $fakeip; } my $tweet="Error"; if ($extract) { $tweet = "$timestamp: $msg $src_addr:$record->{sp} -> $dst_addr:$record->{dp} $classtype - Full pcap session saved"; } else { $tweet = "$timestamp: $sid $msg $src_addr:$record->{sp} -> $dst_addr:$record->{dp} $classtype"; } tweet("$tweet"); open WALDO, ">", "$waldo" or die "Unable to open $waldo for writing"; print WALDO "FILE=$uf_file\n"; print WALDO "RECORD=$recnum\n"; close(WALDO); } } } return 0; } # clean up closeSnortUnified(); sub get_latest_file($) { my $filemask = shift; my @ls = <$filemask*>; my $len = @ls; my $uf_file = ""; if ($len) { # Get the most recent file my @tmparray = sort{$b cmp $a}(@ls); $uf_file = shift(@tmparray); } else { $uf_file = undef; } return $uf_file; } sub tweet(){ my $tweet=shift; if ($disableTweets) { wlog("Tweet Disabled: Not sending $tweet\n"); return 0; } if ($verbose) { print "Tweeting $tweet\n"; } if ( $twit->update({status => "$tweet"}) ) { wlog("Event Posted: $tweet"); } else { wlog("Error sending tweet $tweet"); } } sub wlog(){ # This is designed to be run as a daemon, so lets send any output somewhere my $logmsg=shift; if ($verbose){ print "TW: $logmsg\n"; } system("logger -t TweetYard \"$logmsg\""); } sub extract(){ my $bpf=shift; my $filename=shift; my @buffers = <$buffer_location/$buffer_filename*>; if ($verbose){ print "Extracting....\n"; } for my $buffer (@buffers) { if ($verbose) { print ("Found buffer $buffer\n"); } my $suffix = $buffer; $suffix = chop($suffix); my $cmd = "/usr/sbin/tcpdump -r $buffer -w $save_location/$suffix-$filename $bpf 2>/dev/null"; #if ($verbose) { # print "About to extract with command $cmd\n"; #} system($cmd); my $filesize; if ( -f "$save_location/$suffix-$filename" ) { $filesize = -s "$save_location/$suffix-$filename"; if ( $filesize == 24) { unlink "$save_location/$suffix-$filename"; } else { if ($verbose) { print "$filesize bytes\n"; } } if ($verbose){ print "[-] Extracted $save_location/$suffx-$filename - $filesize Bytes\n"; } } } }