1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
# Copyright (C) 2012 Martin Holste
#
# You can copy, redistribute or modify this Program under the terms of
# the GNU General Public License version 2 as published by the Free
# Software Foundation.
#
# 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
# version 2 along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
package Processor;
use Moose;
use Data::Dumper;
use Module::Pluggable search_path => qw(Processor), sub_name => 'processors';
use Module::Pluggable search_path => qw(Action), sub_name => 'actions';
use Log::Log4perl;
use JSON;
has 'conf' => (is => 'rw', isa => 'HashRef', required => 1);
has 'log' => (is => 'rw', isa => 'Object', required => 1);
has 'json' => (is => 'ro', isa => 'JSON', required => 1, default => sub { return JSON->new->pretty->allow_blessed });
sub BUILD {
my $self = shift;
foreach my $processor_plugin ($self->processors){
next unless exists $self->conf->{processors}->{$processor_plugin};
eval qq{require $processor_plugin};
$self->log->info('Using processor plugin ' . $processor_plugin->description);
}
foreach my $action_plugin ($self->actions){
next unless exists $self->conf->{actions}->{$action_plugin};
eval qq{require $action_plugin};
$self->log->info('Using action plugin ' . $action_plugin->description);
}
}
sub process {
my $self = shift;
my $line = shift;
#$self->log->debug('got line ' . $line);
eval {
my $data = $self->json->decode($line);
$data->{processors} = {};
if($data->{md5}){
foreach my $processor_plugin ($self->processors){
next unless exists $self->conf->{processors}->{$processor_plugin};
my $processor = $processor_plugin->new(conf => $self->conf, log => $self->log, md5 => $data->{md5});
$self->log->debug('processing with plugin ' . $processor->description);
$data->{processors}->{ $processor->name } = $processor->process();
}
}
#$self->log->debug('data: ' . Dumper($data));
foreach my $action_plugin ($self->actions){
next unless exists $self->conf->{actions}->{$action_plugin};
my $action = $action_plugin->new(conf => $self->conf, log => $self->log, data => $data);
$self->log->debug('performing action with plugin ' . $action->description);
$action->perform();
}
};
if ($@){
$self->log->error('Error: ' . $@ . ', processing line: ' . $line);
}
}
package main;
use strict;
use Getopt::Std;
use FindBin;
use Config::JSON;
use File::Tail;
# Include the directory this script is in
use lib $FindBin::Bin;
my %Opts;
getopts('c:', \%Opts);
my $conf_file = $Opts{c} ? $Opts{c} : '/etc/suricata/file_processor.conf';
my $Conf = {
logdir => '/tmp',
debug_level => 'TRACE',
actions => {
'Action::Log' => 1,
'Action::Syslog' => 1,
},
processors => {
'Processor::Anubis' => 1,
'Processor::Malwr' => 1,
'Processor::ThreatExpert' => 1,
}
};
if (-f $conf_file){
$Conf = Config::JSON->new( $conf_file );
$Conf = $Conf->{config}; # native hash is 10x faster than using Config::JSON->get()
}
# Setup logger
my $logdir = $Conf->{logdir} ? $Conf->{logdir} : '/var/log/suricata';
my $debug_level = $Conf->{debug_level} ? $Conf->{debug_level} : 'TRACE';
my $l4pconf = qq(
log4perl.category.App = $debug_level, File, Screen
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.filename = $logdir/file_processor.log
log4perl.appender.File.syswrite = 1
log4perl.appender.File.recreate = 1
log4perl.appender.File.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.File.layout.ConversionPattern = * %p [%d] %F (%L) %M %P %m%n
log4perl.filter.ScreenLevel = Log::Log4perl::Filter::LevelRange
log4perl.filter.ScreenLevel.LevelMin = $debug_level
log4perl.filter.ScreenLevel.LevelMax = ERROR
log4perl.filter.ScreenLevel.AcceptOnMatch = true
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.Filter = ScreenLevel
log4perl.appender.Screen.stderr = 1
log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = * %p [%d] %F (%L) %M %P %m%n
);
Log::Log4perl::init( \$l4pconf ) or die("Unable to init logger\n");
my $Log = Log::Log4perl::get_logger('App') or die("Unable to init logger\n");
my $processor = new Processor(conf => $Conf, log => $Log);
my $file = $Conf->{file} ? $Conf->{file} : '/var/log/suricata/files-json.log';
my $tail = new File::Tail(name => $file, maxinterval => 1);
while (my $line = $tail->read){
$processor->process($line);
}
__END__
Example config file /etc/suricata/file_processor.conf
{
"logdir": "/var/log/suricata",
"debug_level": "INFO",
"virustotal_apikey": "xxx"
"actions": {
"Action::Log": 1
},
"processors": {
"Processor::Anubis": 1,
"Processor::Malwr": 1,
"Processor::ThreatExpert": 1,
"Processor::VirusTotal": 1
}
}
|