Giter Club home page Giter Club logo

pstenable's Introduction

PSTenable

A cross-platform PowerShell Module that uses the Tenable Security Center API.

Overview

Build Status PowerShell Gallery Version PowerShell Gallery Downloads

This is a PowerShell Module that functions as an API wrapper around Tenable Security Center's API version 5.10. PSTenable works with Windows PowerShell 5.1 and PowerShell 6. PSTenable automatically handles token refresh for you, specified in Tenable's documentation here.

Please open a Pull Request if you desire any new features or create an Issue if you come across a bug.

Installation

Install-Module -Name PSTenable -Scope CurrentUser -Repository PSGallery

Examples

## Cache credentials, tenable server, web session, and token.
$Credential = Get-Credential
Connect-PSTenable -Credential $Credential -TenableServer "http(s)://server.domain.com/rest" -Register

## Get all devices affected by CVE-2019-0708
Get-PSTenableCVE -CVE "CVE-2019-0708"

## Get all devices affected by plugin ID 125877
Get-PSTenablePlugin -ID "125877"

## Get all vulnerabilities related to patch family Windows, Windows : Microsoft Bulletins, and Windows : User management
Get-PSTenablePluginFamilyWindows

## Retrieves all non-info vulnerabilities, Detailed data
Get-PSTenableSeverity -Severity "All" -Detailed

## Retrieves high and medium vulnerabilities, up to 200 records, Summary Data
Get-PSTenableSeverity -Severity "High","Medium" -Maxrecords 200

pstenable's People

Contributors

aarong1234 avatar jwmoss avatar

Stargazers

 avatar

Watchers

 avatar  avatar

pstenable's Issues

Examples on Front page of Github has typo

Expected Behavior

Cache credentials, tenable server, web session, and token.

$Credential = Get-Credential
Connect-PSTenable -Credential $Credential -TenableServer "server.domain.com/rest" -Register

Should return nothing, (but have cached a tenable rest api token for future calls)

Current Behavior

PS H:> Connect-PSTenable -Credential $creds -TenableServer "securitycenter.xxxx.xxx/rest" -Register
Invoke-RestMethod : Unable to connect to the remote server
At \pnas02fs2\Home\XXXX\WindowsPowerShell\Modules\PSTenable\0.2.3\public\Connect-PSTenable.ps1:72 char:20

  •     $Session = Invoke-RestMethod @SessionSplat
    
  •                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : NotSpecified: (:) [Invoke-RestMethod], WebException
    • FullyQualifiedErrorId : System.Net.WebException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

Possible Solution

Change the Example to read

Cache credentials, tenable server, web session, and token.

$Credential = Get-Credential
Connect-PSTenable -Credential $Credential -TenableServer "https://server.domain.com/rest" -Register

OR

update code to prepend 'https://' if the tenable server argument does not have :// in it.

Steps to Reproduce (for bugs)

Single step, so just review the above unexpected result.

Context

I had to read your code ... I was traumatized... just kidding, Im satisfied, just caused frustration

Your Environment

  • Module version used: 0.2.3
  • Operating System and PowerShell version: Win10 1809 (17763.805), 5.1

Get-PSTenableSeverity seems to have a max records

Expected Behavior

$Critical = Get-PSTenableSeverity -Severity "critical"
$critical.count
24438

Current Behavior

$Critical = Get-PSTenableSeverity -Severity "critical"
$critical.count
5000

Possible Solution

Get-PSTenableSeverity has a hard coded cap in the query hashtable that builds the json
either stop putting any cap, or have maxRecords be an [int] argument and let the user choose.

Steps to Reproduce (for bugs)

Assuming your SC has more than 5000 critical vuln in the repository
1.if (!($creds)) {$creds = Get-Credential}
Connect-PSTenable -Credential $creds -TenableServer "https://securitycenter.xxxxx.xxxx/rest" -Register

  1. $Critical = Get-PSTenableSeverity -Severity "critical"
    $critical.count
    5000

