Playing with the Tenable.sc API and PowerShell

Here I start a new series of posts where I’ll show how to work with the Tenable.sc API using PowerShell as the scripting language. There is an amazing Python library, pyTenable, which I recommend if you can use Python in your environment, but if you for whatever reason cannot use Python - I might know somebody with that problem πŸ˜‰ - or you just want to learn an alternative, this is the place!

I’ll start with the basics and then start expanding from there.

Login

First things first, how to login or get authenticated access to the API. I’ll show you 2 options for this, our old friends user and password, and API keys - this is the preferred method now by Tenable.

User and password

For this option I’m going to use an encrypted file with an AES key where I store the API user password for my Tenable.sc server.

You can use an encrypted file with no ‘key’ parameter as well, this will use you user credentials instead of the AES key. In this case, only the same user in the same machine can decrypt the file. If you want to fully automate API scripts, this is not the way.

The following steps are done only once, then we access the files from our scripts. We create the AES key and save it for later decryption:

$AESkey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESkey)
$AESkey | Out-File 'C:\creds\aeskey.key'

In the next step you’ll be prompted for your user password. It’s stored in an encrypted file - encrypt.xml. Remember to use a read only user if you don’t need to make changes through API.

Read-Host -AsSecureString | ConvertFrom-SecureString -key $AESkey | Out-File 'C:\creds\encrypt.xml'

Keep the AES key file safe, as it’s used to decrypt your password.

Build login credentials and get token

Now we prepare the credentials from the files above with a PSCredential object

$key = Get-Content 'C:\Users\myuser\Documents\04.Tenable.sc\scripts\creds\aeskey.key'
$username = "api_user"
$password = Get-Content 'C:\Users\myuser\Documents\04.Tenable.sc\scripts\creds\encrypt.xml' | ConvertTo-SecureString -Key $key
$MyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password

Next we build the body of the first request, that contains our user and password; we us a PSObject and then convert to JSON

$login = New-Object PSObject 
$login | Add-Member -MemberType NoteProperty -Name username -Value $MyCredential.UserName
$login | Add-Member -MemberType NoteProperty -Name password -Value $MyCredential.GetNetworkCredential().password
$Data = (ConvertTo-Json -Compress $login)

Last step is getting the authentication token and session variable that we’ll reuse from then on for authentication.

$tenableSC = "https://192.168.1.111"
$request = Invoke-WebRequest -Uri $tenableSC/rest/token -Method Post -Body $Data -UseBasicParsing -SessionVariable sv
$token = (ConvertFrom-Json $request.Content).response.token
Token

Now we can start making API requests with the $token and $sv variables like this, using Invoke-WebRequest commandlet.

$request = Invoke-WebRequest -Uri " $tenableSC/rest/API_endpoint " -Method Get -Headers @{"X-SecurityCenter"="$token"} -WebSession $sv

API Keys

The second option for authentication is API keys, which is now the way forward for Tenable. First we need to generate the access and secret keys for our user.

Login with the Security Manager user and go to Users menu, click in the action icon to the right of the user you want to use, then click on Gnerate API keys.

Options

We get 2 keys, access and secret, copy them because once you close the windows you won’t be able to see them again in the GUI.

Keys

Build login with API keys

I use environment variables to avoid having the API keys in plain text on the script; retrieve them like you see below. I also create the headers for authentication.

$accessKey = $env:TENABLE_ACCESS_KEY
$secretKey = $env:TENABLE_SECRET_KEY
$headers = @{}
$headers.Add("x-apikey", "accessKey=$accessKey;secretKey=$secretKey")

Now we are ready to use the commandlet Invoke-RestMethod to make API requests, for instance, let’s get all saved queries on our server:

$queries = Invoke-RestMethod -Uri $scURL/rest/query -Method Get -Headers $headers

Play time

Now that authentication is done, let’s play with the API. I will use a practical example with assets. Let’s say you have a lot of custom assets, static, dynamic and combination types. The dynamic ones need to be generated on the fly every time new data is added to repositories affecting your assets. The more you use your Tenable.sc, the more assets you’ll be creating that later on can become obsolete or replaced for newer ones, but you are afraid of deleting assets because you are not sure if a report or active scan might be using it and it will break if you delete the asset.

When you have too many dynamic assets, this can consume a lot of processing power in your server, regardless if you are really using those assets or not. To clean your asset list, you’d have to go over all your active scans and reports then take note of which assets you are still using. With the API, I will extract all assets in the server, all active scans and check for every asset if it’s being used in any scan. Then we get a list of assets not being used and that we can remove to reduce the processing cost on the server.

IMPORTANT: IF YOU USE COMBINATION ASSETS, DON’T USE THIS AS IT IS, AS YOU’LL PROBABLY MISS DYNAMIC ASSETS USED INSIDE THE COMBINATION ONES.If you only use Static and Dynamic assets, then you can use the following tips.

We start by getting the following fields from all assets: name, id, type(combination, dynamic, static)

$request = Invoke-WebRequest -Uri " $tenableSC/rest/asset?fields=name,id,type,typeFields " -Method Get -Headers @{"X-SecurityCenter"="$token"} -WebSession $sv
$assets = (ConvertFrom-Json $request.Content)
$assets = $assets.response.usable
Assets

Next we get the active scans currently configured with the following fields: name, id, assets

$request = Invoke-WebRequest -Uri " $tenableSC/rest/scan?fields=name,id,assets " -Method Get -Headers @{"X-SecurityCenter"="$token"} -WebSession $sv
$scans = (ConvertFrom-Json $request.Content)
$scans = $scans.response.usable
Scans

Now let’s see which Dynamic assets are being used by any scan; we go through all assets and for the Dynamic type check if they are assigned to any scan:

$inuse = @()
foreach ( $asset in $assets ) {
    if ($asset.type -eq "Dynamic") {
        if ($asset.id -in $scans.assets.id) {
            $inuse += $asset
        }
    }
}

We have saved all assets currently being used by any scan in our server in the variable $inuse Those are the ones we shouldn’t remove as it would break a scan.

Now we do the opposite, find all Dynamic assets that are not assigned to any scan:

$NOTinuse = @()
foreach ( $asset in $assets ) {
    if ($asset.type -eq "Dynamic")  {
        if ($asset.id -notin $scans.assets.id) {
            $NOTinuse += $asset
        }
    }
}

Now, all the assets in $NOTinuse are not being used by any scan, but they are using resources every time there is a change to recalculate their content. If we know that they are not used anywhere else, like reports - something we can easily check with the same approach with the API looking at reports endpoint -, we can delete them.

You can use this at your own responsibility - do a sanity check first to be sure the output is correct, then you could just just mass delete them with the following:

$NOTinuse = @()
foreach ($asset in $NOTinuse) {
    $request = Invoke-WebRequest -Uri " $tenableSC/rest/asset/$asset.id " -Method Delete -Headers @{"X-SecurityCenter"="$token"} -WebSession $sv
}

Conclusion

These are just some examples to give you an idea on how to operate with the Tenable.sc API. Next time I’ll show you how to access the Analysis tool which has a lot of uses as well, or how to automatically import scans from one network to another that is air gapped.

Stay tuned!

You can find all the code snippets from this post here.


See also