AVDS Integration with TOPdesk

The following script can be used to integrate TOPdesk with your AVDS 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' => 'persoonid',
 '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;
}
Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.
Powered by Zendesk