Context

i have no way to use this module, and see the 5001-24000ish critical vulns i would like to see

Your Environment

  • Module version used: 0.2.3
  • Operating System and PowerShell version: Win 10, 5.1

'Invoke-PSTenableTokenStatus' is not recognized as the name of a cmdlet

Hello,

First of all, thank you very much for this project, looks very useful !

I tried to test the module, by running a Get-PSTenablePlugin, but it seems that powershell cannot find the cmdlet Invoke-PSTenableTokenStatus.
Seems other function like Get-PSTenableCVE -CVE "CVE-2019-0708" return the same error.

Expected Behavior

Should get the affected devices by a pluggin ID

Current Behavior

PS C:\WINDOWS\system32> Get-PSTenablePlugin -ID "119583"
Invoke-PSTenableTokenStatus : The term 'Invoke-PSTenableTokenStatus' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.
At C:\Users\xxx\Documents\WindowsPowerShell\Modules\PSTenable\0.2.1\public\Get-PSTenablePlugin.ps1:32 char:24
+         $TokenExpiry = Invoke-PSTenableTokenStatus
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Invoke-PSTenableTokenStatus:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Steps to Reproduce (for bugs)

  1. Install-Module -Name PSTenable -Scope CurrentUser -Repository PSGallery
  2. $Credential = Get-Credential
  3. Connect-PSTenable -Credential $Credential -TenableServer "xxx/rest" -Register
  4. Get-PSTenablePlugin -ID "119583"

Context

Your Environment

PS C:\WINDOWS\system32> $PSVersionTable.PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      16299  1146

Code Review

Invoke-PSTenableRest

TLS settings should be declared during begin, not process

Extract TLS settings into dedicated function, to avoid code duplication.

Invoke-PSTenableTokenStatus

Unsuitable use of Write-Output

Connect-PSTenable

Wrong check for $Register parameter: Should be truth content check
Otherwise this:

-Register:$false

Will cause the command to perform a register afterall!

Get-PSTenableAssetAnalysis

Duplicate type declaration in Parameter

$ComputerName parameter should be of type [PSFComputer] not [string]

No reason to not accept from pipeline and process multiple computers in one call

Get-PSTenableCVE

$CVE should be a [string[]] not a [string] type

Storing results, just so they can be returned in the end block is a bad design. Should emit them as they are reached at.

Get-PSTenablePlugin

$ID should be a [string[]] not a [string] type

Storing results, just so they can be returned in the end block is a bad design. Should emit them as they are reached at.

Get-PSTenablePluginFamilyWindows

Storing results, just so they can be returned in the end block is a bad design. Should emit them as they are reached at.

Get-PSTenableSeverity

Data retrieval should happen during process

Get-PSTenableWindowsServerJava

Data retrieval should happen during process

General PSFramework Stuff:

Configuration settings should be declared & Initialized when used in modules.

e.g. during module import, execute something like this:

Set-PSFconfig -Module PSTenable -Name Credential -Value $null -Initialize -Validation credential -Description 'The credentials used for authenticating to the Tenable server'

Get-PSTenableSeverity -Severity 'All with Info' doesn't return everything

Using the 'All with Info' option doesn't seem to return more than Critical findings. I moved $CurrentStartOffset = 0 inside the foreach loop to get this far. When it tries to filter for Info objects, the server returns a 500 error. Does this function correctly for you?

Expected Behavior

Returns 0-4 severity level information.

Current Behavior

