Skip to content
Snippets Groups Projects
Commit bbb57927 authored by Radoslav Bodó's avatar Radoslav Bodó
Browse files

pridani a presun kostejova udelatoru, merge vetvi se nam moc nepovedl

takze to musime udelat takto osklive rucne
parent 9467d78b
No related branches found
No related tags found
No related merge requests found
Showing
with 1367 additions and 0 deletions
package DNSblacklist;
use strict;
use warnings;
use Data::Dumper;
my %CONSTANTS = (
target => "127.0.0.2",
outputfile => "tmp/blacklist.csv",
threshold => 10,
excludedip => [],
eventtype => [],
maxage => "1D",
ttl => "3600",
zone => "@",
dns => "dns.example.com",
hostmaster => "hostmaster\@example.com",
refresh => "1800 ; refresh (30 minutes)",
retry => "600 ; retry (10 minutes)",
expire => "1209600 ; expire (2 weeks)",
minimum => "86400 ; minimum (1 day)",
);
my %FORMAT = ( maxage => qr/\d+[hdmHDM]/, );
sub run {
my (undef, $modprefix, $cfg, $dbh, $db_engine) = @_;
my $v = Constants::mergeConfigs($cfg, $modprefix, \%CONSTANTS, \%FORMAT);
my $eventtype_query = DB::joinIN("type", \@{$v->{'eventtype'}});
my $excluded_query = DB::joinNotIN("source", \@{$v->{'excludedip'}});
my $condition = substr($excluded_query . $eventtype_query, 0, -5);
my @columns= ("source", "note");
my @params = ($condition, DB::getOldDataDB($db_engine, "NEWER", $v->{'maxage'}));
my $query = DB::getQueryCondThreshold($db_engine, "events", \@columns, \@params, $v->{'threshold'});
my @rows = Utils::fetchall_array_hashref($dbh, $query);
my ($sec, $min, $hr, $day, $mon, $year) = localtime;
$v->{'serial'} = sprintf("%02d%02d%02d%02d%02d", $year - 100 , $mon + 1, $day, $hr, $min);
$v->{'hostmaster'} =~ s/\@/\./;
sub header { my $v = shift; return "\$ORIGIN .\n\$TTL $v->{'ttl'}\n$v->{'zone'}\t\t\t\t\t\tIN\tSOA\t$v->{'dns'}. $v->{'hostmaster'}. (\n\t\t\t\t\t\t\t\t$v->{'serial'} ; serial\n\t\t\t\t\t\t\t\t$v->{'refresh'}\n\t\t\t\t\t\t\t\t$v->{'retry'}\n\t\t\t\t\t\t\t\t$v->{'expire'}\n\t\t\t\t\t\t\t\t$v->{'minimum'}\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tNS\t$v->{'dns'}.\n"; };
sub record { my ($r, $v) = @_; $r->{'note'} = "" if !defined $r->{'note'}; return ";" . "$r->{'source'}\n" . join(".", reverse( split(/\./, $r->{'source'}))) . "\t\tIN\t\tA\t$v->{'target'}\n\t\t\t\t\tIN\t\tTXT\t\"$r->{'note'}\"\n"; };
my $ret = Utils::generateOutput($v->{'outputfile'}, \@rows, \&header, \&record, undef, $v);
return $ret;
}
1;
package IPblacklist;
use strict;
use warnings;
use Data::Dumper;
my %CONSTANTS = (
outputfile => "tmp/blacklist.csv",
threshold => 200,
excludedip => [],
eventtype => [],
maxage => "1D",
);
my %FORMAT = ( maxage => qr/\d+[hdmHDM]/, );
sub run {
my (undef, $modprefix, $cfg, $dbh, $db_engine) = @_;
my $v = Constants::mergeConfigs($cfg, $modprefix, \%CONSTANTS, \%FORMAT);
my $eventtype_query = DB::joinIN("type", \@{$v->{'eventtype'}});
my $excluded_query = DB::joinNotIN("source", \@{$v->{'excludedip'}});
my $condition = substr($excluded_query . $eventtype_query, 0, -5);
my @columns= ("source");
my @params = ($condition, DB::getOldDataDB($db_engine, "NEWER", $v->{'maxage'}));
my $query = DB::getQueryCondThreshold($db_engine, "events", \@columns, \@params, $v->{'threshold'});
my @rows = Utils::fetchall_array_hashref($dbh, $query);
sub record { my $r = shift; return "$r->{'source'},\n"; };
my $ret = Utils::generateOutput($v->{'outputfile'}, \@rows, undef, \&record, undef, $v);
return $ret;
}
1;
package IPtables;
use strict;
use warnings;
use Data::Dumper;
my %CONSTANTS = (
enabled => "no",
outputfile => "tmp/iptables.txt",
threshold => 250,
excludedip => [],
eventtype => [],
chainname => "BLOCK",
destchain => "DROP",
maxage => "1D",
);
my %FORMAT = ( maxage => qr/\d+[hdmHDM]/, logging => qr/enable|disable/,);
sub run {
my (undef, $modprefix, $cfg, $dbh, $db_engine) = @_;
my $v = Constants::mergeConfigs($cfg, $modprefix, \%CONSTANTS, \%FORMAT);
my $eventtype_query = DB::joinIN("type", \@{$v->{'eventtype'}});
my $excluded_query = DB::joinNotIN("source", \@{$v->{'excludedip'}});
my $condition = substr($excluded_query . $eventtype_query, 0, -5);
my @columns= ("source");
my @params = ($condition, DB::getOldDataDB($db_engine, "NEWER", $v->{'maxage'}));
my $query = DB::getQueryCondThreshold($db_engine, "events", \@columns, \@params, $v->{'threshold'});
my @rows = Utils::fetchall_array_hashref($dbh, $query);
sub header { my $v = shift; return "/sbin/iptables -F $v->{'chainname'}\n"; };
sub record { my ($r, $v) = @_; return "/sbin/iptables -A $v->{'chainname'} -s $r->{'source'}/32 -j $v->{'destchain'}\n"; };
my $ret = Utils::generateOutput($v->{'outputfile'}, \@rows, \&header, \&record, undef, $v);
return $ret;
}
1;
package MailReport;
use strict;
use warnings;
my %CONSTANTS = (
tool => "sendmail",
sender => "",
recipients => [],
subject => "",
subnets => ["147."],
signature => "XXX",
threshold => 0,
excludedsensor => [],
excludedip => [],
eventtype => [],
maxage => "1D",
summary => "yes",
);
my %FORMAT = ( maxage => qr/\d+[hdmHDM]/,
tool => qr/(ssmtp|sendmail)/,
);
sub run {
my (undef, $modprefix, $cfg, $dbh, $db_engine) = @_;
my $v = Constants::mergeConfigs($cfg, $modprefix, \%CONSTANTS, \%FORMAT);
my $eventtype_query = DB::joinIN("type", \@{$v->{'eventtype'}});
my $excluded_query = DB::joinNotIN("source", \@{$v->{'excludedip'}});
my $excludedsensor_query = DB::joinNotIN("service", \@{$v->{'excludedsensor'}});
my $subnets_query = DB::joinLIKE("source", \@{$v->{'subnets'}});
my $condition = substr($excluded_query . $eventtype_query . $excludedsensor_query . $subnets_query, 0, -5);
my @columns= ("source", "hostname", "service", "type", "detected", "target_proto", "target_port", "attack_scale");
my @params = ($condition, DB::getOldDataDB($db_engine, "NEWER", $v->{'maxage'}));
my $query = DB::getQueryCondThreshold($db_engine, "events", \@columns, \@params, $v->{'threshold'});
my @rows = Utils::fetchall_array_hashref($dbh, $query);
if($v->{'subject'} eq "") {
my $hostname = `hostname -f`;
$v->{'subject'} = "$modprefix (Warden-app) on $hostname";
}
$v->{'modprefix'} = $modprefix;
sub header {
my $v = shift;
my $header;
$header = "$v->{'modprefix'} noticed following events during $v->{'maxage'} timeframe:\n\n";
$header .= sprintf("+-------------------------------+---------------------+------------+-----------------+-------+----------+--------+\n");
$header .= sprintf("| Detector/Service | Detected | Type | Source | Dport | Proto | Volume |\n");
$header .= sprintf("+-------------------------------+---------------------+------------+-----------------+-------+----------+--------+\n");
return $header
};
sub record { my $r = shift; return sprintf("|%30s | %19s | %10s | %15s | %5s | %8s | %6s |\n", "$r->{'hostname'}/$r->{'service'}", $r->{'detected'}, $r->{'type'}, $r->{'source'}, $r->{'target_port'}, $r->{'target_proto'}, $r->{'attack_scale'}); };
sub footer {
my $v = shift;
my $footer = sprintf("+-------------------------------+---------------------+------------+-----------------+-------+----------+--------+\n\n");
$footer .= $v->{'signature'};
return $footer;
};
Utils::generateEmails($v->{'tool'}, \@{$v->{'recipients'}}, $v->{'sender'}, $v->{'subject'}, \@rows, \&header, \&record, \&footer, $v, $v->{'summary'});
return 1;
}
1;
package Constants;
use strict;
use warnings;
use Data::Dumper;
use constant SCALAR => 'SCALAR';
our %DEFAULTS =
(
factory => {
GENERAL_logfile => "var/log/factory.log",
GENERAL_modpath => "Modules",
},
receiver => {
GENERAL_method => ( "stdout" ),
GENERAL_logfile => "var/log/receiver.log",
GENERAL_wardenpath => "/opt/warden/client",
GENERAL_requested_type => "_all_",
FILE_directory => "var/fileout/",
FILE_method => "append",
FILE_appendfilename => "received",
FILE_extension => "csv",
DB_dbengine => "sqlite",
},
db => {
SQLITE_db => "var/db.sqlite",
SQLITE_user => "",
SQLITE_pass => "",
MYSQL_db => "warden",
MYSQL_user => "root",
MYSQL_pass => "",
MYSQL_host => "localhost",
MYSQL_port => "3306",
},
cleaner => {
GENERAL_method => ( "db" ),
GENERAL_maxage => "11D",
},
);
sub getDefaultValue {
my ($valuename, $section) = @_;
$valuename =~ s/\./_/g;
my @value = $DEFAULTS{$section}{$valuename};
die "Value '$valuename' is not defined" if not @value;
return (wantarray ? @value : $value[0]);
}
sub assignValue {
my ($valuename, $cfg, $section, $nocheck) = @_;
my @configvalue = $cfg->param($valuename);
$valuename =~ s/\./_/g;
my @value;
if(!defined $nocheck) {
my @defaultvalue = getDefaultValue($valuename, $section);
@value = (@configvalue ? @configvalue : @defaultvalue);
}
else {
@value = @configvalue;
}
die "Value '$valuename' is not defined" if not defined $value[0];
if(wantarray and $value[0] eq "") {
return ();
}
else {
return (wantarray ? @value : $value[0]);
}
}
sub mergeConfigs {
my ($config, $section, $constants, $format) = @_;
my %ret;
my $conf_hash = $config->get_block($section);
foreach my $const_key ( keys %$constants )
{
if( exists $conf_hash->{$const_key} ) {
if(ref($constants->{$const_key}) eq ref($conf_hash->{$const_key})) {
$ret{$const_key} = $conf_hash->{$const_key};
}
elsif (ref($constants->{$const_key}) eq 'ARRAY') {
$ret{$const_key} = (defined $conf_hash->{$const_key} ? [$conf_hash->{$const_key}] : []);
}
elsif (ref(\$constants->{$const_key}) eq 'SCALAR') {
$ret{$const_key} = (defined $conf_hash->{$const_key} ? $conf_hash->{$const_key}->[0] : "");
}
if(exists $format->{$const_key}) {
if($ret{$const_key} !~ $format->{$const_key}) {
$ret{$const_key} = $constants->{$const_key};
}
}
}
else {
$ret{$const_key} = $constants->{$const_key};
}
}
return \%ret;
}
package DB;
use strict;
use warnings;
use WardenApp::Constants;
use constant DB_ENGINE_MYSQL => 'mysql';
use constant DB_ENGINE_SQLITE => 'sqlite';
use constant DB_SECTION => 'db';
use DBI;
sub connectDB {
my ($cfg, $db_engine) = @_;
my $dbh;
if(lc $db_engine eq DB_ENGINE_MYSQL) {
my $db = Constants::assignValue('MYSQL.db', $cfg, DB_SECTION);
my $host = Constants::assignValue('MYSQL.host', $cfg, DB_SECTION);
my $user = Constants::assignValue('MYSQL.user', $cfg, DB_SECTION);
my $pass = Constants::assignValue('MYSQL.pass', $cfg, DB_SECTION);
my $port = Constants::assignValue('MYSQL.port', $cfg, DB_SECTION);
$dbh = DBI->connect("DBI:mysql:host=" . $host . ";port=" . $port . ";database=" . $db,
$user,
$pass,
{RaiseError => 0,AutoCommit => 0}) || die "Database connection not made: $DBI::errstr";
}
elsif (lc $db_engine eq DB_ENGINE_SQLITE) {
my $db = Constants::assignValue('SQLITE.db', $cfg, DB_SECTION);
my $user = Constants::assignValue('SQLITE.user', $cfg, DB_SECTION);
my $pass = Constants::assignValue('SQLITE.pass', $cfg, DB_SECTION);
$dbh = DBI->connect("DBI:SQLite:" . $db,
$user,
$pass,
{RaiseError => 0,AutoCommit => 1}) || die "Database connection not made: $DBI::errstr";
}
return \$dbh;
}
sub getOldDataDB {
my ($db_engine, $expr, $maxage) = @_;
my ($num, $word) = $maxage =~ /(\d+)([dmhDMH])/;
my ($word_long, $word_desc);
$word_long = "HOUR" if $word =~ /[hH]/;
$word_long = "DAY" if $word =~ /[dD]/;
$word_long = "MONTH" if $word =~ /[mM]/;
my $c;
$c = "<" if($expr eq "OLDER");
$c = ">" if($expr eq "NEWER");
if($db_engine eq DB_ENGINE_MYSQL) {
return sprintf("detected %s DATE_SUB(NOW(), INTERVAL %d %s)", $c, $num, $word_long);
}
if($db_engine eq DB_ENGINE_SQLITE) {
return sprintf("datetime(detected) %s datetime('now','-%d %s')", $c, $num, $word_long);
}
return "";
}
sub closeDB {
my $dbh = shift;
$$dbh->disconnect;
}
sub getQueryCondThreshold {
my ($db_engine, $table, $columns, $params, $threshold) = @_;
my $columns_q = join ", ", @$columns;
my $params_q = join " AND ", grep { $_ } @$params;
return sprintf("SELECT %s FROM %s WHERE %s GROUP BY source HAVING COUNT(id) > %s", $columns_q, $table, $params_q, $threshold);
}
sub joinIN {
my ($column, $data) = @_;
return (@$data ? sprintf("%s IN (%s) AND ", $column, join ",", map { "'$_'" } @$data) : "");
}
sub joinNotIN {
my ($column, $data) = @_;
return (@$data ? sprintf("%s NOT IN (%s) AND ", $column, join ",", map { "'$_'" } @$data) : "");
}
sub joinLIKE {
my ($column, $data) = @_;
my $ret = (@$data ? sprintf("%s", join ",", map { "$column LIKE '$_%' OR " } @$data) : "");
return ($ret ne "" ? substr($ret, 0, -4) . " AND " : "");
}
1;
package Factory;
use strict;
use warnings;
use Config::Simple;
use WardenApp::Constants;
use WardenApp::DB;
use WardenApp::Utils;
use Data::Dumper;
use constant TRUE => 1;
use constant FALSE => 0;
use constant ENABLED => 'yes';
use constant DB_ENGINE_MYSQL => 'mysql';
use constant DB_ENGINE_SQLITE => 'sqlite';
use constant FACTORY_SECTION => 'factory';
use constant CFG_MODULE_DIR => 'GENERAL.modpath';
sub isModEnabled {
my ($modprefix, $cfg) = @_;
my $enabled = Constants::assignValue($modprefix . ".enabled", $cfg, FACTORY_SECTION);
if ($enabled eq ENABLED) {
return TRUE;
}
else {
return FALSE;
}
}
sub runModule {
my ($modulename, $cfg, $dbh, $db_engine) = @_;
unless(isModEnabled($modulename, $cfg)) {
print "Module '$modulename' disabled! See configuration file!\n";
return 0;
}
my $moddir = Constants::assignValue(CFG_MODULE_DIR, $cfg, FACTORY_SECTION);
my $module = Constants::assignValue($modulename . ".module", $cfg, FACTORY_SECTION, "nocheck");
require "$moddir/$module.pm";
print "Module '$modulename' started\n";
my $ret = $module->run($modulename, $cfg, $dbh, $db_engine);
if($ret) {
print "Module '$modulename' finished\n";
return 1;
}
else {
print "Module '$modulename' finished with errors\n";
return 0;
}
}
1;
package Receiver;
use strict;
use warnings;
use WardenApp::Constants;
use constant RECEIVER_SECTION => 'receiver';
use constant SQL_INSERT_EVENT => "INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";
sub openfile {
my ($cfg) = @_;
my ($filename, $openparam);
my $method = Constants::assignValue('FILE.method', $cfg, RECEIVER_SECTION);
if($method ne 'newfile' and $method ne 'append') {
$method = Constants::getDefaultValue('FILE.method', RECEIVER_SECTION);
}
if($method eq 'newfile') {
my ($sec, $min, $hr, $day, $mon, $year) = localtime;
$openparam = ">";
$filename = sprintf("%02d-%02d-%04d_%02d-%02d", $day, $mon + 1, 1900 + $year, $hr, $min);
}
elsif ($method eq 'append') {
$openparam = ">>";
$filename = Constants::assignValue('FILE.appendfilename', $cfg, RECEIVER_SECTION);
}
my $directory = Constants::assignValue('FILE.directory', $cfg, RECEIVER_SECTION);
my $extension = Constants::assignValue('FILE.extension', $cfg, RECEIVER_SECTION);
my $openstring = $openparam . $directory . "/" . $filename . "." . $extension;
open FILE, $openstring or die $!;
return \*FILE;
}
sub saveToDB {
my ($dbh, $event, $db_engine) = @_;
my $sth = $$dbh->prepare(SQL_INSERT_EVENT);
#my $data = join(',', @$event);
$sth->execute(@$event) || die $sth->errstr;
print "Receiver-$db_engine:\tError \"$@\" while processing data\n" if $@;
}
sub saveToFile {
my ($file, $event) = @_;
my $data = join(';', @$event);
print $file $data . "\n";
}
sub printToStdout {
my $event = shift;
print "| " . join(' | ', @$event ) . " |" . "\n";
}
sub closeFile {
my $file = shift;
close $file;
}
1;
package Utils;
use strict;
use warnings;
use Data::Dumper;
sub generateOutput {
my ($outputfile, $rows, $header, $record, $footer, $values) = @_;
return 0 if not defined $record;
if(open FILE, ">$outputfile") {
print FILE &$header($values) if defined $header;
foreach my $r (@$rows) {
my $record_alt = &$record($r, $values);
print FILE $record_alt;
}
print FILE &$footer($values) . "\n" if defined $footer;
close FILE;
return 1;
}
else {
return 0;
}
}
sub generateEmails {
my ($tool, $to, $from, $subject, $rows, $header, $record, $footer, $values, $summary) = @_;
my ($msg, $body) = ("", "");
if($summary eq "yes") {
foreach my $r (@$rows) {
$body .= &$record($r, $values) if defined $record;
}
if($body ne "") {
$msg .= &$header($values) if defined $header;
$msg .= $body;
$msg .= &$footer($values) if defined $footer;
foreach my $recipient (@$to) {
sendEmail($tool, $recipient, $from, $subject, $msg) if defined $record;
}
}
}
else {
foreach my $r (@$rows) {
$msg = "";
$msg .= &$header($values) if defined &$header;
$msg .= &$record($r, $values);
$msg .= &$footer($values) . "\n" if defined $footer;
if(defined $record) {
foreach my $recipient (@$to) {
sendEmail($tool, $recipient, $from, $subject, $msg) if defined $record;
}
}
}
}
}
sub sendEmail {
my($tool, $to, $from, $subject, $body) = @_;
if(($from !~ /^(\w|\-|\_|\.)+\@((\w|\-|\_)+\.)+[a-zA-Z]{2,}$/) || ($from =~ /\.@|\.\./)) {
print "Senders address ('$from') is not valid!\n";
return 0;
}
if(($to !~ /^(\w|\-|\_|\.)+\@((\w|\-|\_)+\.)+[a-zA-Z]{2,}$/) || ($to =~ /\.@|\.\./)) {
print "Recipients address ('$to') is not valid!\n";
return 0;
}
if($subject eq "") {
print "Subject cannot be empty!\n";
return 0;
}
if(open(MAIL, "|/usr/sbin/$tool -t")) {
print MAIL "To: $to\n";
print MAIL "From: $from\n";
print MAIL "Subject: $subject\n\n";
print MAIL "$body";
close(MAIL);
return 1;
}
else {
return (0, "Sending email failed: $!");
}
}
sub fetchall_array_hashref {
my ($dbh, $query) = @_;
my $sth = $$dbh->prepare($query);
$sth->execute();
my (@rows, $x);
push(@rows, $x) while ($x = $sth->fetchrow_hashref());
return @rows;
}
1;
#!/usr/bin/perl
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../";
use WardenApp::DB;
use WardenApp::Constants;
use Config::Simple;
use constant GENERAL_CONFIG_FILE => "$Bin/../etc/cleaner.conf";
use constant DB_CONFIG_FILE => "$Bin/../etc/db.conf";
use constant RECV_CONFIG_FILE => "$Bin/../etc/receiver.conf";
use constant RECEIVER_SECTION => 'receiver';
use constant CLEANER_SECTION => 'cleaner';
use constant DB_ENGINE_MYSQL => 'mysql';
use constant DB_ENGINE_SQLITE => 'sqlite';
use constant TRUE => 1;
use constant FALSE => 0;
my $cfg = new Config::Simple;
$cfg->read(GENERAL_CONFIG_FILE) or die $cfg->error();
my $cfg_rcv = new Config::Simple;
$cfg_rcv->read(RECV_CONFIG_FILE) or die $cfg_rcv->error();
my $cfg_db = new Config::Simple;
$cfg_db->read(DB_CONFIG_FILE) or die $cfg_db->error();
my @general_method = Constants::assignValue('GENERAL.method', $cfg, CLEANER_SECTION);
my $general_method_db = (grep (/db/, @general_method) ? TRUE : FALSE);
my $general_maxage = Constants::assignValue('GENERAL.maxage', $cfg, CLEANER_SECTION);
$general_maxage = Constants::getDefaultValue('GENERAL.maxage', CLEANER_SECTION) if $general_maxage !~ /\d+[hdmHDM]/;
if($general_method_db) {
my $db_engine = Constants::assignValue('DB.dbengine', $cfg_rcv, RECEIVER_SECTION);
my $dbh = DB::connectDB($cfg_db, $db_engine);
if($dbh) {
my $query = sprintf("DELETE FROM events WHERE %s", DB::getOldDataDB($db_engine, "OLDER", $general_maxage));
my $sth = $$dbh->prepare($query);
$sth->execute;
my $num_deleted = $sth->rows;
print "Removed '$num_deleted' events older than $general_maxage.\n";
DB::closeDB($dbh);
exit 1;
}
else {
exit 0;
}
}
else {
print "General DB method is not configured\n";
exit 0;
}
#!/usr/bin/perl
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../";
use WardenApp::DB;
use WardenApp::Factory;
use Config::Simple;
use WardenApp::Constants;
use constant RECEIVER_SECTION => 'receiver';
use constant DB_CONFIG_FILE => "$Bin/../etc/db.conf";
use constant RECV_CONFIG_FILE => "$Bin/../etc/receiver.conf";
use constant GENERAL_CONFIG_FILE => "$Bin/../etc/factory.conf";
my $cfg = new Config::Simple;
$cfg->read(GENERAL_CONFIG_FILE) or die $cfg->error();
my $cfg_rcv = new Config::Simple;
$cfg_rcv->read(RECV_CONFIG_FILE) or die $cfg_rcv->error();
my $cfg_db = new Config::Simple;
$cfg_db->read(DB_CONFIG_FILE) or die $cfg_db->error();
my $db_engine = Constants::assignValue("DB.dbengine", $cfg_rcv, RECEIVER_SECTION);
my $dbh = DB::connectDB($cfg_db, $db_engine);
if(not defined $dbh) {
exit 0;
}
if(defined $ARGV[0] and $ARGV[0] ne "") {
if(Factory::runModule($ARGV[0], $cfg, $dbh, $db_engine)) {
exit 1;
}
else {
exit 0;
}
}
else {
print "Use module's name as parameter", "\n";
exit 0;
}
DB::closeDB($dbh);
#!/usr/bin/perl -w
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../";
use Config::Simple;
use WardenApp::DB;
use WardenApp::Receiver;
use WardenApp::Constants;
use constant GENERAL_CONFIG_FILE => "$Bin/../etc/receiver.conf";
use constant DB_CONFIG_FILE => "$Bin/../etc/db.conf";
use constant RECEIVER_SECTION => 'receiver';
use constant DEFAULT_REQ_ALL => "_all_";
use constant TRUE => 1;
use constant FALSE => 0;
use Data::Dumper;
my $cfg = new Config::Simple;
$cfg->read(GENERAL_CONFIG_FILE) or exit 0;
my $cfg_db = new Config::Simple;
$cfg_db->read(DB_CONFIG_FILE) or exit 0;
my @general_method = Constants::assignValue('GENERAL.method', $cfg, RECEIVER_SECTION);
my $general_method_stdout = (grep (/stdout/, @general_method) ? TRUE : FALSE);
my $general_method_file = (grep (/file/, @general_method) ? TRUE : FALSE);
my $general_method_db = (grep (/db/, @general_method) ? TRUE : FALSE);
my $warden_path = Constants::assignValue('GENERAL.wardenpath', $cfg, RECEIVER_SECTION);
my $requested_type = Constants::assignValue('GENERAL.requested_type', $cfg, RECEIVER_SECTION);
$requested_type = DEFAULT_REQ_ALL if $requested_type eq "";
my $logfile = Constants::assignValue('GENERAL.logfile', $cfg, RECEIVER_SECTION);
my $db_engine = Constants::assignValue('DB.dbengine', $cfg, RECEIVER_SECTION);
my $dbh = DB::connectDB($cfg_db, $db_engine) if $general_method_db;
my $file_ref = Receiver::openfile($cfg) if $general_method_file;
require $warden_path . '/lib/WardenClientReceive.pm';
# Download of new evetns from Warden server
while (my @new_events = WardenClientReceive::getNewEvents($warden_path, $requested_type)) {
foreach my $event_ref (@new_events) {
if($general_method_stdout) {
Receiver::printToStdout($event_ref);
}
if($general_method_file) {
Receiver::saveToFile($file_ref, $event_ref);
}
if($general_method_db) {
Receiver::saveToDB($dbh, $event_ref, $db_engine);
}
}
}
DB::closeDB($dbh) if $general_method_db;
Receiver::closeFile($file_ref) if $general_method_file;
+-------------------------------+
| README - WardenApp (WApp) 0.1 |
+-------------------------------+
Content
A. Overall Information
B. Installation Dependencies
C. Installation
D. Uninstallation
E. Configuration
F. Modules
G. Run
X. Tutorial: Running of the WApp along with the database backend
XX. Tutorial: Writing your own module
--------------------------------------------------------------------------------
A. Overall Information
1. About WardenApp
Warden is a client-based architecture service designed to share detected
security events (issues) among CSIRT and CERT teams in a simple and fast
way.
WardenApp included in this package is an extension of classical Warden Client.
It allows automated evaluation received data and generates base of data for
another tools or just allows generating reports for human checking.
2. Version
0.1 (2013-03-20)
3. Package structure
warden-app/
|-- bin
| |-- warden-cleaner.pl
| |-- warden-factory.pl
| `-- warden-receiver.pl
|-- doc
| |-- WApp.cron
| `-- WApp.README
|-- etc
| |-- cleaner.conf
| |-- db.conf
| |-- factory.conf
| `-- receiver.conf
|-- Modules
| |-- DNSblacklist.pm
| |-- IPblacklist.pm
| |-- IPtables.pm
| `-- MailReport.pm
|-- sh
| |-- create_tables_mysql.sh
| `-- create_tables_sqlite.sh
`-- WardenApp
|-- Constants.pm
|-- DB.pm
|-- Factory.pm
|-- Receiver.pm
`-- Utils.pm
--------------------------------------------------------------------------------
A1. Essence of WardenApp
The core consists of three parts with this specific functions:
Receiver - Receives data and stores it in the selected location (stdout, file, db [sqlite, MySQL])
Factory - Generates output that is requested. In short, it processes the data from the database
and generates an output dependent on the module. More about modules in section X.
Cleaner - Erases old unnecessary events.
Each part is represented by a Perl script that runs at defined intervals by the cron daemon. See
section H.
--------------------------------------------------------------------------------
B. Installation Dependencies
Perl >= 5.10.1
Config::Simple >= 4.59-5
DBI
Supported drivers for DBI are DBD::mysql and DBD::SQLite.
--------------------------------------------------------------------------------
C. Installation
1. Check SHA1 checksum of corresponding Warden client package archive
$ sha1sum -c warden-app-0.1.tar.gz.sig
2. Untar it
$ tar xzvf warden-app-0.1.tar.gz
3. Just copy
Copy to any directory. For simplicity, use the default location of the Warden
project (/opt).
4. Installation Privileges
The Warden client is designed to be run under standard privileges. It should
be a part of other applications that are run under usual user privileges.
5. Configuration files
The files are located in directory 'conf'. For defails, see section E.
6. Initialize database backend
WApp has interface for use MySQL or sqlite as database engine. Preparing
for the basic database structure can use the scripts from 'bin' directory.
MySQL - create_tables_mysql.sh
sqlite - create_tables_sqlite.sh
--------------------------------------------------------------------------------
D. Uninstallation
Simply delete all files from WardenApp's installation directory.
Optionally delete database if you used this choice.
--------------------------------------------------------------------------------
E. Configuration
All configuration files are placed in 'conf' directory. File 'factory.conf'
is used for configuration each of modules, other 'db.conf', 'receiver.conf'
and 'cleaner.conf' are used for general purposes.
Each of configuration parameters is described directly in configuration file.
--------------------------------------------------------------------------------
F. Modules
Modules are placed in 'Modules' directory. Module is assigned to specific configuration
section in 'conf/facory.conf' file.
This package includes these modules with these specific functions:
DNSblacklist - generates zone file for the most widely used DNS software on the Internet.
IPblacklist - generates traditional CSV file with IP addresses.
IPtables - generates iptables rules.
MailReport - generates reports which are sent to specific recipients.
Section XX. describes how to write own module.
--------------------------------------------------------------------------------
G. Run
1. Receiver
Usage: warden-receiver.pl
2. Factory
Usage: warden-factory.pl MODULE_CONF_NAME
MODULE_CONF_NAME
Represents specific configuration section placed in 'factory.conf'. Each of modules
can have more configuration alternatives which are distinguished by name.
For simple call of individual modules is possible use prepared cron script
placed in 'etc/cron.d/' directory.
*WARNING: When generating a report using cron, the interval in configuration file and
interval in cron script must be IDENTICAL, otherwise a result may be INACCURATE.
3. Cleaner
Usage: warden-cleaner.pl
--------------------------------------------------------------------------------
X. Tutorial: Running of the WApp along with the database backend
1. Database engine configuration (conf/db.conf)
[SQLITE]
db="var/warden.db"
user=
pass=
2. Receiver configuration (conf/receiver.conf)
[GENERAL]
method="db"
wardenpath="/opt/warden-client"
requested_type="_all_"
[DB]
dbengine="sqlite"
3. Factory configuration, IPtables module (conf/factory.conf)
[MOD_IPTABLES_1]
enabled="yes"
module="IPtables"
outputfile="tmp/iptables.txt"
threshold="10"
excludedip="1.1.1.1","2.2.2.2"
eventtype=
chainname="BLOCK"
destchain="DROP"
maxage="4M"
4. Cleaner configuration (conf/cleaner.conf)
[GENERAL]
method="db"
maxage="5D"
[DB]
dbengine="sqlite"
5. Run
I. Manually
# ./warden-receiver.pl
# ./warden-factory.pl MOD_IPTABLES_1
# ./warden-cleaner.pl
II. Regularly using cron (example in etc/cron.d/wardenapp)
SCRIPT_PATH=/opt/warden-app/
*/5 * * * * root cd $SCRIPT_PATH; ./warden-receiver.pl
21 * * * * root cd $SCRIPT_PATH; ./warden-factory.pl MOD_IPTABLES_1
1 1 * * * root cd $SCRIPT_PATH; ./warden-cleaner.pl
--------------------------------------------------------------------------------
X. Tutorial: Writing your own module
The base for modules is interface with hashes '%CONSTANTS', '%FORMAT' and sub 'run()'.
Both of them has to be implemented. Modules are placed in 'Modules' directory.
sub run() - Main subroutine, which is always called from the parent's environment.
%CONSTANTS - Variable with all supported options and their default values.
%FORMAT - Regexp for variables which must have a specific format.
IPtables module more deeply
===========================
1. Defining configuration parameters
All configuration options with their default values are stored in %CONSTANTS hash.
my %CONSTANTS = (
enabled => "no",
outputfile => "tmp/iptables.txt",
threshold => 250,
excludedip => [],
eventtype => [],
chainname => "BLOCK",
destchain => "DROP",
maxage => "1D",
);
In this case is used parameter maxage which must be in format '\d+[hdmHDM]'. Then it is
possible use '%FORMAT' hash to enforce specific format, otherwise will be used default
value from '%CONSTANTS'.
my %FORMAT = ( maxage => qr/\d+[hdmHDM]/, );
2. Implementation of the main function
I. The usual start function, reading the parameters from the parent function and retrieving
values from the config file.
sub run {
my (undef, $modprefix, $cfg, $dbh, $db_engine) = @_;
my $v = Constants::mergeConfigs($cfg, $modprefix, \%CONSTANTS, \%FORMAT);
II. Creating of string with database query. It is possible use built-in routines like joinIN,
joinNotIN, getOldDataDB or getQueryCondThreshold.
my $eventtype_query = DB::joinIN("type", \@{$v->{'eventtype'}});
my $excluded_query = DB::joinNotIN("source", \@{$v->{'excludedip'}});
my $condition = substr($excluded_query . $eventtype_query, 0, -5);
my @columns= ("source");
my @params = ($condition, DB::getOldDataDB($db_engine, "NEWER", $v->{'maxage'}));
my $query = DB::getQueryCondThreshold($db_engine, "events", \@columns, \@params, $v->{'threshold'});
III. Executing of created query and storing result to array.
my @rows = Utils::fetchall_array_hashref($dbh, $query);
IV. Implementing of subroutines 'header', 'record' and 'footer'. References to these functions
can be used like parameter in routine, which generates the final output. Mentioned routines
can get configuration values as a parameter and in addition, 'record' gets value of actual
record in loop over result of query.
sub header { my $v = shift; return "/sbin/iptables -F $v->{'chainname'}\n"; };
sub record { my ($r, $v) = @_; return "/sbin/iptables -A $v->{'chainname'} -s $r->{'source'}/32 -j $v->{'destchain'}\n"; };
my $ret = Utils::generateOutput($v->{'outputfile'}, \@rows, \&header, \&record, undef, $v);
V. Exit code is returned to parent.
return $ret;
}
--------------------------------------------------------------------------------
Copyright (C) 2013 Cesnet z.s.p.o
#+--------------------------- minute [0-59;*/10 means every 10 minutes (0,10,20,30,40,50)]
#| +------------------- hour [0-23]
#| | +--------------- day of month [1-31]
#| | | +------------- month [1-12]
#| | | | +----------- day of week [0-7; 0 or 7 is Sunday]
#| | | | | +-------- user
#| | | | | | +-- command
#| | | | | | |
SCRIPT_PATH=/opt/warden-app/
*/5 * * * * root cd $SCRIPT_PATH; ./warden-receiver.pl
21 * * * * root cd $SCRIPT_PATH; ./warden-factory.pl MOD_IPTABLES_1
21 * * * * root cd $SCRIPT_PATH; ./warden-factory.pl MOD_BLACKLISTIP_1
1 1 * * * root cd $SCRIPT_PATH; ./warden-cleaner.pl
#CLEANER configuration file
[GENERAL]
#What will be cleaned; [db]
method="db"
#How old events will be deleted [D - Days, M - Months, H - Hours];
maxage="5M"
#DB configuration file
[SQLITE]
# Path to sqlite database file
db="/root/warden/src/warden-app/var/warden.db"
# Username
user=
# Password
pass=
[MYSQL]
# Name of MySQL database
db="warden2"
# Username
user="root"
# Password
pass=
# Hostname or IP of MySQL server
host="localhost"
# Port
port="3306"
#FACTORY configuration file
[GENERAL]
# Directory with modules
moddir="Modules"
[MOD_BLACKLISTIP_1]
# Enabling module [yes, no]
enabled="yes"
# Type of module; see 'moddir' directory
module="IPblacklist"
# Where will be result stored
outputfile="/root/warden/src/warden-app/tmp/blacklist.csv"
# Threshold for SQL query (events grouped by source IP) [number]
threshold="2"
# Which source IP we want to exclude from result [ip1, ip2, ipN]
excludedip="147.228.1.70","147.228.1.61"
# Which type of events we want to process [ see Warden manual ]
eventtype="portscan","bruteforce","spam"
# Time interval for generating data [D - Days, M - Months, H - Hours];
maxage="10M"
[MOD_IPTABLES_1]
# Enabling module [yes, no]
enabled="yes"
# Type of module; see 'moddir' directory
module="IPtables"
# Where will be result stored
outputfile="/root/warden/src/warden-app/tmp/iptables2.txt"
# Threshold for SQL query (events grouped by source IP) [number]
threshold="10"
# Which source IP we want to exclude from result [ip1, ip2, ipN]
excludedip="1.1.1.1"
# Which type of events we want to process [ see Warden manual ]
eventtype=
# Chain in which will be iptables rules inserted
chainname="BLOCK"
# Default chain after -j (jump) statement
destchain="DROP"
# Time interval for generating data [D - Days, M - Months, H - Hours];
maxage="4M"
[MOD_DNSBL_1]
# Enabling module [yes, no]
enabled="yes"
# Type of module; see 'moddir' directory
module="DNSblacklist"
# Where will be result stored
outputfile="/root/warden/src/warden-app/tmp/dnsbl2.txt"
# Default target for blacklisted A record
target="127.0.0.2"
# Threshold for SQL query (events grouped by source IP) [number]
threshold="0"
# Which source IP we want to exclude from result [ip1, ip2, ipN]
excludedip=
# Which type of events we want to process [ see Warden manual ]
eventtype=
# Chain in which will be iptables rules inserted
maxage="10M"
# Default TTL for DNS zone file
ttl="3600"
# DNS name of zone
zone="@"
# SOA authoritative DNS server for zone
dns="dns3.example.com"
# SOA hostmaster's e-mail
hostmaster="hostmaster@example.com"
# SOA refresh value [s]
refresh="1800 ; refresh (30 minutes)"
# SOA retry value [s]
retry="600 ; retry (10 minutes)"
# SOA expire value [s]
expire="1209600 ; expire (2 weeks)"
# SOA minimum value [s]
minimum="86400 ; minimum (1 day)"
[MOD_MREPORT_1]
# Enabling module [yes, no]
enabled="yes"
# Type of module; see 'moddir' directory
module="MailReport"
# Tool for send e-mails [sendmail,ssmtp]
tool="sendmail"
# Senders address
sender="wardenapp@domena.cz"
# Recipient addresses
recipients="kostenec@zcu.cz","kostenec@civ.zcu.cz"
# Subject of e-mail; leave blank for subject like [MOD_NAME (Warden-app) on HOSTNAME
subject=""
# E-mail signature
signature="XXX Incidents Response Team (XIRT)"
# List of excluded sensors
excludedsensor=
# Watched network subnets [syntax LIKE subnet%]
subnets="147.228."
# Threshold for SQL query (events grouped by source IP) [number]
threshold="0"
# Which source IP we want to exclude from result [ip1, ip2, ipN]
excludedip="147.228.209.171","147.228.1.61"
# Which type of events we want to process [ see Warden manual ]
eventtype=
# Chain in which will be iptables rules inserted
maxage="3M"
# All records will be sent in one summarized e-mail or one e-mail per record [yes,no]
summary="yes"
#RECEIVER configuration file
[GENERAL]
# Where will be received events stored [stdout,file,db]
method="stdout","file","db"
# Path to Warden client
wardenpath="/opt/warden-client"
# Type of event which will be requested. To get all types of event, leave this option blank.
requested_type=
[FILE]
# Where will be received results stored
directory="/root/warden/src/warden-app/var/fileout/"
# How we will handle files
# append - one file named by 'appendfilename' param,
# newfile - new file for every client's call in dd-mm-yyyy_hh_mm ended by 'extension' param
method="append"
# Name of file when method 'append' will be chosen
appendfilename="warden-received"
# Extension of file when method 'newfile' or 'append' will be chosen
extension="csv"
[DB]
# Database engine in which will be received events stored
dbengine="sqlite"
#!/bin/bash
USER=$1
DB=$2
if [ $# -ne 2 ]; then
echo "Usage: $0 username dbname"
exit -1
fi
# create table events
echo "CREATE TABLE \`events\` (
\`id\` int(11) NOT NULL AUTO_INCREMENT,
\`hostname\` varchar(256) DEFAULT NULL,
\`service\` varchar(64) DEFAULT NULL,
\`detected\` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
\`type\` varchar(64) DEFAULT NULL,
\`source_type\` varchar(64) DEFAULT NULL,
\`source\` varchar(256) DEFAULT NULL,
\`target_proto\` varchar(16) DEFAULT NULL,
\`target_port\` int(2) DEFAULT NULL,
\`attack_scale\` int(4) DEFAULT NULL,
\`note\` text,
\`priority\` int(1) DEFAULT NULL,
\`timeout\` int(2) DEFAULT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;" | mysql -u$USER -p $DB
exit 0
#!/bin/bash
db_file=$1
if [ $# -ne 1 ]; then
echo "Usage: $0 dbfile"
exit -1
fi
# create table events
sqlite3 $db_file "CREATE TABLE events (id INTEGER, hostname VARCHAR(256), service VARCHAR(64), detected TIMESTAMP, type VARCHAR(64), source_type VARCHAR(64), source VARCHAR(256), target_proto VARCHAR(16), target_port INT(2), attack_scale INT(4), note TEXT, priority INT(1), timeout INT(2));"
exit 0
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment