Skip to content

Instantly share code, notes, and snippets.

@mbrownnycnyc
Created January 3, 2019 17:35
Show Gist options
  • Save mbrownnycnyc/d39027685b033090aec883d72472ca08 to your computer and use it in GitHub Desktop.
Save mbrownnycnyc/d39027685b033090aec883d72472ca08 to your computer and use it in GitHub Desktop.
powershell script for object comparison for Nexpose/InsightVM discovered open ports custom report. Didn't drink enough coffee during this one, and sprinted to the end. Updates to come: support for 2FA, would like to make more DRY.
<# https://help.rapid7.com/insightvm/en-us/api/index.html
-- open ports custom report --
SELECT da.ip_address, da.host_name, dos.name AS OS, dos.version AS os_version, das.port, dp.name AS protocol, ds.name AS service, dsf.name AS service_name, dsf.version AS service_version
FROM dim_asset_service das
JOIN dim_service ds USING (service_id)
JOIN dim_protocol dp USING (protocol_id)
JOIN dim_asset da USING (asset_id)
JOIN dim_operating_system dos USING (operating_system_id)
JOIN dim_service_fingerprint dsf USING (service_fingerprint_id)
ORDER BY da.ip_address, das.port
#>
function ignore-certificatevalidation {
#ignore SSL/TLS cert errors:
if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) {
$certCallback = @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class ServerCertificateValidationCallback
{
public static void Ignore()
{
if(ServicePointManager.ServerCertificateValidationCallback ==null)
{
ServicePointManager.ServerCertificateValidationCallback +=
delegate
(
Object obj,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors errors
)
{
return true;
};
}
}
}
"@
Add-Type $certCallback
}
[ServerCertificateValidationCallback]::Ignore()
}
function generate-nscauthheaders{
[cmdletbinding()]
param (
[string]$nschost,
[string]$nscusername,
[string]$nscpassword
)
#https://stackoverflow.com/a/27951845/843000
#build base64 encoded string
$authpair = "$($nscusername):$($nscpassword)"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authpair))
$basicAuthValue = "Basic $encodedCreds"
$Headers = @{
Authorization = $basicAuthValue
}
return $headers
}
function get-listofreports {
[cmdletbinding()]
param (
[string]$nschost,
$nscauthheader
)
$resource = "reports"
$uri = "https://$($nschost)/api/3/$($resource)"
$ret = Invoke-WebRequest -URI $URI -headers $nscauthheader -UseBasicParsing -method GET
# Extract data from response
$retdata = [object[]](convertfrom-json ($ret.Content)).resources
return $retdata
}
function run-allreportsbyid {
[cmdletbinding()]
param (
[string]$nschost,
$nscauthheader,
$reportids
)
$resource = "reports"
$uri = "https://$($nschost)/api/3/$($resource)"
$returndata = @()
foreach ($reportid in $reportids) {
$method = "generate"
$uri = $uri + "/$($reportid.id)/$($method)"
$postparams = $reportid
$data = (convertto-json -compress $postparams)
try {
$ret = (Invoke-WebRequest -URI $URI -headers $nscauthheader -UseBasicParsing -method POST -body $data)
} catch {
if ( $($_.exception.response.statuscode).value__ -eq 405 ) {
$optionscontent = Invoke-WebRequest -URI $URI -headers $nscauthheader -UseBasicParsing -method OPTIONS
$retoptions = [object[]](convertfrom-json ($optionscontent.content)).links
return $retoptions
}
}
# Extract data from response
$retdata = [object[]]( convertfrom-json ($ret.Content) ).links | where {$_.rel -ne "self" }
$returndata += $retdata
}
return $returndata
}
function download-reportbyid {
[cmdletbinding()]
param (
[string]$nschost,
$nscauthheader,
$reportinstancelink,
$downloadlocation,
$reportname
)
$uri = $reportinstancelink
$method = "output"
$uri = $uri + "/$($method)"
$reportname = $reportnames -replace " ","_"
#https://lazywinadmin.com/2015/08/powershell-remove-special-characters.html
$reportname = $reportname -replace '[^\p{L}\p{Nd}/_]', ''
$isolatedreportindex = ($reportinstancelink -split "/")[-1]
# or the manly way: ($reportinstancelink.href) -match ".*\/(?!.*\/)(.*)" | out-null; $matches[1]
$reportname = $reportname + "_" + $isolatedreportindex
$downloadtarget = $downloadlocation + "\" + (get-date).tostring("yyyyMMdd_HHmmmss") + "-" + $reportname + ".xml"
try {
Invoke-WebRequest -verbose -URI $URI -headers $nscauthheader -outfile $downloadtarget -UseBasicParsing -method GET
} catch {
$downloadtarget = $_.exception
}
return $downloadtarget
}
function getdiffsin-XMLs {
[cmdletbinding()]
param (
$xmlfilenames
)
$nscxmlfilesinfo = @()
#build new data object to perform comparisons
foreach ($xmlfilename in $xmlfilenames) {
$xmlcontent = [xml](get-content "$($xmlfilename.fullname)")
#each file info object should contain info:
$xmlfileinfo = "" | select filename, nodesdata
$xmlfileinfo.filename = $xmlfilename
#each node info object should contain info:
$nodesdataobj = "" | select addresses, nodesinfo
$nodesdataobj.addresses = $xmlcontent.NexposeReport.nodes.node | select address
$nodesinfo = @()
foreach ($address in $nodesdataobj.addresses) {
#each nodesdata record should contain the nodes and a node info record structured as follows:
$tempobj= "" | select address, endpoints
$tempobj.address = $address.address
$tempobj.endpoints = ( ($xmlcontent.NexposeReport.nodes.node) | ? { ($_.address) -eq $address.address } ).endpoints.endpoint
$nodesinfo += $tempobj
}
$nodesdataobj.nodesinfo = $nodesinfo
$xmlfileinfo.nodesdata = $nodesdataobj
$nscxmlfilesinfo += $xmlfileinfo
}
#comparisons:
#questions to answer:
#what changes in addresses?
#what changes in open ports?
#note that $nscxmlfilesinfo[1] should always be _latest, so this is the perspective we're rendering resultant info
$olderfileinfo = $nscxmlfilesinfo[0]
$currentfileinfo = $nscxmlfilesinfo[1]
#addresses
$addresscomp = compare-object $olderfileinfo.nodesdata.addresses $currentfileinfo.nodesdata.addresses
$addressstatus = @()
foreach ($addresscompinfo in $addresscomp) {
$tempobj = "" | select address, addedorremoved
$tempobj.address = ($addresscompinfo | select address).address
if ($addresscompinfo.sideindicator -eq "<=" ) {
$tempobj.addedorremoved = "removed from ($currentfileinfo.filename).name"
} else {
$tempobj.addedorremoved = "added to $(($currentfileinfo.filename).name)"
}
$addressstatus += $tempobj
}
#endpoints
#obtain all data per node per file
$olderfileinfoendpoints = @()
foreach ($item in $olderfileinfo.nodesdata.nodesinfo) {
foreach ($endpoint in $item.endpoints) {
$tempobj = "" | select combined, address, ep_protocol, ep_port, ep_status, ep_service_name
$tempobj.combined = $item.address + "," + $endpoint.protocol + "," + $endpoint.port + "," + $endpoint.status + "," + $endpoint.services.service.name
$tempobj.address = $item.address
$tempobj.ep_protocol = $endpoint.protocol
$tempobj.ep_port = $endpoint.port
$tempobj.ep_status = $endpoint.status
$tempobj.ep_service_name = $endpoint.services.service.name
$olderfileinfoendpoints += $tempobj
}
}
$currentfileinfoendpoints = @()
foreach ($item in $currentfileinfo.nodesdata.nodesinfo) {
foreach ($endpoint in $item.endpoints) {
$tempobj = "" | select combined, address, ep_protocol, ep_port, ep_status, ep_service_name
$tempobj.combined = $item.address + "," + $endpoint.protocol + "," + $endpoint.port + "," + $endpoint.status + "," + $endpoint.services.service.name
$tempobj.address = $item.address
$tempobj.ep_protocol = $endpoint.protocol
$tempobj.ep_port = $endpoint.port
$tempobj.ep_status = $endpoint.status
$tempobj.ep_service_name = $endpoint.services.service.name
$currentfileinfoendpoints += $tempobj
}
}
$endpointcomp = compare-object $olderfileinfoendpoints.combined $currentfileinfoendpoints.combined
$endpointstatus = @()
foreach ($endpointcompinfo in $endpointcomp) {
$tempobj = "" | select endpoint, addedorremoved
$tempobj.endpoint = $endpointcompinfo.inputobject
if ($endpointcompinfo.sideindicator -eq "<=" ) {
$tempobj.addedorremoved = "removed from $(($currentfileinfo.filename).name)"
} else {
$tempobj.addedorremoved = "added to $(($currentfileinfo.filename).name)"
}
$endpointstatus += $tempobj
}
# $endpointstatus contains all info needed, but $addressstatus contains just the addresses
if ( $endpointstatus.endpoint.count -gt 0 ) {
return $endpointstatus
} else {
return "No changes"
}
}
function main {
$nschost = "localhost:3780"
$nscusername = "admin"
$nscpassword = "XXXXXX"
$reportnames = "Available ports report*"
$downloadlocation = "C:\Users\Matt\docs\Nexpose-InsightVM\reports"
ignore-certificatevalidation
$nscauthheader = generate-nscauthheaders -nschost $nschost -nscusername $nscusername -nscpassword $nscpassword
#find all report IDs
$listofreportids = get-listofreports -verbose -nschost $nschost -nscauthheader $nscauthheader | where {$_.name -like $reportnames} | select id
#run all reports and get instance uris
$reportinstancelinks = run-allreportsbyid -verbose -nschost $nschost -nscauthheader $nscauthheader -reportids $listofreportids
#find the links for the latest links and find the links for the previous run
$reportinstancedownloadlinks = @()
foreach ($reportinstancelink in $reportinstancelinks) {
if ($reportinstancelink.rel -eq "Report Instance") {
#find the base link
$baselink = $($reportinstancelink.href).substring(0,$($reportinstancelink.href).indexof("/history/") + "/history/".length)
#populate with latest link
$reportinstancedownloadlinks += $baselink + "latest"
#populate with previous link
$penultimatereportindex = $(($reportinstancelink.href -split "/")[-1]) -1
$reportinstancedownloadlinks += $baselink + $penultimatereportindex
}
}
#download all required reports
foreach ($reportinstancedownloadlink in $reportinstancedownloadlinks) {
download-reportbyid -verbose -nscauthheader $nscauthheader -reportinstancelink $reportinstancedownloadlink -downloadlocation $downloadlocation -reportname $reportnames
}
#get diffs in XMLs contents
$xmlfilenames = dir -path $downloadlocation | sort lastwritetime | select -first 2
$xmlchanges = getdiffsin-XMLs -xmlfilenames $xmlfilenames
#produce a report on the changes
if ($xmlchanges -ne "No Changes") {
produce-nscxmlchangereport -xmlchanges $xmlchanges
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment