Newer
Older
Tomáš Plesník
committed
# Warden.pm
Tomáš Plesník
committed
# Use of this source is governed by a BSD-style license, see LICENSE file.
use DBI;
use DBD::mysql;
use Sys::Syslog qw(:DEFAULT setlogsock);
Sys::Syslog::setlogsock('unix');
use Net::CIDR::Lite;
use DateTime;
use MIME::Base64;
use Crypt::X509;
Tomáš Plesník
committed
use SOAP::Lite;
Tomáš Plesník
committed
use Carp;
use File::Basename;
use lib File::Basename::dirname(__FILE__);
Tomáš Plesník
committed
use WardenCommon;
################################################################################
# VARIABLES
################################################################################
Tomáš Plesník
committed
our $FILENAME = File::Basename::basename(__FILE__);
my $lib = File::Basename::dirname(__FILE__);
Tomáš Plesník
committed
my $etc = "$lib/../etc";
Tomáš Plesník
committed
################################################################################
Tomáš Plesník
committed
# READING OF CONFIGURATION VARIABLES
################################################################################
Tomáš Plesník
committed
# load server configuration
my $conf_file = "$etc/warden-server.conf";
WardenCommon::loadConf($conf_file);
Tomáš Plesník
committed
################################################################################
################################################################################
Tomáš Plesník
committed
# create database handler
our $DBH = DBI->connect("DBI:mysql:database=$WardenCommon::DB_NAME;host=$WardenCommon::DB_HOST", $WardenCommon::DB_USER, $WardenCommon::DB_PASS, {RaiseError => 1, mysql_auto_reconnect => 1})
|| die "Could not connect to database '$WardenCommon::DB_NAME' at '$WardenCommon::DB_HOST': $DBI::errstr";
Tomáš Plesník
committed
################################################################################
################################################################################
#-------------------------------------------------------------------------------
# sendMsg - wrapper for more complex WardenCommon::sendMsg function
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
sub sendMsg
my $severity = shift;
my $syslog_msg = shift;
my $soap_msg = shift;
Tomáš Plesník
committed
# send message via syslog
WardenCommon::sendMsg($WardenCommon::SYSLOG, $WardenCommon::SYSLOG_VERBOSE, $WardenCommon::SYSLOG_FACILITY, $severity,
$syslog_msg, $soap_msg, $FILENAME);
}
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
# getAltNames - parse Alternate names from SSL certifiate
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
sub getAltNames
Tomáš Plesník
committed
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
Tomáš Plesník
committed
push(@an_array, $DBH->quote($cn));
my @a = split("\n", $ENV{'SSL_CLIENT_CERT'});
pop @a;
shift @a;
my $der = decode_base64(join("", @a));
my $decoded= Crypt::X509->new(cert => $der);
Tomáš Plesník
committed
# obtain Subject Alternative Names from SSL certificate (if any exist)
if (defined $decoded->SubjectAltName) {
foreach my $tmp (@{$decoded->SubjectAltName}) {
if($tmp =~ s/dNSName=//){
push(@an_array, $DBH->quote($tmp));
}
Tomáš Plesník
committed
}
}
Tomáš Plesník
committed
Tomáš Plesník
committed
my $alt_names = join(',', @an_array);
return $alt_names;
#-------------------------------------------------------------------------------
# authorizeClient - authorize client by CN,AN and source IP range
#-------------------------------------------------------------------------------
sub authorizeClient
{
Tomáš Plesník
committed
my ($alt_names, $ip, $service_type, $client_type, $function_name) = @_;
Tomáš Plesník
committed
# check if client is valid and obtain client_id, ip_net_client and receive_own_events
Tomáš Plesník
committed
if($function_name eq 'saveNewEvent') {
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND service = ? AND client_type = ? AND valid = 't' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
Tomáš Plesník
committed
} elsif($function_name eq 'getNewEvents') {
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND (type = ? OR type = '_any_') AND client_type = ? AND valid = 't' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
} elsif($function_name eq 'getClientInfo') {
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND valid = 't' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
} elsif($function_name eq 'getLastId') {
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND client_type = 'r' AND valid = 't' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
# check if db handler is defined
Tomáš Plesník
committed
unless (defined $sth) {
Tomáš Plesník
committed
sendMsg("err",
"Cannot prepare authorization statement in function 'authorizeClient': $DBH->errstr",
Tomáš Plesník
committed
}
Tomáš Plesník
committed
# execute query for two or none params functions
if ($function_name eq 'saveNewEvent' || $function_name eq 'getNewEvents') {
$rc = $sth->execute($service_type, $client_type);
Tomáš Plesník
committed
unless ($rc) {
sendMsg("err",
"Cannot execute authorization statement in function 'authorizeClient': $DBH->errstr",
"Internal 'execute' server error");
}
$rc = $sth->execute;
Tomáš Plesník
committed
unless ($rc) {
sendMsg("err",
"Cannot execute authorization statement in function 'authorizeClient': $DBH->errstr",
"Internal 'execute' server error");
}
Tomáš Plesník
committed
# obtain registration information about clients
my ($client_id, $ip_net_client, $receive_own, $ip_net_client_list);
Tomáš Plesník
committed
my $correct_ip_source = 0;
my %ret;
Tomáš Plesník
committed
while(($client_id, $ip_net_client, $receive_own) = $sth->fetchrow()) {
my $ip_net_client_list = Net::CIDR::Lite->new->add($ip_net_client);
Tomáš Plesník
committed
$ret{'receive_own'} = $receive_own;
if ($ip_net_client_list->bin_find($ip)) {
Tomáš Plesník
committed
$correct_ip_source = 1;
last;
Tomáš Plesník
committed
}
Tomáš Plesník
committed
# check if client is registered
if ($sth->rows == 0) {
Tomáš Plesník
committed
sendMsg("err",
"Unauthorized access to function '$function_name' from [IP: '$ip'; CN(AN): $alt_names; Client_type: '$client_type'; Service/Type: '$service_type'] - client is not registered at Warden server '$ENV{'SERVER_NAME'}'",
"Access denied - client is not registered at Warden server '$ENV{'SERVER_NAME'}'");
Tomáš Plesník
committed
return undef;
}
# check if client has IP from registered CIDR
Tomáš Plesník
committed
unless ($correct_ip_source) {
Tomáš Plesník
committed
sendMsg ("err",
"Unauthorized access to function '$function_name' from [IP: '$ip'; CN(AN): $alt_names; Client_type: '$client_type'; Service/Type: '$service_type'] - access to Warden server '$ENV{'SERVER_NAME'}' from another subnet than '$ip_net_client'",
"Access denied - access to Warden server '$ENV{'SERVER_NAME'}' from unauthorized subnet '$ip_net_client'");
Tomáš Plesník
committed
return undef;
}
return %ret;
} # END of authorizeClient
################################################################################
# SOAP Functions
################################################################################
#-----------------------------------------------------------------------------
# saveNewEvent - save new received event into database
#-----------------------------------------------------------------------------
sub saveNewEvent
{
my ($class, $data) = @_;
Tomáš Plesník
committed
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $function_name = 'saveNewEvent';
my $client_type = 's'; # incoming client MUST be sender
my $valid = 't'; # registered sender has valid events
my $received = DateTime->now; # time of event delivery (UTC)
my $service = $data->{'SERVICE'} || ""; # tested
my $detected = $data->{'DETECTED'} || ""; # tested
my $type = $data->{'TYPE'} || ""; # tested
my $source_type = $data->{'SOURCE_TYPE'} || ""; # tested
my $source = $data->{'SOURCE'} || ""; # untested
my $target_proto = $data->{'TARGET_PROTO'} || ""; # untested
my $target_port = $data->{'TARGET_PORT'} || ""; # tested
my $attack_scale = $data->{'ATTACK_SCALE'} || ""; # tested
my $note = $data->{'NOTE'} || ""; # untested
my $priority = $data->{'PRIORITY'} || ""; # tested
my $timeout = $data->{'TIMEOUT'} || ""; # tested
Tomáš Plesník
committed
# authorize incoming client
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
Tomáš Plesník
committed
"Incoming event: [client_id: '$client{'client_id'}', service: '$service', detected: '$detected', type: '$type', source_type: '$source_type', source: '$source', target_proto: '$target_proto', target_port: '$target_port', attack_scale: '$attack_scale', note: '$note', priority: '$priority', timeout: '$timeout']",
Tomáš Plesník
committed
# MySQL optimalization - replace empty string to undef
$service = undef if $service eq "";
$detected = undef if $detected eq "";
$type = undef if $type eq "";
$source_type = undef if $source_type eq "";
$source = undef if $source eq "";
$target_proto = undef if $target_proto eq "";
$target_port = undef if $target_port eq "";
$attack_scale = undef if $attack_scale eq "";
$note = undef if $note eq "";
$priority = undef if $priority eq "";
$timeout = undef if $timeout eq "";
# test event item: 'detected'
# http://my.safaribooksonline.com/book/programming/regular-expressions/9780596802837/4dot-validation-and-formatting/id2983571
if ($detected !~ /^((?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?/) {
sendMsg("err",
"Unknown item 'detected' from client '$client{'client_id'}': '$detected'",
"Unknown detected time format: '$detected'");
}
# test event item: 'event_type', 'source_type' (based on VALIDATION HASH)
if (%WardenCommon::VALID_STRINGS) {
if (!(exists $WardenCommon::VALID_STRINGS{'type'} && grep $type eq $_, @{$WardenCommon::VALID_STRINGS{'type'}})) {
sendMsg("err",
"Unknown item 'event_type' from client '$client{'client_id'}': '$type'",
"Unknown event type: '$type'");
} elsif (!(exists $WardenCommon::VALID_STRINGS{'source_type'} && grep $source_type eq $_, @{$WardenCommon::VALID_STRINGS{'source_type'}})) {
sendMsg("err",
"Unknown item 'source_type' from client '$client{'client_id'}': '$source_type'",
"Unknown source type: '$source_type'");
}
}
Tomáš Plesník
committed
# test event items: 'target_port', 'attack_scale', 'priority', 'timeout'
if (defined $target_port && $target_port !~ /^\d+\z/) {
push(@change_list, "target_port: '$target_port'");
Tomáš Plesník
committed
$target_port = undef;
}
Tomáš Plesník
committed
if (defined $attack_scale && $attack_scale !~ /^\d+\z/) {
push(@change_list, "attack_scale: '$attack_scale'");
Tomáš Plesník
committed
$attack_scale = undef;
}
Tomáš Plesník
committed
if (defined $priority && $priority !~ /^\d+\z/) {
push(@change_list, "priority: '$priority'");
Tomáš Plesník
committed
$priority = undef;
}
Tomáš Plesník
committed
if (defined $timeout && $timeout !~ /^\d+\z/) {
push(@change_list, "timeout: '$timeout'");
Tomáš Plesník
committed
$timeout = undef;
}
Tomáš Plesník
committed
my $change_string = join(", ", @change_list);
Tomáš Plesník
committed
unless ($change_string eq "") {
Tomáš Plesník
committed
sendMsg("info",
"Unknown other event items from client '$client{'client_id'}': ($change_string)",
Tomáš Plesník
committed
undef);
}
Tomáš Plesník
committed
# save new event into database
$sth = $DBH->prepare("INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
Tomáš Plesník
committed
unless (defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$rc = $sth->execute(undef, $detected, $received, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $valid, $client{'client_id'});
Tomáš Plesník
committed
unless ($rc) {
sendMsg("err",
"Cannot execute statement in function '$function_name': $DBH->errstr",
"Internal 'execute' server error");
}
return 1;
Tomáš Plesník
committed
}
} # END of saveNewEvent
#-----------------------------------------------------------------------------
# getNewEvents - get new events from the DB greater than received ID
#-----------------------------------------------------------------------------
sub getNewEvents
{
my ($class, $data) = @_;
Tomáš Plesník
committed
my ($sth, $rc, @events, $event, @ids, $used_limit);
Tomáš Plesník
committed
# client network information
Tomáš Plesník
committed
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
Tomáš Plesník
committed
my $client_type = 'r'; # incoming client MUST be receiver
my $function_name = 'getNewEvents';
my $requested_type = $data->{'REQUESTED_TYPE'} || '_any_';
my $last_id = $data->{'LAST_ID'};
Tomáš Plesník
committed
my $max_rcv_events_limit = $data->{'MAX_RCV_EVENTS_LIMIT'}; # client events limit
# comparison of client and server limit - which can be used
if (defined $max_rcv_events_limit && $max_rcv_events_limit < $WardenCommon::MAX_EVENTS_LIMIT) {
Tomáš Plesník
committed
$used_limit = $max_rcv_events_limit;
} else {
$used_limit = $WardenCommon::MAX_EVENTS_LIMIT;
Tomáš Plesník
committed
}
Tomáš Plesník
committed
# authorize incoming client
my %client = authorizeClient($alt_names, $ip, $requested_type, $client_type, $function_name);
Tomáš Plesník
committed
# obtain events from database
Tomáš Plesník
committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
my $query = "SELECT id, hostname, service, detected, events.type, source_type, source, target_proto, target_port, attack_scale, note, priority, timeout FROM events INNER JOIN clients ON events.client_id = clients.client_id WHERE events.type != 'test' AND id > ? AND events.valid = 't'";
my @params = ($last_id);
unless ($requested_type eq '_any_') {
$query .= " AND events.type = ?";
push(@params, $requested_type);
}
unless ($client{'receive_own'} eq 't') {
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$query .= " AND hostname NOT LIKE ?";
push(@params, '%' . $domain);
}
$query .= " ORDER BY id ASC LIMIT ?;";
push(@params, $used_limit);
$sth = $DBH->prepare($query);
unless (defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
$rc = $sth->execute(@params);
unless (defined $rc) {
sendMsg("err",
"Cannot execute statement in function '$function_name': $DBH->errstr",
"Internal 'execute' server error");
Tomáš Plesník
committed
# obtain event entries from query
Tomáš Plesník
committed
# create SOAP object
$event = SOAP::Data->name(event => \SOAP::Data->value(
Tomáš Plesník
committed
SOAP::Data->name(ID => $result[0]),
SOAP::Data->name(HOSTNAME => $result[1]),
SOAP::Data->name(SERVICE => $result[2]),
SOAP::Data->name(DETECTED => $result[3]),
SOAP::Data->name(TYPE => $result[4]),
SOAP::Data->name(SOURCE_TYPE => $result[5]),
SOAP::Data->name(SOURCE => $result[6]),
SOAP::Data->name(TARGET_PROTO => $result[7]),
SOAP::Data->name(TARGET_PORT => $result[8]),
SOAP::Data->name(ATTACK_SCALE => $result[9]),
SOAP::Data->name(NOTE => $result[10]),
SOAP::Data->name(PRIORITY => $result[11]),
SOAP::Data->name(TIMEOUT => $result[12]),
),
);
Tomáš Plesník
committed
push(@ids, $result[0]);
Tomáš Plesník
committed
if (scalar @events != 0) {
if (scalar @ids == 1) {
Tomáš Plesník
committed
sendMsg("info",
Tomáš Plesník
committed
"Sent 1 event [#$ids[0]] of type '$requested_type' to client '$client{'client_id'}'",
Tomáš Plesník
committed
undef);
Tomáš Plesník
committed
} else {
Tomáš Plesník
committed
sendMsg("info",
Tomáš Plesník
committed
"Sent " . scalar @ids . " events [#$ids[0] - #$ids[-1]] of type '$requested_type' to client '$client{'client_id'}'",
Tomáš Plesník
committed
undef);
Tomáš Plesník
committed
}
Tomáš Plesník
committed
return @events;
}
} # END of getNewEvents
Tomáš Plesník
committed
#-----------------------------------------------------------------------------
# getLastId - get lastest saved event ID
#-----------------------------------------------------------------------------
sub getLastId
{
my ($class, $arg) = @_;
Tomáš Plesník
committed
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $service = undef;
my $client_type = undef;
Tomáš Plesník
committed
# authorize incoming client
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
Tomáš Plesník
committed
# obtain max event ID
my $sth = $DBH->prepare("SELECT max(id) FROM events;");
Tomáš Plesník
committed
unless (defined $sth) {
Tomáš Plesník
committed
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
Tomáš Plesník
committed
}
Tomáš Plesník
committed
my $rc = $sth->execute;
Tomáš Plesník
committed
unless ($rc) {
sendMsg("err",
"Cannot execute statement in function '$function_name': $DBH->errstr",
"Internal 'execute' server error");
}
Tomáš Plesník
committed
#-------------------------------------------------------------------------------
# getClientInfo - get list of registered clients on Warden server
# by Warden client
#-------------------------------------------------------------------------------
sub getClientInfo
Tomáš Plesník
committed
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $service = undef;
my $client_type = undef;
Tomáš Plesník
committed
# authorize incoming client
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
Tomáš Plesník
committed
# obtain all valid clients from DB
my $sth = $DBH->prepare("SELECT * FROM clients WHERE valid = 't' ORDER BY client_id ASC;");
Tomáš Plesník
committed
unless (defined $sth) {
Tomáš Plesník
committed
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
Tomáš Plesník
committed
}
Tomáš Plesník
committed
my $rc = $sth->execute;
Tomáš Plesník
committed
unless ($rc) {
sendMsg("err",
"Cannot execute statement in function '$function_name': $DBH->errstr",
"Internal 'execute' server error");
}
Tomáš Plesník
committed
# create SOAP object
while ( my @result = $sth->fetchrow() ) {
$client = SOAP::Data->name(client => \SOAP::Data->value(
Tomáš Plesník
committed
SOAP::Data->name(CLIENT_ID => $result[0]),
SOAP::Data->name(HOSTNAME => $result[1]),
SOAP::Data->name(REGISTERED => $result[2]),
SOAP::Data->name(REQUESTOR => $result[3]),
SOAP::Data->name(SERVICE => $result[4]),
SOAP::Data->name(CLIENT_TYPE => $result[5]),
SOAP::Data->name(TYPE => $result[6]),
SOAP::Data->name(RECEIVE_OWN_EVENTS => $result[7]),
SOAP::Data->name(DESCRIPTION_TAGS => $result[8]),
SOAP::Data->name(IP_NET_CLIENT => $result[9]),
),
);
Tomáš Plesník
committed
Tomáš Plesník
committed
# log information message
Tomáš Plesník
committed
sendMsg("info",
Tomáš Plesník
committed
"Sent information about $sum registered clients from Warden server '$ENV{'SERVER_NAME'}' to client '$client{'client_id'}'",