$all = Get-PSTenableSeverity -Severity 'All with Info'
4 3 2 1 0
Running 4
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  2147483647,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "4",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  0,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 8384
CurrentStartOffset: 2147483647
Max records: 0
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  4294967294,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "4",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  2147483647,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 0
Running 3
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  2147483647,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "3",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  0,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 125810
CurrentStartOffset: 2147483647
Max records: 0
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  4294967294,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "3",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  2147483647,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 0
Running 2
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  2147483647,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "2",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  0,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 260503
CurrentStartOffset: 2147483647
Max records: 0
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  4294967294,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "2",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  2147483647,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 0
Running 1
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  2147483647,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "1",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  0,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 12831
CurrentStartOffset: 2147483647
Max records: 0
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  4294967294,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "1",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  2147483647,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Line Count: 0
Running 0
PreJSON:
{
    "sourceType":  "cumulative",
    "query":  {
                  "endOffset":  2147483647,
                  "filters":  [
                                  {
                                      "operator":  "=",
                                      "value":  "0",
                                      "filterName":  "severity"
                                  }
                              ],
                  "startOffset":  0,
                  "type":  "vuln",
                  "tool":  "listvuln"
              },
    "sortDir":  "DESC",
    "type":  "vuln",
    "sortField":  "basescore"
}

Invoke-RestMethod : The remote server returned an error: (500) Internal Server Error.
At C:\Users\*\Documents\WindowsPowerShell\Modules\pstenable\0.2.4\private\Invoke-PSTenableRest.ps1:65 char:9
+         Invoke-RestMethod @RestMethodParams
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

Possible Solution

Steps to Reproduce (for bugs)

My modified version of Get-PSTenableSeverity with some poor man's debugging:

