Using the Rapid7 InsightVM API with PowerShell

An explanation and some examples for connecting to Rapid7's InsightVM API (version 3) with PowerShell.

Screenshot of some code (described in the post) on a slant, and a screenshot of the result.

I've been using Rapid7 InsightVM to evaluate security risks and vulnerabilities on networks for several years.  Scans run regularly to check for problems and I then analyse the results.  At the moment I do that weekly, and log a ticket to provide an audit trail.

Some parts of what I do are always the same.  For example, I look at each "site" (an office or data centre) in InsightVM and record its risk score.  Then I look at the top risky assets for each site.  I track the risk trend (increasing or decreasing) and review the most risky vulnerabilities in the system.  Tickets then get raised for the relevant team to handle remediation.

Due to the repeated nature of this review I produced a template so each ticket followed the same format.  This week I then wrote a script to populate the values so that I can read the output and act accordingly.  In this blog post I'm going to give some examples of how to interact with the InsightVM API.

Note: I cannot provide a copy of the script as that was written on work time, and is property of my employer.

Warning

This post is semi technical, and assumes you have some experience of using PowerShell.  While you can copy the example scripts you will need to customise them to work in your environment.  This post will not fully explain all concepts.

Requirements

Probably obvious, but some requirements before we begin:

  • A Rapid7 InsightVM installation
  • A user account within InsightVM to use for API access
  • PowerShell (tested with version 7.3.4)

Key cmdlets

In PowerShell, a cmdlet is a command that you run and usually looks like a verb followed by a noun with a hyphen in between.  For example Write-Host or Get-Credential.  For this script our key cmdlets are:

  • Get-Credential to request a username and password
  • Write-Host for outputting text to our terminal
  • Invoke-RestMethod for connecting to the API

The InsightVM API

InsightVM's API is REST based meaning it responds differently based on the HTTP method used to make the request.  REST stands for REpresentational State Transfer and if you want to know more about it I suggest you consult Wikipedia.  HTTP methods are out of scope for this blog post, but as an example, a REST API would provide you information if you submitted information via an HTTP GET request.  If you submitted an HTTP POST request the REST API would take that information and save it (or perform an action).

There are many features available via InsightVM's API, including the ability to trigger scans, create sites, and retrieve data and it's retrieving data that we're interested in for this work.

API authentication

Unauthenticated access to the API is very limited, largely just providing help documentation.  In order to retrieve data or perform actions we have to authenticate, and while PowerShell handles this for us you might be interested to know how that works.  This API uses HTTP Basic Authentication, meaning we send an HTTP header called Authorization along with the type (basic) and our credentials as a base64 encoded version of username:password.  The resulting header would look like this (fake details):

Authorization: Basic ZnJlZDpwYXNzd29yZA==

Fortunately we don't need to build the header, or base64 encode our credentials, manually as Invoke-RestMethod will do that for us.

First, get credentials

Using Get-Credential we can prompt the user to enter a username and password.  I've opted to enter these details each time my script runs in order to avoid saving secrets in my script file.

The example below will prompt for a username and password:

$credentials = Get-Credential

Which looks like this:

PS C:\> $credentials = Get-Credential

PowerShell credential request
Enter your credentials.
User: [email protected]
Password for user [email protected]: ********************

PS C:\> 

Alternatively we can pre-set the username so we're only asked for the password:

PS C:\> $credentials = Get-Credential -UserName [email protected]

PowerShell credential request
Enter your credentials.
Password for user [email protected]: ********************

PS C:\> 

Note that in each case the password is masked on entry.  The variable $credentials now contains our details as a secure string.

Second, request a list of sites

Next we want to pull back a list of sites from the API, and we use Invoke-RestMethod to do this.  By default, this cmdlet will use the GET HTTP method, which is what we want in this scenario.

A list of sites can be obtained from the API's sites endpoint.  Let's grab a list of sites and store them in an object called $allSites:

PS C:\> $allSites = Invoke-RestMethod -Uri https://insightvm.example.org/api/3/sites -Authentication Basic -Credential $credentials

PS C:\> 

