use
5.006;
use
File::Slurp
qw(append_file write_file read_file write_file)
;
our
$VERSION
=
'3.0.0'
;
sub
new {
my
$ini
=
$_
[1];
if
( !
defined
(
$ini
) ) {
$ini
=
'/usr/local/etc/cape_utils.ini'
;
}
my
$base_config
= {
'_'
=> {
dsn
=>
'dbi:Pg:dbname=cape'
,
user
=>
'cape'
,
pass
=>
''
,
base
=>
'/opt/CAPEv2/'
,
eve
=>
'/opt/CAPEv2/log/eve.json'
,
poetry
=> 1,
fail_all
=> 0,
pending_columns
=>
'id,target,package,timeout,ET,route,options,clock,added_on'
,
running_columns
=>
'id,target,package,timeout,ET,route,options,clock,added_on,started_on,machine'
,
task_columns
=>
'id,target,package,timeout,ET,route,options,clock,added_on,latest,machine,status'
,
running_target_clip
=> 1,
running_time_clip
=> 1,
pending_target_clip
=> 1,
pending_time_clip
=> 1,
task_target_clip
=> 1,
task_time_clip
=> 1,
table_color
=>
'Text::ANSITable::Standard::NoGradation'
,
table_border
=>
'ASCII::None'
,
set_clock_to_now
=> 1,
timeout
=> 200,
enforce_timeout
=> 0,
subnets
=>
'192.168.0.0/16,127.0.0.1/8,::1/128,172.16.0.0/12,10.0.0.0/8'
,
apikey
=>
''
,
auth
=>
'ip'
,
incoming
=>
'/malware/client-incoming'
,
incoming_json
=>
'/malware/incoming-json'
,
eve_look_back
=> 360,
malscore
=> 0,
},
};
my
$config
= Config::Tiny->
read
(
$ini
,
'utf8'
);
if
( !
defined
(
$config
) ) {
$config
=
$base_config
;
}
else
{
my
@to_merge
=
keys
( %{
$base_config
->{_} } );
foreach
my
$item
(
@to_merge
) {
if
( !
defined
(
$config
->{_}->{
$item
} ) ) {
$config
->{_}->{
$item
} =
$base_config
->{_}->{
$item
};
}
}
}
my
$self
= {
config
=>
$config
, };
bless
$self
;
return
$self
;
}
sub
connect
{
my
$self
=
$_
[0];
my
$dbh
= DBI->
connect
(
$self
->{config}->{_}->{dsn},
$self
->{config}->{_}->{user},
$self
->{config}->{_}->{pass} )
||
die
(
$DBI::errstr
);
return
$dbh
;
}
sub
fail {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
if
( !
defined
(
$opts
{where} ) && !
$self
->{config}->{_}->{fail_all} ) {
die
"fail_all is disabled and nothing specified for where"
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"UPDATE tasks SET status = 'failed_processing' WHERE status = 'pending'"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' AND '
.
$opts
{where};
}
$statement
=
$statement
.
';'
;
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$rows
=
$sth
->rows;
$sth
->finish;
$dbh
->disconnect;
return
$rows
;
}
sub
get_pending_count {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"select * from tasks where status = 'pending'"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' AND '
.
$opts
{where};
}
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$rows
=
$sth
->rows;
$sth
->finish;
$dbh
->disconnect;
return
$rows
;
}
sub
get_pending {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"select * from tasks where status = 'pending'"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' AND '
.
$opts
{where};
}
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$row
;
my
@rows
;
while
(
$row
=
$sth
->fetchrow_hashref ) {
push
(
@rows
,
$row
);
}
$sth
->finish;
$dbh
->disconnect;
return
\
@rows
;
}
sub
get_pending_table {
my
(
$self
,
%opts
) =
@_
;
my
@overrides
= (
'table_border'
,
'table_color'
,
'pending_columns'
,
'pending_target_clip'
,
'pending_time_clip'
);
foreach
my
$override
(
@overrides
) {
if
( !
defined
(
$opts
{
$override
} ) ) {
$opts
{
$override
} =
$self
->{config}->{_}->{
$override
};
}
}
my
$rows
=
$self
->get_pending(
where
=>
$opts
{where} );
my
$tb
= Text::ANSITable->new;
$tb
->border_style(
$opts
{table_border} );
$tb
->color_theme(
$opts
{table_color} );
my
@columns
=
split
( /,/,
$opts
{pending_columns} );
my
$header_int
= 0;
my
$padding
= 0;
foreach
my
$header
(
@columns
) {
if
( (
$header_int
% 2 ) != 0 ) {
$padding
= 1; }
else
{
$padding
= 0; }
$tb
->set_column_style(
$header_int
,
pad
=>
$padding
);
$header_int
++;
}
$tb
->columns( \
@columns
);
my
@td
;
foreach
my
$row
( @{
$rows
} ) {
my
@new_line
;
foreach
my
$column
(
@columns
) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
(
defined
(
$row
->{
$column
} ) ) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
( (
$column
eq
'clock'
||
$column
eq
'added_on'
) &&
$opts
{pending_time_clip} ) {
$row
->{
$column
} =~ s/\.[0-9]+$//;
}
elsif
(
$column
eq
'target'
&&
$opts
{pending_target_clip} ) {
$row
->{target} =~ s/^.*\///;
}
push
(
@new_line
,
$row
->{
$column
} );
}
else
{
push
(
@new_line
,
''
);
}
}
push
(
@td
, \
@new_line
);
}
$tb
->add_rows( \
@td
);
return
$tb
->draw;
}
sub
get_running {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"select * from tasks where status = 'running'"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' AND '
.
$opts
{where};
}
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$row
;
my
@rows
;
while
(
$row
=
$sth
->fetchrow_hashref ) {
push
(
@rows
,
$row
);
}
$sth
->finish;
$dbh
->disconnect;
return
\
@rows
;
}
sub
get_running_count {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"select * from tasks where status = 'running'"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' AND '
.
$opts
{where};
}
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$rows
=
$sth
->rows;
$sth
->finish;
$dbh
->disconnect;
return
$rows
;
}
sub
get_running_table {
my
(
$self
,
%opts
) =
@_
;
my
@overrides
= (
'table_border'
,
'table_color'
,
'running_columns'
,
'running_target_clip'
,
'running_time_clip'
);
foreach
my
$override
(
@overrides
) {
if
( !
defined
(
$opts
{
$override
} ) ) {
$opts
{
$override
} =
$self
->{config}->{_}->{
$override
};
}
}
my
$rows
=
$self
->get_running(
where
=>
$opts
{where} );
my
$tb
= Text::ANSITable->new;
$tb
->border_style(
$opts
{table_border} );
$tb
->color_theme(
$opts
{table_color} );
my
@columns
=
split
( /,/,
$opts
{running_columns} );
my
$header_int
= 0;
my
$padding
= 0;
foreach
my
$header
(
@columns
) {
if
( (
$header_int
% 2 ) != 0 ) {
$padding
= 1; }
else
{
$padding
= 0; }
$tb
->set_column_style(
$header_int
,
pad
=>
$padding
);
$header_int
++;
}
$tb
->columns( \
@columns
);
my
@td
;
foreach
my
$row
( @{
$rows
} ) {
my
@new_line
;
foreach
my
$column
(
@columns
) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
(
defined
(
$row
->{
$column
} ) ) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
( (
$column
eq
'clock'
||
$column
eq
'added_on'
||
$column
eq
'started_on'
)
&&
$opts
{running_time_clip} )
{
$row
->{
$column
} =~ s/\.[0-9]+$//;
}
elsif
(
$column
eq
'target'
&&
$opts
{running_target_clip} ) {
$row
->{target} =~ s/^.*\///;
}
push
(
@new_line
,
$row
->{
$column
} );
}
else
{
push
(
@new_line
,
''
);
}
}
push
(
@td
, \
@new_line
);
}
$tb
->add_rows( \
@td
);
return
$tb
->draw;
}
sub
get_tasks {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
if
(
defined
(
$opts
{order} ) &&
$opts
{order} !~ /^[0-9a-zA-Z]+$/ ) {
die
'$opts{order},"'
.
$opts
{order} .
'", does not match /^[0-9a-zA-Z]+$/'
;
}
else
{
$opts
{order} =
'id'
;
}
if
(
defined
(
$opts
{limit} ) &&
$opts
{limit} !~ /^[0-9]+$/ ) {
die
'$opts{limit},"'
.
$opts
{limit} .
'", does not match /^[0-9]+$/'
;
}
else
{
$opts
{limit} =
'100'
;
}
if
(
defined
(
$opts
{direction} ) ) {
$opts
{direction} =
lc
(
$opts
{direction} );
}
if
(
defined
(
$opts
{direction} ) && (
$opts
{direction} ne
'desc'
||
$opts
{direction} ne
'asc'
) ) {
die
'$opts{diirection},"'
.
$opts
{direction} .
'", does not match desc or asc'
;
}
else
{
$opts
{direction} =
'desc'
;
}
my
$dbh
;
eval
{
$dbh
=
$self
->
connect
or
die
$DBI::errstr
};
if
($@) {
die
(
'Failed to connect to the DB... '
. $@ );
}
my
$statement
=
"select * from tasks"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' where '
.
$opts
{where};
}
$statement
=
$statement
.
' order by '
.
$opts
{order} .
' '
.
$opts
{direction} .
' limit '
.
$opts
{limit} .
';'
;
my
$sth
;
eval
{
$sth
=
$dbh
->prepare(
$statement
) or
die
$DBI::errstr
;
$sth
->execute or
die
$DBI::errstr
;
};
if
($@) {
die
(
'Failed to connect to run the search... '
. $@ );
}
my
$row
;
my
@rows
;
while
(
$row
=
$sth
->fetchrow_hashref ) {
push
(
@rows
,
$row
);
}
$sth
->finish;
$dbh
->disconnect;
return
\
@rows
;
}
sub
get_tasks_count {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
$dbh
=
$self
->
connect
;
my
$statement
=
"select * from tasks"
;
if
(
defined
(
$opts
{where} ) ) {
$statement
=
$statement
.
' '
.
$opts
{where};
}
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$rows
=
$sth
->rows;
$sth
->finish;
$dbh
->disconnect;
return
$rows
;
}
sub
get_tasks_table {
my
(
$self
,
%opts
) =
@_
;
my
@overrides
= (
'table_border'
,
'table_color'
,
'task_columns'
,
'task_target_clip'
,
'task_time_clip'
);
foreach
my
$override
(
@overrides
) {
if
( !
defined
(
$opts
{
$override
} ) ) {
$opts
{
$override
} =
$self
->{config}->{_}->{
$override
};
}
}
my
$rows
=
$self
->get_tasks(
where
=>
$opts
{where},
order
=>
$opts
{order},
limit
=>
$opts
{limit},
direction
=>
$opts
{direction}
);
my
$tb
= Text::ANSITable->new;
$tb
->border_style(
$opts
{table_border} );
$tb
->color_theme(
$opts
{table_color} );
my
@columns
=
split
( /,/,
$opts
{task_columns} );
my
$header_int
= 0;
my
$padding
= 0;
foreach
my
$header
(
@columns
) {
if
( (
$header_int
% 2 ) != 0 ) {
$padding
= 1; }
else
{
$padding
= 0; }
$tb
->set_column_style(
$header_int
,
pad
=>
$padding
);
$header_int
++;
}
$tb
->columns( \
@columns
);
my
@td
;
my
@latest_check
= (
'started_on'
,
'analysis_started_on'
,
'analysis_finished_on'
,
'analysis_finished_on'
,
'processing_finished_on'
,
'signatures_started_on'
,
'signatures_finished_on'
,
'reporting_started_on'
,
'reporting_finished_on'
,
'completed_on'
);
foreach
my
$row
( @{
$rows
} ) {
my
@new_line
;
foreach
my
$column
(
@columns
) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
(
$column
eq
'latest'
) {
$row
->{latest} =
''
;
foreach
my
$item
(
@latest_check
) {
if
(
defined
(
$row
->{
$item
} ) ) {
$row
->{latest} =
$row
->{
$item
};
}
}
}
if
(
defined
(
$row
->{
$column
} ) ) {
if
(
$column
eq
'ET'
) {
$row
->{ET} =
$row
->{enforce_timeout};
}
if
(
(
$column
eq
'clock'
||
$column
eq
'added_on'
||
$column
eq
'started_on'
||
$column
eq
'completed_on'
||
$column
eq
'analysis_started_on'
||
$column
eq
'analysis_finished_on'
||
$column
eq
'processing_started_on'
||
$column
eq
'processing_finished_on'
||
$column
eq
'signatures_started_on'
||
$column
eq
'signatures_finished_on'
||
$column
eq
'reporting_started_on'
||
$column
eq
'reporting_finished_on'
||
$column
eq
'latest'
)
&&
$opts
{task_time_clip}
)
{
$row
->{
$column
} =~ s/\.[0-9]+$//;
}
elsif
(
$column
eq
'target'
&&
$opts
{task_target_clip} ) {
$row
->{target} =~ s/^.*\///;
}
push
(
@new_line
,
$row
->{
$column
} );
}
else
{
push
(
@new_line
,
''
);
}
}
push
(
@td
, \
@new_line
);
}
$tb
->add_rows( \
@td
);
return
$tb
->draw;
}
sub
munge {
my
(
$self
,
%opts
) =
@_
;
if
( !
defined
(
$opts
{file} ) ) {
die
(
'No file specified via $opts{file}'
);
}
if
( !-f
$opts
{file} ) {
die
(
'"'
.
$opts
{file} .
'" is not a file'
);
}
my
$pre_munge_file
=
$opts
{file} .
'.pre-cape_utils_munge'
;
if
( !-f
$pre_munge_file
) {
copy(
$opts
{file},
$pre_munge_file
)
||
die
(
'Creating pre-munge file for "'
.
$opts
{file} .
'" failed... '
. $! );
}
else
{
warn
(
'Pre-munge file, "'
.
$pre_munge_file
.
'", already exists, skippying coppying'
);
}
my
$report
;
eval
{
$report
= decode_json( read_file(
$opts
{file} ) ); };
if
($@) {
die
(
'Failed to parse "'
.
$opts
{file} .
'"... '
. $@ );
}
my
@sections
=
sort
(
keys
( %{
$self
->{config} } ) );
my
@munges
;
foreach
my
$item
(
@sections
) {
if
(
$item
=~ /^munge\_/ ) {
push
(
@munges
,
$item
);
}
}
my
$changed
= 0;
my
%all_scratch
;
foreach
my
$item
(
@munges
) {
my
%scratch
;
if
(
defined
(
$self
->{config}{
$item
}{check} ) &&
defined
(
$self
->{config}{
$item
}{munge} ) ) {
my
$check_file
=
$self
->{config}{
$item
}{check};
my
$munge_file
=
$self
->{config}{
$item
}{munge};
if
(
$check_file
!~ /^\// &&
$check_file
!~ /^.\// &&
$check_file
!~ /^..\// ) {
$check_file
=
'/usr/local/etc/cape_utils_munge/'
.
$check_file
;
}
if
(
$munge_file
!~ /^\// &&
$munge_file
!~ /^.\// &&
$munge_file
!~ /^..\// ) {
$munge_file
=
'/usr/local/etc/cape_utils_munge/'
.
$munge_file
;
}
my
$munge_it
= 0;
eval
{
my
$check_code
= read_file(
$check_file
);
eval
(
$check_code
);
if
($@) {
die
($@);
}
};
if
($@) {
warn
(
'Munge "'
.
$item
.
'" errored during the check... '
. $@ );
$munge_it
= 0;
}
if
(
$munge_it
) {
eval
{
my
$munge_code
= read_file(
$munge_file
);
eval
(
$munge_code
);
if
($@) {
die
($@);
}
};
if
($@) {
warn
(
'Munge "'
.
$item
.
'" errored during the munge... '
. $@ );
}
}
}
else
{
warn
(
'Section "'
.
$item
.
'" missing either the key "check" or munge"'
);
}
}
if
(
$changed
) {
my
$malscore
= 0.0;
my
$sig_int
= 0;
while
(
defined
(
$report
->{signatures}[
$sig_int
] ) ) {
if
(
$report
->{signatures}[
$sig_int
]{severity} ) {
$malscore
+=
$report
->{signatures}[
$sig_int
]{weight} * 0.5
* (
$report
->{signatures}[
$sig_int
]{confidence} / 100 );
}
else
{
$malscore
+=
$report
->{signatures}[
$sig_int
]{weight}
* (
$report
->{signatures}[
$sig_int
]{weight} - 1 )
* (
$report
->{signatures}[
$sig_int
]{confidence} / 100 );
}
$sig_int
++;
}
if
(
$malscore
> 10.0 ) {
$malscore
= 10.0;
}
$report
->{malscore} =
$malscore
;
eval
{ write_file(
$opts
{file}, encode_json(
$report
) ); };
if
($@) {
die
(
'Failed to encode updated report post munging and write it to "'
.
$opts
{file} .
'"... '
. $@ );
}
}
return
1;
}
sub
search {
my
(
$self
,
%opts
) =
@_
;
if
(
defined
(
$opts
{where} ) &&
$opts
{where} =~ /\;/ ) {
die
'$opts{where},"'
.
$opts
{where} .
'", contains a ";"'
;
}
my
@to_check
= (
'id'
,
'target'
,
'route'
,
'machine'
,
'timeout'
,
'priority'
,
'route'
,
'tags_tasks'
,
'options'
,
'clock'
,
'added_on'
,
'started_on'
,
'completed_on'
,
'status'
,
'dropped_files'
,
'running_processes'
,
'api_calls'
,
'domains'
,
'signatures_total'
,
'signatures_alert'
,
'files_written'
,
'registry_keys_modified'
,
'crash_issues'
,
'anti_issues'
,
'timedout'
,
'sample_id'
,
'machine_id'
,
'parent_id'
,
'tlp'
,
'category'
,
'package'
);
foreach
my
$var_to_check
(
@to_check
) {
if
(
defined
(
$opts
{
$var_to_check
} ) &&
$opts
{
$var_to_check
} =~ /[\\\']/ ) {
die
(
'"'
.
$opts
{
$var_to_check
} .
'" for "'
.
$var_to_check
.
'" matched /[\\\']/'
);
}
}
my
$sql
=
"select * from tasks where id >= 0"
;
if
(
defined
(
$opts
{where} ) ) {
$sql
=
$sql
.
' AND '
.
$opts
{where};
}
my
@simple
= (
'timeout'
,
'memory'
,
'enforce_timeout'
,
'timedout'
);
foreach
my
$item
(
@simple
) {
if
(
defined
(
$opts
{
$item
} ) ) {
$sql
=
$sql
.
" and "
.
$item
.
" = '"
.
$opts
{
$item
} .
"'"
;
}
}
my
@numeric
= (
'id'
,
'timeout'
,
'priority'
,
'dropped_files'
,
'running_processes'
,
'api_calls'
,
'domains'
,
'signatures_total'
,
'signatures_alert'
,
'files_written'
,
'registry_keys_modified'
,
'crash_issues'
,
'anti_issues'
,
'sample_id'
,
'machine_id'
);
foreach
my
$item
(
@numeric
) {
if
(
defined
(
$opts
{
$item
} ) ) {
$opts
{
$item
} =~ s/[\ \t]//g;
my
@arg_split
=
split
( /\,/,
$opts
{
$item
} );
foreach
my
$arg
(
@arg_split
) {
if
(
$arg
=~ /^[0-9]+$/ ) {
$sql
=
$sql
.
" and "
.
$item
.
" = '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\=[0-9]+$/ ) {
$arg
=~ s/^\=//;
$sql
=
$sql
.
" and "
.
$item
.
" <= '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\<\=[0-9]+$/ ) {
$arg
=~ s/^\<\=//;
$sql
=
$sql
.
" and "
.
$item
.
" <= '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\<[0-9]+$/ ) {
$arg
=~ s/^\<//;
$sql
=
$sql
.
" and "
.
$item
.
" < '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\>\=[0-9]+$/ ) {
$arg
=~ s/^\>\=//;
$sql
=
$sql
.
" and "
.
$item
.
" >= '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\>[0-9]+$/ ) {
$arg
=~ s/^\>\=//;
$sql
=
$sql
.
" and "
.
$item
.
" > '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^\![0-9]+$/ ) {
$arg
=~ s/^\!//;
$sql
=
$sql
.
" and "
.
$item
.
" != '"
.
$arg
.
"'"
;
}
elsif
(
$arg
=~ /^$/ ) {
}
else
{
die
(
'"'
.
$arg
.
'" does not appear to be a valid item for a numeric search for the '
.
$item
);
}
}
}
}
my
@strings
= (
'target'
,
'category'
,
'custom'
,
'machine'
,
'package'
,
'route'
,
'tags_tasks'
,
'options'
,
'platform'
, );
foreach
my
$item
(
@strings
) {
if
(
defined
(
$opts
{
$item
} ) ) {
if
(
defined
(
$opts
{
$item
.
'_like'
} ) &&
$opts
{
$item
.
'_like'
} ) {
$sql
=
$sql
.
" and host like '"
.
$opts
{
$item
} .
"'"
;
}
else
{
$sql
=
$sql
.
" and "
.
$item
.
" = '"
.
$opts
{
$item
} .
"'"
;
}
}
}
$sql
=
$sql
.
';'
;
my
$dbh
=
$self
->
connect
;
my
$sth
=
$dbh
->prepare(
$sql
);
$sth
->execute;
my
$rows
;
return
$rows
;
}
sub
submit {
my
(
$self
,
%opts
) =
@_
;
if
( !
defined
(
$opts
{items}[0] ) ) {
die
'No items to submit passed'
;
}
if
( !
defined
(
$opts
{clock} ) &&
$self
->{config}->{_}->{set_clock_to_now} ) {
$opts
{clock} =
$self
->timestamp;
}
if
( !
defined
(
$opts
{timeout} ) ) {
$opts
{timeout} =
$self
->{config}->{_}->{timeout};
}
if
( !
defined
(
$opts
{enforce_timeout} ) ) {
$opts
{enforce_timeout} =
$self
->{config}->{_}->{enforce_timeout};
}
my
@to_submit
;
foreach
my
$item
( @{
$opts
{items} } ) {
if
( -f
$item
) {
push
(
@to_submit
, File::Spec->rel2abs(
$item
) );
}
elsif
( -d
$item
) {
opendir
(
my
$dh
,
$item
);
while
(
readdir
(
$dh
) ) {
if
( -f
$item
.
'/'
.
$_
) {
push
(
@to_submit
, File::Spec->rel2abs(
$item
.
'/'
.
$_
) );
}
}
closedir
(
$dh
);
}
}
chdir
(
$self
->{config}->{_}->{base} ) ||
die
(
'Unable to CD to "'
.
$self
->{config}->{_}->{base} .
'"'
);
my
@to_run
= ();
if
(
$self
->{config}->{_}->{poetry} ) {
push
(
@to_run
,
'poetry'
,
'run'
);
}
push
(
@to_run
,
'python3'
,
$self
->{config}->{_}->{base} .
'/utils/submit.py'
);
if
(
defined
(
$opts
{clock} ) ) {
push
(
@to_run
,
'--clock'
,
$opts
{clock} );
}
if
(
defined
(
$opts
{unique} ) &&
$opts
{unique} ) {
push
(
@to_run
,
'--unique'
);
}
if
(
defined
(
$opts
{timeout} ) ) {
push
(
@to_run
,
'--timeout'
,
$opts
{timeout} );
}
if
(
$opts
{enforce_timeout} &&
$opts
{enforce_timeout} ) {
push
(
@to_run
,
'--enforce-timeout'
);
}
if
(
defined
(
$opts
{
package
} ) ) {
push
(
@to_run
,
'--package'
,
$opts
{
package
} );
}
if
(
defined
(
$opts
{machine} ) ) {
push
(
@to_run
,
'--machine'
,
$opts
{machine} );
}
if
(
defined
(
$opts
{options} ) ) {
push
(
@to_run
,
'--options'
,
$opts
{options} );
}
if
(
defined
(
$opts
{tags} ) ) {
push
(
@to_run
,
'--tags'
,
$opts
{tags} );
}
my
$added
= {};
foreach
(
@to_submit
) {
my
@tmp_to_run
=
@to_run
;
push
(
@tmp_to_run
,
$_
);
my
(
$success
,
$error_message
,
$full_buf
,
$stdout_buf
,
$stderr_buf
) = run(
command
=> \
@tmp_to_run
,
verbose
=> 0
);
my
$results
=
join
(
''
, @{
$full_buf
} );
if
( !
$opts
{quiet} ) {
print
$results
;
}
my
@results_split
=
split
( /\n/,
$results
);
foreach
my
$item
(
@results_split
) {
$item
=~ s/\e\[[0-9;]
*m
(?:\e\[K)?//g;
chomp
(
$item
);
if
(
$item
=~ /^Success\:\ File\ \".*\"\ added\ as\ task\
with
\ ID\ \d+$/ ) {
$item
=~ s/^Success\:\ File\ \"//;
my
(
$file
,
$task
) =
split
( /\"\ added\ as\ task\
with
\ ID\ /,
$item
);
$added
->{
$file
} =
$task
;
}
}
}
return
$added
;
}
sub
timestamp {
my
(
$sec
,
$min
,
$hour
,
$mday
,
$mon
,
$year
,
$wday
,
$yday
,
$isdst
) =
localtime
;
$year
+= 1900;
$mon
++;
if
(
$sec
< 10 ) {
$sec
=
'0'
.
$sec
;
}
if
(
$min
< 10 ) {
$min
=
'0'
.
$min
;
}
if
(
$hour
< 10 ) {
$hour
=
'0'
.
$hour
;
}
if
(
$mon
< 10 ) {
$mon
=
'0'
.
$mon
;
}
if
(
$mday
< 10 ) {
$mday
=
'0'
.
$mday
;
}
return
$mon
.
'-'
.
$mday
.
'-'
.
$year
.
' '
.
$hour
.
':'
.
$min
.
':'
.
$sec
;
}
sub
shuffle {
my
$self
=
shift
;
my
$array
=
shift
;
my
$i
;
for
(
$i
=
@$array
; --
$i
; ) {
my
$j
=
int
rand
(
$i
+ 1 );
next
if
$i
==
$j
;
@$array
[
$i
,
$j
] =
@$array
[
$j
,
$i
];
}
return
$array
;
}
sub
check_remote {
my
(
$self
,
%opts
) =
@_
;
if
( (
$self
->{config}->{_}->{auth} ne
'ip'
||
$self
->{config}->{_}->{auth} ne
'either'
)
&& !
defined
(
$opts
{apikey} ) )
{
return
0;
}
if
(
$self
->{config}->{_}->{auth} ne
'ip'
&&
defined
(
$opts
{apikey} )
&&
$opts
{apikey} ne
$self
->{config}->{_}->{apikey} )
{
if
(
$self
->{config}->{_}->{auth} ne
'either'
) {
return
0;
}
}
if
(
defined
(
$opts
{apikey} )
&& (
$self
->{config}->{_}->{auth} ne
'apikey'
||
$self
->{config}->{_}->{auth} ne
'either'
) )
{
return
1;
}
if
( !
defined
(
$opts
{ip} ) ) {
return
0;
}
my
$subnets_string
=
$self
->{config}->{_}->{subnets};
$subnets_string
=~ s/[\ \t]+//g;
$subnets_string
=~ s/\,+/,/g;
my
@subnets_split
=
split
( /,/,
$subnets_string
);
my
@subnets
;
foreach
my
$item
(
@subnets_split
) {
if
(
$item
=~ /^[\:A-Fa-f0-9]+$/ ) {
push
(
@subnets
,
$item
.
'/128'
);
}
elsif
(
$item
=~ /^[\:A-Fa-f0-9]+\/[0-9]+$/ ) {
push
(
@subnets
,
$item
);
}
elsif
(
$item
=~ /^[\.0-9]+$/ ) {
push
(
@subnets
,
$item
.
'/32'
);
}
elsif
(
$item
=~ /^[\.0-9]+\/[0-9]+$/ ) {
push
(
@subnets
,
$item
);
}
}
my
$allowed_subnets
;
eval
{
$allowed_subnets
= subnet_matcher(
@subnets
); };
if
($@) {
die
(
'Failed it init subnet matcher... '
. $@ );
}
if
(
$allowed_subnets
->(
$opts
{ip} ) ) {
return
1;
}
return
0;
}
sub
eve_process {
my
(
$self
,
%opts
) =
@_
;
my
$dbh
;
eval
{
$dbh
=
$self
->
connect
or
die
$DBI::errstr
};
if
($@) {
die
(
'Failed to connect to the DB... '
. $@ );
}
my
$statement
=
"select * from tasks where ( status = 'reported' ) AND ( completed_on >= CURRENT_TIMESTAMP - interval '"
.
$self
->{config}{_}{eve_look_back}
.
" seconds' )"
;
my
$sth
=
$dbh
->prepare(
$statement
);
$sth
->execute;
my
$row
;
my
@rows
;
while
(
$row
=
$sth
->fetchrow_hashref ) {
push
(
@rows
,
$row
);
}
$sth
->finish;
$dbh
->disconnect;
my
$main_eve
=
$self
->{config}{_}{eve};
foreach
my
$row
(
@rows
) {
my
$report
=
$self
->{config}{_}{base} .
'/storage/analyses/'
.
$row
->{id} .
'/reports/lite.json'
;
my
$id_eve
=
$self
->{config}{_}{incoming_json} .
'/'
.
$row
->{id} .
'.eve.json'
;
my
$incoming_json
=
$self
->{config}{_}{incoming_json} .
'/'
.
$row
->{id} .
'.json'
;
if
( -f
$report
&& -r
$report
&& !-f
$id_eve
) {
my
$eve_json
;
if
( -f
$incoming_json
) {
eval
{
$eve_json
= decode_json( read_file(
$incoming_json
) );
$eve_json
->{cape_eve_process} = {
incoming_json_error
=>
undef
, };
};
if
($@) {
my
$error_message
=
'Failed to decode incoming JSON for '
.
$row
->{id} .
' ... '
. $@;
$self
->log_drek(
'cape_eve_process'
,
'err'
,
$error_message
);
$eve_json
= {
cape_eve_process
=> {
incoming_json_error
=>
$error_message
,
},
};
}
}
else
{
$eve_json
= {
cape_eve_process
=> {}, };
}
$eve_json
->{cape_eve_process}{
time
} =
time
;
$eve_json
->{cape_eve_process}{host} = hostname;
$eve_json
->{row} =
$row
;
$eve_json
->{event_type} =
'potential_malware_detonation'
;
if
( !
defined
(
$eve_json
->{cape_eve_process}{incoming_json_error} ) ) {
$eve_json
->{cape_eve_process}{incoming_json_error} =
undef
,;
}
my
$lite_json
;
eval
{
$lite_json
= decode_json( read_file(
$report
) );
if
(
defined
(
$lite_json
->{signatures} ) ) {
$eve_json
->{signatures} =
$lite_json
->{signatures};
}
if
(
defined
(
$lite_json
->{malscore} ) ) {
$eve_json
->{malscore} =
$lite_json
->{malscore};
if
(
$lite_json
->{malscore} >=
$self
->{config}{_}{malscore} ) {
$eve_json
->{event_type} =
'alert'
;
}
}
};
if
($@) {
my
$error_message
=
'Failed to decode lite.json for '
.
$row
->{id} .
' ... '
. $@;
$self
->log_drek(
'cape_eve_process'
,
'err'
,
$error_message
);
$eve_json
->{cape_eve_process}{lite_json_error} =
$error_message
,;
}
my
$raw_eve_json
= encode_json(
$eve_json
) .
"\n"
;
eval
{ write_file(
$id_eve
,
$raw_eve_json
); };
if
($@) {
my
$error_message
=
'Failed to write out ID EVE for '
.
$row
->{id} .
' at '
.
$id_eve
.
' ... '
. $@;
$self
->log_drek(
'cape_eve_process'
,
'err'
,
$error_message
);
}
eval
{ append_file(
$self
->{config}{_}{eve},
$raw_eve_json
); };
}
else
{
if
( !-f
$report
|| !-r
$report
) {
warn
(
$row
->{id} .
' reported, but lite.json does not exist for it or it is not readable'
);
}
}
}
}
sub
log_drek {
my
(
$self
,
$sender
,
$level
,
$message
) =
@_
;
if
( !
defined
(
$level
) ) {
$level
=
'info'
;
}
if
( !
defined
(
$sender
) ) {
$sender
=
'CAPE::Utils'
;
}
openlog(
$sender
,
'cons,pid'
,
'daemon'
);
syslog(
$level
,
'%s'
,
$message
);
closelog();
}
1;