The following script can be used to integrate TOPdesk with your beSECURE solution, the script does a few things to preform this:
- Connects to the server using the API key you provide in the command line
- Asks the AVDS system to return the list of scans (also called networks) whose scan has completed in the past time period of minutes/hours/days based on the parameter you provided in the command line
- Requests a differential report for each of these scans
- Generate a ticket for every High or Medium risk vulnerability that is new
Code:
#!/usr/bin/perl -w
use Compress::Zlib qw(uncompress);
use MIME::Base64 qw(decode_base64);
use XML::Simple;
use Data::Dumper;
use LWP::UserAgent;
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
use HTTP::Request::Common;
use HTTP::Cookies;
use JSON::XS;
use Getopt::Std;
use strict;
my $http = "http"; # add s for https - perl has a documented bug where SSL connection would prematurely terminate - returning "half-buffers"
my $CallerName = "AVDS System";
my $DescriptionCategory = "HR Services";
my $DescriptionSubCategory = "Company Policy";
my $TOPDeskUsername = "GuardmanBob";
my $TOPDeskPassword = "CoyuNLz4";
my $TOPDeskURL = "http://webdemo-enterprise-en.topdesk.com/tas/secure/incident";
my %TOPDeskItems = (
'action' => 'new',
'status' => '1', # 1 for First Line, 2 for Second Line
'replacefield0' => 'personido',
'searchfield0' => 'ref_dynanaam',
'searchvalue0' => $CallerName,
'replacefield1' => 'incident_domeinid',
'searchfield1' => 'naam',
'searchvalue1' => $DescriptionCategory,
'replacefield2' => 'incident_specid',
'searchfield2' => 'naam',
'searchvalue2' => $DescriptionSubCategory,
'field0' => 'verzoek',
'value0' => '', # Summary of vulnerability
'field1' => 'actie',
'value1' => '', # Solution to vulnerability
'save' => 'true',
'j_username' => $TOPDeskUsername,
'j_password' => $TOPDeskPassword,
);
$| = 1;
my %opt;
my $opt_string = 'H:a:d:m:h:t';
getopts( "$opt_string", \%opt );
if ( not defined $opt{H} or
not defined $opt{a}
)
{
usage();
}
my $Host = $opt{H};
my $APIKey = $opt{a};
my $DayRange = $opt{d}; # How many days back to download reports for
my $MinRange = $opt{m};
my $HourRange = $opt{h};
my $FilenameType = $opt{t};
if (not defined $FilenameType) {
$FilenameType = 1; ## Scan Name
}
if (not defined $MinRange and
not defined $HourRange and
not defined $DayRange) {
$MinRange = 0;
$HourRange = 0;
$DayRange = 1; ## Default one day
}
if (not defined $DayRange) {
$DayRange = 0;
}
if (not defined $HourRange) {
$HourRange = 0;
}
if (not defined $MinRange) {
$MinRange = 0;
}
my $ptrUA = LWP::UserAgent->new;
$ptrUA->timeout(10);
$ptrUA->max_size(undef);
$ptrUA->show_progress(1);
my $jar_jar = HTTP::Cookies->new
(autosave => 1,
max_cookie_size => 4096,
max_cookies_per_domain => 5, );
$ptrUA->cookie_jar($jar_jar);
print "Connecting to $Host with $APIKey.\n";
print "Will download in XML any report that is younger then $DayRange day(s) / $HourRange hour(s) / $MinRange min(s)\n";
print "Get a list of networks\n";
my %hashItems;
$hashItems{'primary'} = "admin";
$hashItems{'secondary'} = "networks";
$hashItems{'action'} = 'returnnetworks';
$hashItems{'search_limit'} = "10000"; # AS many as possible
if ($MinRange != 0) {
$hashItems{'search_datelastscanned_value'} = "$MinRange";
$hashItems{'search_datelastscanned_type'} = 'minute';
}
if ($HourRange != 0) {
$hashItems{'search_datelastscanned_value'} = "$HourRange";
$hashItems{'search_datelastscanned_type'} = 'hour';
}
if ($DayRange != 0) {
$hashItems{'search_datelastscanned_value'} = "$DayRange";
$hashItems{'search_datelastscanned_type'} = 'day';
}
$hashItems{'apikey'} = $APIKey;
my $list_networks = $ptrUA->request( GET "$http://$Host/json.cgi?".buildURL(\%hashItems));
my $content = $list_networks->content;
#print STDERR "content: $content\n";
my %result;
eval {
my $ptrResult = decode_json($content);
if (defined $ptrResult) {
%result = %{$ptrResult};
}
};
if ($@) {
die("Malformed response");
}
my @arrayNetworks;
if (defined $result{'data'}) {
@arrayNetworks = @{$result{'data'}};
}
#print "arrayNetworks: ".Dumper(\@arrayNetworks);
print "Found ".(scalar @arrayNetworks)." network(s) to download their report\n";
my %hashNetworks;
foreach my $ptrNetwork (@arrayNetworks) {
my %Item = %{$ptrNetwork};
my $Network = $Item{'ID'};
print "Looking at network: $Network (".$Item{'Name'}.")\n";
my %hashItems;
$hashItems{'primary'} = 'vulnerabilities';
$hashItems{'secondary'} = "report";
$hashItems{'differential'} = '1';
$hashItems{'action'} = 'getreport';
$hashItems{'format'} = 'xml';
$hashItems{'network'} = "$Network";
$hashItems{'apikey'} = $APIKey;
my $jsonFile = $ptrUA->request( GET "$http://$Host/json.cgi?".buildURL(\%hashItems));
my $jsonFileContent = $jsonFile->content;
my %result;
my $ptrResult;
eval {
$ptrResult = decode_json($jsonFileContent);
};
if ($@) {
print STDERR "Malformed json received: $@\n";
print STDERR "stream size: ".length($jsonFileContent)."\n";
print STDERR "jsonFileContent: [$jsonFileContent]\n";
next;
}
if (defined $ptrResult) {
%result = %{$ptrResult};
}
#print STDERR "result: ".Dumper(\%result);
my $compresseddata = "";
if (defined $result{'compresseddata'}) {
$compresseddata = $result{'compresseddata'};
}
if (length($compresseddata) != $result{'compressedlength'}) {
print STDERR "Invalid compressed data has been received\n";
next;
}
my $uncompresseddata = uncompress(decode_base64($compresseddata));
if (length($uncompresseddata) != $result{'uncompressedlength'}) {
print STDERR "Invalid uncompressed data has been received (".length($uncompresseddata)." != ".$result{'uncompressedlength'}.")\n";
next;
}
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
#Before parsing the XML, tell the parser which elements will !Always! be an array, even if there are no siblings.
#Rule of thumb: Everything we later use in a 'foreach' or in folding must be in the @ForceArrays list
my @ForceArrays = ();
#Items that will be enumerated into a hash, and what string is the key
my @FoldArrays = ();
#Items that are over complicated can be simplified.
my @GroupTags = ();
#Construct a new XML::Simple object
my $xs = new XML::Simple (RootName => 'Results',
ForceArray => [@ForceArrays],
KeyAttr => {@FoldArrays},
GroupTags => {@GroupTags},
ParserOpts => [
ProtocolEncoding => "UTF-8",
], );
my $ref;
eval {
$ref = $xs->XMLin($uncompresseddata);
};
if ($@) {
print STDERR "Malformed xml found: [$uncompresseddata]\n";
print STDERR ($@);
next;
}
#print "ref: ".Dumper($ref);
my %XML;
if (defined $ref) {
%XML = %{$ref};
}
my @VulnerableHosts;
if (defined $XML{'VulnerableHosts'}) {
if (defined $XML{'VulnerableHosts'}{'VulnerableHost'}) {
@VulnerableHosts = @{$XML{'VulnerableHosts'}{'VulnerableHost'}};
}
}
if (scalar @VulnerableHosts == 0) {
print "No vulnerabilities to report\n";
}
foreach my $ptrVulnerableHost (@VulnerableHosts) {
my %VulnerableHost = %{$ptrVulnerableHost};
if ($VulnerableHost{'PreviousStatus'} eq 'New' and
$VulnerableHost{'CurrentStatus'} eq 'Existing') {
###
print "A new vulnerability\n";
if ($VulnerableHost{'Vulnerability'}{'RiskFactor'} >= 4) {
###
# Medium and High only
createIncident(\%VulnerableHost);
} else {
print "Severity too low\n";
}
}
}
}
###
#
sub createIncident
{
my $ptrItem = shift or return;
my %Item = %{$ptrItem};
#print STDERR Dumper(\%Item);
my %hashIncident = %TOPDeskItems;
if (ref $Item{'Vulnerability'}{'Solution'} eq '') {
$hashIncident{'value1'} = $Item{'Vulnerability'}{'Solution'};
}
$hashIncident{'value0'} = "Vulnerability found on: ".$Item{'Name'}." (".$Item{'StrPort'}.")\n";
$hashIncident{'value0'} .= "Link: https://$Host/?primary%3Dvulnerability%26secondary%3Ddetailed%26view%3Ddetails%26id%3D".$Item{'VulnID'}."\n";
$hashIncident{'value0'} .= ref $Item{'Vulnerability'}{'Summary'} eq '' ? url_encode($Item{'Vulnerability'}{'Summary'}) : url_encode($Item{'Vulnerability'}{'SummaryOriginal'});
my $incidentURL = $ptrUA->request( GET "$TOPDeskURL?".buildURL(\%hashIncident));
my $incidentURLContent = $incidentURL->content;
#print STDERR "incidentURLContent: [$incidentURLContent]\n";
#print $incidentURL;
return;
}
###
#
sub buildURL {
my $ptrHash = shift or return "";
my %Hash = %{$ptrHash};
my $returnvalue = "";
foreach my $strName (keys %Hash) {
if ($returnvalue ne '') {
$returnvalue .= "&";
}
$returnvalue .= "$strName=".$Hash{$strName};
}
return $returnvalue;
}
sub usage
{
print <<EOF;
$0 -H hostname -a APIKey <-d [days back]> <-h [hours back]> <-m [mins back]> <-t [filename type]>
Example(s):
1) Get from host 192.168.1.199 the XML reports as far back as 1 day:
$0 -H 192.168.1.199 -a '44829793-EF31-3F01-847B-5B85406FF170' -d 1
2) Get from host 192.168.1.199 the XML reports as far back as 59 minutes (above it use hour value):
$0 -H 192.168.1.199 -u '44829793-EF31-3F01-847B-5B85406FF170' -m 59
2) Get from host 192.168.1.199 the XML reports as far back as 23 hours (above it use day value):
$0 -H 192.168.1.199 -u '44829793-EF31-3F01-847B-5B85406FF170' -h 23
4) Get from host 192.168.1.199 the XML reports as far back as 1 day and the filename type will be an GID and ScanNumber:
$0 -H 192.168.1.199 -u '44829793-EF31-3F01-847B-5B85406FF170' -d 1 -t 1
5) Get from host 192.168.1.199 the XML reports as far back as 1 day and the filename type will be the Scan Name:
$0 -H 192.168.1.199 -u '44829793-EF31-3F01-847B-5B85406FF170' -d 1 -t 0
EOF
exit;
}
##
#
sub url_encode
{
my $string = shift;
$string =~ s/([^A-Za-z0-9\-\.])/sprintf ('%%%0.2x', ord ($1))/eg;
return $string;
}