function Get-PSTenableSeverity {
    <#
    .SYNOPSIS
        Retrieves all vulnerabilities that are Critical, High, Medium, or Low in Tenable.
    .DESCRIPTION
        This function provides a way to retrieve all vulnerabilities in Tenable that are Critical, High,
        Medium, or Low.  
        Revision 0.1, Sept 2019, jwmoss
        Revision 0.2, Nov 2019, aarong1234 
    .INPUTS
        None
    .PARAMETER Severity
        Option for any of "Critical", "High", "Medium", "Low", "All", "All with Info". Defaults to "Critical","High".  All with Info will get ALL vuln data
    .PARAMETER MaxRecords
        Option for maximum records (rows of data) that should be requested (as a throttle), Default is 0 (all records) [note: sorted by score, descending]
    .PARAMETER Detailed
        Option to enable detailed data. Defaults to Summary Data. To determine how detailed the resulting data is by querying Tenable.sc "Vulnerability Summary" versus "Vulnerability Detail"
    .OUTPUTS
        PSCustomObject
    .NOTES
        None
    .EXAMPLE
        Get-PSTenableSeverity -Severity "Critical"
        Retrieves all critical vulnerabilities, Summary Data [sorted by vprscore (note:Very OLD plugins dont have a VPR Scores)]
    .EXAMPLE
        Get-PSTenableSeverity -Detailed
        Retrieves all critical and high vulnerabilities, Detailed Data (return Tenable.sc Vulnerability Detail data instead of summary) [sorted by vprscore (note:Very OLD plugins dont have a VPR Scores)]
    .EXAMPLE 
        Get-PSTenableSeverity -Severity "High","Medium" -Maxrecords 200
        Retrieves high and medium vulnerabilities, up to 200 records, Summary Data [sorted by vprscore (note:Very OLD plugins dont have a VPR Scores)]
    .EXAMPLE
        Get-PSTenableSeverity -Severity "All" -Detailed
        Retrieves all non-info vulnerabilities, Detailed data [sorted by vprscore (note:Very OLD plugins dont have a VPR Scores)]
    #>
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $false)]
        [ValidateSet(
            'Critical',
            'High',
            'Medium',
            'Low',
            'Info',
            'All',
            'All with Info'

        )]
        [string[]]
        $Severity,
        
        [Parameter(Position = 1, Mandatory = $false)]
        [int]$MaxRecords = 0,

        [Parameter(Mandatory = $false)]
        [ValidateSet($true,$false)]
        [switch]
        $Detailed
        
    )

    begin {

        $TokenExpiry = Invoke-PSTenableTokenStatus
       # write-host $TokenExpiry
        if ($TokenExpiry -eq $True) {Invoke-PSTenableTokenRenewal}

        if (-not $Severity) {
            $ID = @("4","3") #Magic Numbers for Critical & High
        } elseif ($Severity -contains "All with Info") {
            $ID = @("4","3","2","1","0") #all but info
        } elseif ($Severity -contains "All") { #if $severity has both All and All with Info.. All with Info will take precedence
            $ID = @("4","3","2","1")
        } else { #if Severity has All or All with info, other values are ignored
            $ID = @() 
            switch ($Severity) {
                "Critical" { $ID += "4" }
                "High" { $ID += "3" }
                "Medium" { $ID += "2" }
                "Low" { $ID += "1" }
                "Info" { $ID += "0" }
            }
        }
        $ID = $ID | Sort-Object -Descending
				Write-host "$ID"
				#Write-host "$Severity"
    }

    process {
        $APIresults = @()
        #$CurrentStartOffset = 0

        $ID | ForEach-Object { #because of lack of useful sorting data (scores, time) within a severity in summary data... we are going to query severities in order
            $Sev = $_
            Write-host "Running $Sev"
            $CurrentStartOffset = 0

            Do {
                if ($MaxRecords -ne 0 -and $MaxRecords -lt ($CurrentStartOffset + 2147483647)) { 
                    $CurrentEndOffset = $MaxRecords
                } else {
                    $CurrentEndOffset = ($CurrentStartOffset + 2147483647)
                }

                $PreJSON = @{
                    "type"       = "vuln"
                    "sourceType" = "cumulative"
                    "sortField"    = "basescore"
                    "sortDir"      = "DESC"
                    "query"      = @{
                        "type"         = "vuln"
                        "startOffset"  = $CurrentStartOffset
                        "endOffset"    = $CurrentEndOffset
                        "tool"         = & {if ($Detailed) {"vulndetails"} else {"listvuln"}}
                    }
                }
                if ($ID) { 
                    $PreJSON.query.add("filters",[array]@{
                        "filterName"   = "severity"
                        "operator"     = "="
                        "value"        = "$Sev"
                    })
                } 
                
     						$zJSON = $(ConvertTo-Json $PreJSON -depth 5)
								Write-host "PreJSON:"
								$zJSON | Out-String | Write-Host
                $Splat = @{
                    Method   = "Post"
                    Body     = $(ConvertTo-Json $PreJSON -depth 5)
                    Endpoint = "/analysis"
                }
                #Note: initially I was paginating every 2000, but frankly it was super inefficient, now we paginate on sizeof(Int32) 
                $ThisResults = Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results
                $lcount = $ThisResults | out-string | measure -line
                $lcount = $lcount.lines
                Write-Host "Line Count: $lcount"
                if ($ThisResults) { #non zero records came back
                    $APIresults += $Thisresults
                    #move pagination line (if you it is ever hit)
                    $CurrentStartOffset = $CurrentEndOffset
                    Write-host "CurrentStartOffset: $CurrentStartOffSet"
                    Write-host "Max records: $Maxrecords"
                    if ($Maxrecords -and ($CurrentStartOffset -ge $MaxRecords)) {$ThisResults = $Null} # we don't need to loop anymore
                    #$ThisResults = $Null
                }
            } While ($ThisResults)
        }

        $APIresults | Sort-object vprscore,basescore -Descending

    }

    end {
    }
}

Question, NON issue

Do you know how to get the Vuln Text... from the API

in the web gui, if i am in Vuln Details, i can see the actual vuln
"expected value was this", "current value is this"

i am looking to get that text for each of my criticals
(some vuln data is useless without it, especially MS family patches that in addition to files versions require a registry setting... without that info, it appears the patch is not installed, but in fact it is the setting that is missing)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.