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;
Tomáš Plesník
committed
my $lib = File::Basename::dirname(__FILE__);
use lib $lib;
Tomáš Plesník
committed
use WardenCommon;
################################################################################
# VARIABLES
################################################################################
Tomáš Plesník
committed
our $FILENAME = File::Basename::basename(__FILE__);
my $etc = "$lib/../etc";
Tomáš Plesník
committed
################################################################################
Tomáš Plesník
committed
# READING OF CONFIGURATION VARIABLES
################################################################################
my $conf_file = "$etc/warden-server.conf";
WardenCommon::loadConf($conf_file);
Tomáš Plesník
committed
################################################################################
################################################################################
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: $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;
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);
foreach my $tmp (@{$decoded->SubjectAltName}) {
Tomáš Plesník
committed
if($tmp =~ s/dNSName=//){
push(@an_array, $DBH->quote($tmp));
}
}
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) = @_;
my $sth;
# obtain cidr based on rigth common name and alternate names, service and client_type
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 = ? 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 = ? 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) 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' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
}
# check db handler
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare authorization statement in $function_name: $DBH->errstr",
"Internal 'prepare' server error");
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') {
$sth->execute($service_type, $client_type);
} else {
$sth->execute;
}
# obtain registration info 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;
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
if (!$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)
Tomáš Plesník
committed
# parse object (event) parameters
my $service = $data->{'SERVICE'};
my $detected = $data->{'DETECTED'};
Tomáš Plesník
committed
my $type = $data->{'TYPE'};
my $source_type = $data->{'SOURCE_TYPE'};
my $source = $data->{'SOURCE'};
my $target_proto = $data->{'TARGET_PROTO'};
my $target_port = $data->{'TARGET_PORT'};
my $attack_scale = $data->{'ATTACK_SCALE'};
Tomáš Plesník
committed
my $note = $data->{'NOTE'};
my $priority = $data->{'PRIORITY'};
my $timeout = $data->{'TIMEOUT'};
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
"Incoming event: [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']",
undef);
if (%WardenCommon::VALID_STRINGS) { # check if hash is not empty - use VALIDATION HASH
if (!(exists $WardenCommon::VALID_STRINGS{'type'} && grep $type eq $_, @{$WardenCommon::VALID_STRINGS{'type'}})) {
sendMsg("err",
"Unknown event type from [IP: '$ip'; CN(AN): $alt_names; Service: '$service'; Type: '$type']",
"Unknown event type: '$type'");
} elsif (!(exists $WardenCommon::VALID_STRINGS{'source_type'} && grep $source_type eq $_, @{$WardenCommon::VALID_STRINGS{'source_type'}})) {
sendMsg("err",
"Unknown source type from [IP '$ip'; CN(AN): $alt_names; Service: '$service'; Source_type: '$source_type']",
"Unknown source type: '$source_type'");
}
}
Tomáš Plesník
committed
Tomáš Plesník
committed
# 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])?/) {
Tomáš Plesník
committed
sendMsg("err",
"Unknown detected time format from [IP: '$ip'; CN(AN): $alt_names; Service: '$service'; Detected: '$detected']",
"Unknown detected time format: '$detected'");
}
Tomáš Plesník
committed
if (defined $target_port && $target_port !~ /^\d+\z/) {
push(@change_list, "target_port: '$target_port'");
Tomáš Plesník
committed
$target_port = undef;
}
if (defined $attack_scale && $attack_scale !~ /^\d+\z/) {
push(@change_list, "attack_scale: '$attack_scale'");
Tomáš Plesník
committed
$attack_scale = undef;
}
if (defined $priority && $priority !~ /^\d+\z/) {
push(@change_list, "priority: '$priority'");
Tomáš Plesník
committed
$priority = undef;
}
if (defined $timeout && $timeout !~ /^\d+\z/) {
push(@change_list, "timeout: '$timeout'");
Tomáš Plesník
committed
$timeout = undef;
}
my $change_string = join(", ", @change_list);
if ($change_string ne "") {
Tomáš Plesník
committed
sendMsg("info",
"Unknown event items detected {originaly - $change_string} received in $received from [IP '$ip'; CN(AN): $alt_names; Service: '$service'; Type: '$type'; Detected: $detected]",
Tomáš Plesník
committed
undef);
}
$sth=$DBH->prepare("INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
$sth->execute(undef, $detected, $received, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $valid, $client{'client_id'});
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) = @_;
my ($sth, @events, $event, @ids);
my ($id, $hostname, $service, $detected, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $client_id);
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'};
my $client_type = 'r'; # incoming client MUST be sender
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
Tomáš Plesník
committed
my $used_limit;
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
}
my %client = authorizeClient($alt_names, $ip, $requested_type, $client_type, $function_name);
Tomáš Plesník
committed
if ($client{'receive_own'} eq 't') {
if ($requested_type eq '_any_') {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ROE-ANY statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$sth->execute($last_id, $used_limit);
Tomáš Plesník
committed
} else {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND type = ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ROE statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$sth->execute($last_id, $requested_type, $used_limit);
Tomáš Plesník
committed
}
Tomáš Plesník
committed
if ($requested_type eq '_any_') {
$sth = $DBH->prepare("SELECT * FROM events e, clients c WHERE e.type != 'test' AND e.id > ? AND e.valid = 't' AND e.client_id = c.client_id AND c.hostname NOT LIKE ? ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ANY statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '%' . $domain;
Tomáš Plesník
committed
$sth->execute($last_id, $domain, $used_limit);
Tomáš Plesník
committed
} else {
$sth = $DBH->prepare("SELECT * FROM events e, clients c WHERE e.type != 'test' AND e.id > ? AND e.type = ? AND e.valid = 't' AND e.client_id = c.client_id AND c.hostname NOT LIKE ? ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr\n",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '%' . $domain;
Tomáš Plesník
committed
$sth->execute($last_id, $requested_type, $domain, $used_limit);
Tomáš Plesník
committed
}
# obtain items of events stored in events table
while (my @result = $sth->fetchrow()) {
$id = $result[0];
$detected = $result[1];
$type = $result[3];
$source_type = $result[4];
$source = $result[5];
$target_proto = $result[6];
$target_port = $result[7];
$attack_scale = $result[8];
$note = $result[9];
$priority = $result[10];
$timeout = $result[11];
$client_id = $result[13];
# obtain hostname and service of events based on client_id from clients table
$sth = $DBH->prepare("SELECT hostname, service FROM clients WHERE client_id = ?;");
$sth->execute($client_id);
($hostname, $service) = $sth->fetchrow();
# create SOAP data object
$event = SOAP::Data->name(event => \SOAP::Data->value(
Tomáš Plesník
committed
SOAP::Data->name(ID => $id),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(DETECTED => $detected),
SOAP::Data->name(TYPE => $type),
Tomáš Plesník
committed
SOAP::Data->name(SOURCE_TYPE => $source_type),
SOAP::Data->name(SOURCE => $source),
SOAP::Data->name(TARGET_PROTO => $target_proto),
SOAP::Data->name(TARGET_PORT => $target_port),
SOAP::Data->name(ATTACK_SCALE => $attack_scale),
SOAP::Data->name(NOTE => $note),
SOAP::Data->name(PRIORITY => $priority),
SOAP::Data->name(TIMEOUT => $timeout)
));
push(@events, $event);
push(@ids, $id);
}
# log sent ID of events
Tomáš Plesník
committed
if (scalar @events != 0) {
if (scalar @ids == 1) {
Tomáš Plesník
committed
sendMsg("info",
"Sent 1 event [#$ids[0]] to [IP: '$ip'; CN(AN): $alt_names; Client_limit: '$max_rcv_events_limit'; Requested_type: '$requested_type'; ROE: '$client{'receive_own'}']",
Tomáš Plesník
committed
undef);
Tomáš Plesník
committed
} else {
Tomáš Plesník
committed
sendMsg("info",
"Sent " . scalar @ids . " events [#$ids[0] - #$ids[-1]] to [IP: '$ip'; CN(AN): $alt_names; Client_limit: '$max_rcv_events_limit'; Requested_type: '$requested_type'; ROE: '$client{'receive_own'}']",
Tomáš Plesník
committed
undef);
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;
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
my $sth = $DBH->prepare("SELECT max(id) FROM events;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
Tomáš Plesník
committed
}
#-------------------------------------------------------------------------------
# getClientInfo - get list of registered clients on Warden server
# by Warden client
#-------------------------------------------------------------------------------
sub getClientInfo
my (@clients, $client);
my ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
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;
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
my $sth = $DBH->prepare("SELECT * FROM clients WHERE valid = 't' ORDER BY client_id ASC;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
Tomáš Plesník
committed
}
$sth->execute;
while ( my @result = $sth->fetchrow() ) {
$client_id = $result[0];
$hostname = $result[1];
$registered = $result[2];
$requestor = $result[3];
$service = $result[4];
$type = $result[6];
$receive_own_events = $result[7];
$description_tags = $result[8];
$ip_net_client = $result[9];
$client = SOAP::Data->name(client => \SOAP::Data->value(
SOAP::Data->name(CLIENT_ID => $client_id),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(REGISTERED => $registered),
SOAP::Data->name(REQUESTOR => $requestor),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(CLIENT_TYPE => $client_type),
SOAP::Data->name(TYPE => $type),
SOAP::Data->name(RECEIVE_OWN_EVENTS => $receive_own_events),
SOAP::Data->name(DESCRIPTION_TAGS => $description_tags),
SOAP::Data->name(IP_NET_CLIENT => $ip_net_client),
));
push(@clients, $client);
}
my $sum = scalar @clients;
Tomáš Plesník
committed
sendMsg("info",
"Sent information about $sum registered clients from Warden server '$ENV{'SERVER_NAME'}'",
undef);