Using the Rapid7 InsightVM API with PowerShell
An explanation and some examples for connecting to Rapid7's InsightVM API (version 3) with PowerShell.
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 passwordWrite-Host
for outputting text to our terminalInvoke-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 calledresources
so ourforeach
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$(
tellsWrite-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
- Rapid7 InsightVM API documentation
- PowerShell
Invoke-RestMethod
documentation - Representational State Transfer on Wikipedia
Banner image: Screenshot of the PowerShell code on a slant, with the output alongside it. My artistic skills 😀️.