The following script can be used to integrate TOPdesk with your beSECURE solution, the script does a few things to preform this:



  1. Connects to the server using the API key you provide in the command line
  2. 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
  3. Requests a differential report for each of these scans
  4. 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;

}