In the above we're using Invoke-RestMethod to make a connection to the API.  We provide the cmdlet the API endpoint's address via the -Uri argument.  Next we state our authentication type using -Authentication Basic and finally we pass in the credentials for the connection via -Credential $credentials.

After we've pressed enter we don't see any output (we get back a waiting PowerShell prompt) because the result from our API call is held in the $allSites object.

If we write the contents of $allSites to the screen it looks like this:

resources
---------
{@{assets=12; description=My main office; id=3; importance=high; lastScanTime=02/06/2023 13:12:11; links=System.Object[]; name=Jonsdocs HQ; riskScore=9000;...}

Processing $allSites

I'm sure we can all agree that reading the output like that isn't the most useful.  Instead we need to process this output and to do that we'll use a PowerShell foreach block.  foreach cycles through every element in an array (or item in an object) and performs an operation.

Let's use foreach to write the name, risk score, and number of assets for each site to screen.  We'll continue from where we left off, so we already have $allSites defined in memory.  Note that we don't need to type >>, that's just PowerShell's way of showing it's continuing on a new line.  As soon as we press enter after our closing brace (}) the foreach will run.

PS C:\> foreach ($site in $allSites.resources){
>> Write-Host -ForegroundColor yellow $site.name
>> Write-Host "Site has a risk score of: $($site.riskScore.ToString('N0'))"
>> Write-Host "Number of assets in site: $($site.assets)"
>> }
Jonsdocs HQ
Site has a risk score of: 9,000
Number of assets in site: 16
Field Office
Site has a risk score of: 1,103
Number of assets in site: 1

There's a few extra things to note here:

  • The information we want is inside the $allSites object but that's a multi-dimensional object.  Consider $allSites to be a big box with other boxes inside.  We're interested in the inner box called resources so our foreach works through $allSites.resources
  • Text to be output by Write-Host is enclosed in double quotes (").  When including variables these are incleded in the form $($site.assets) - the $( tells Write-Host to get the value rather than just writing the dollar symbol and the variable name
  • The site name will be in yellow, because we told Write-Host to have a -foregroundcolor yellow
  • Our numbers have a thousands seperator (comma) because we told the riskScore to be translated into a string with formatting using .ToString('N0')

As you can see, in this example it's not very pretty - there's no line space between each site, for example.  We could make the output easier to read, but I'll leave that as an exercise for the reader.

Example script - get all sites

Putting the above steps together into a script, we get:

# Capture credentials to use (will only prompt for the password)
$credentials = Get-Credential -UserName [email protected]

# Connect to the API sites endpoint to get a list of sites
$allSites = Invoke-RestMethod -Uri https://insightvm.example.org/api/3/sites -Authentication Basic -Credential $credentials

# Output details of each site, its risk score and number of assets
foreach ($site in $allSites.resources)
{
    Write-Host -ForegroundColor yellow $site.name
    Write-Host "Site has a risk score of: $($site.riskScore.ToString('N0'))"
    Write-Host "Number of assets in site: $($site.assets)"
}

What else could we do?

While having a list of all sites, their risk score and assets, is useful, we can do more.  One example would be to use the site assets API endpoint at /api/3/sites/<siteID>/assets with a site ID to pull back all the assets in that site.  Alternatively we could restrict that to only provide us the top five risky assets.  For example:

PS C:\> $riskyAssets = Invoke-RestMethod -Uri https://insightvm.example.org/api/3/sites/3/assets?sort=riskScore%2CDESC&size=5 -Authentication Basic -Credential $credentials

Because I've told the API to sort the assets by riskScore in descending order (assets?sort=riskScore%2CDESC), and limited the API to only giving me five results(&size=5) I know I'll only get the five assets with the worst risk score - a higher score is worse.

To be able to read the information sensibly we'd need to use a foreach again (this time on $riskyAssets.resources).

There are plenty more API endpoints that provide us more options, and I've only given a couple of examples here.  To find out what you can do I recommend looking at the API documentation, and leave further development to the reader.

Further reading


Banner image: Screenshot of the PowerShell code on a slant, with the output alongside it.  My artistic skills 😀️.