From 70efe0bde19a4d8295f20e3c95a9df740bb6705f Mon Sep 17 00:00:00 2001 From: Jonathan Moss Date: Sun, 1 Sep 2019 16:15:30 -0400 Subject: [PATCH 01/18] !deploy code cleanup --- CHANGELOG.md | 4 ++++ PSTenable/PSTenable.psd1 | 2 +- PSTenable/PSTenable.psm1 | 6 ++++++ PSTenable/Public/Connect-PSTenable.ps1 | 2 +- PSTenable/Public/Get-PSTenableAssetAnalysis.ps1 | 3 +-- PSTenable/Public/Get-PSTenableCVE.ps1 | 6 +++--- PSTenable/Public/Get-PSTenablePluginFamilyWindows.ps1 | 4 ++-- PSTenable/Public/Get-PSTenableSeverity.ps1 | 4 +++- PSTenable/Public/Get-PSTenableWindowsServerJava.ps1 | 4 +++- 9 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dba5d8f..f58efc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [0.2.3] 2019-09-01 + +- Code cleanup [#9](https://github.com/jwmoss/PSTenable/issues/9) + ## [0.2.2] 2019-07-13 - Fixed automatically retrieving token. diff --git a/PSTenable/PSTenable.psd1 b/PSTenable/PSTenable.psd1 index 4db917e..121e57c 100644 --- a/PSTenable/PSTenable.psd1 +++ b/PSTenable/PSTenable.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSTenable.psm1' # Version number of this module. - ModuleVersion = '0.2.2' + ModuleVersion = '0.2.3' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/PSTenable/PSTenable.psm1 b/PSTenable/PSTenable.psm1 index 4a0f27e..35bd367 100644 --- a/PSTenable/PSTenable.psm1 +++ b/PSTenable/PSTenable.psm1 @@ -1,6 +1,12 @@ # Dot source public/private functions $public = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'public/*.ps1') -Recurse -ErrorAction Stop) $private = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'private/*.ps1') -Recurse -ErrorAction Stop) + +Set-PSFconfig -Module PSTenable -Name Credential -Value $null -Initialize -Validation credential -Description "The credentials used for authenticating to the Tenable server" +Set-PSFconfig -Module PSTenalbe -Name WebSession -Value $null -Initialize -Description "WebSession for Invoke Rest Method" +Set-PSFconfig -Module PSTenalbe -Name Token -Value $null -Initialize -Validation string -Description "Token used with Tenable" +Set-PSFconfig -Module PSTenalbe -Name Server -Value $null -Initialize -Validation string -Description "REST Tenable endpoint for Tenable Server" + foreach ($import in @($public + $private)) { try { . $import.FullName diff --git a/PSTenable/Public/Connect-PSTenable.ps1 b/PSTenable/Public/Connect-PSTenable.ps1 index 6809e3d..5a8437e 100755 --- a/PSTenable/Public/Connect-PSTenable.ps1 +++ b/PSTenable/Public/Connect-PSTenable.ps1 @@ -82,7 +82,7 @@ function Connect-PSTenable { Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer Set-PSFconfig -FullName "PSTenable.Credential" -Value $Credential - if ($PSBoundParameters.ContainsKey('Register')) { + if ($Register -eq $true) { Register-PSFConfig -FullName "PSTenable.WebSession" Register-PSFConfig -FullName "PSTenable.Token" Register-PSFConfig -FullName "PSTenable.Server" diff --git a/PSTenable/Public/Get-PSTenableAssetAnalysis.ps1 b/PSTenable/Public/Get-PSTenableAssetAnalysis.ps1 index 4cd994c..145fc88 100755 --- a/PSTenable/Public/Get-PSTenableAssetAnalysis.ps1 +++ b/PSTenable/Public/Get-PSTenableAssetAnalysis.ps1 @@ -18,9 +18,8 @@ function Get-PSTenableAssetAnalysis { #> [CmdletBinding()] param ( - [String] [Parameter(Position = 0, Mandatory = $true)] - [string] + [PSFComputer] $ComputerName ) diff --git a/PSTenable/Public/Get-PSTenableCVE.ps1 b/PSTenable/Public/Get-PSTenableCVE.ps1 index 7836370..da15101 100755 --- a/PSTenable/Public/Get-PSTenableCVE.ps1 +++ b/PSTenable/Public/Get-PSTenableCVE.ps1 @@ -24,7 +24,7 @@ function Get-PSTenableCVE { [parameter(Position = 0, mandatory = $true, ValueFromPipeline = $true)] - [string] + [string[]] $CVE ) @@ -74,13 +74,13 @@ function Get-PSTenableCVE { } ## Get the pluginID and then call Get-TenablePlugin, and then output those results - $Results = Foreach ($Plugin in $output.response.results.pluginid) { + Foreach ($Plugin in $output.response.results.pluginid) { Get-PSTenablePlugin -ID $Plugin } } end { - $Results + } } diff --git a/PSTenable/Public/Get-PSTenablePluginFamilyWindows.ps1 b/PSTenable/Public/Get-PSTenablePluginFamilyWindows.ps1 index 5e027a5..834107c 100755 --- a/PSTenable/Public/Get-PSTenablePluginFamilyWindows.ps1 +++ b/PSTenable/Public/Get-PSTenablePluginFamilyWindows.ps1 @@ -34,7 +34,7 @@ function Get-PSTenablePluginFamilyWindows { '29' ) - $Output = Foreach ($plugin in $WindowsPlugins) { + Foreach ($plugin in $WindowsPlugins) { $query = @{ "tool" = "vulnipdetail" @@ -82,6 +82,6 @@ function Get-PSTenablePluginFamilyWindows { } end { - $output + } } diff --git a/PSTenable/Public/Get-PSTenableSeverity.ps1 b/PSTenable/Public/Get-PSTenableSeverity.ps1 index ac2679b..67a6769 100755 --- a/PSTenable/Public/Get-PSTenableSeverity.ps1 +++ b/PSTenable/Public/Get-PSTenableSeverity.ps1 @@ -84,9 +84,11 @@ function Get-PSTenableSeverity { Body = $(ConvertTo-Json $query -depth 5) Endpoint = "/analysis" } + + Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results + } end { - Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results } } diff --git a/PSTenable/Public/Get-PSTenableWindowsServerJava.ps1 b/PSTenable/Public/Get-PSTenableWindowsServerJava.ps1 index abe62ff..264351f 100644 --- a/PSTenable/Public/Get-PSTenableWindowsServerJava.ps1 +++ b/PSTenable/Public/Get-PSTenableWindowsServerJava.ps1 @@ -85,10 +85,12 @@ function Get-PSTenableWindowsServerJava { Body = $(ConvertTo-Json $query -depth 50) Endpoint = "/analysis" } + + Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results + } End { - Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results } } From 74560d311db50cbf7ee9a9341d630e18ea46b965 Mon Sep 17 00:00:00 2001 From: jwmoss Date: Thu, 14 Nov 2019 14:17:29 -0500 Subject: [PATCH 02/18] Put http:// in example Derp. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e76fb4..a71a69c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Install-Module -Name PSTenable -Scope CurrentUser -Repository PSGallery ```powershell ## Cache credentials, tenable server, web session, and token. $Credential = Get-Credential -Connect-PSTenable -Credential $Credential -TenableServer "server.domain.com/rest" -Register +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" From 4305374e14d26c17e3b8093d31db61a51744370d Mon Sep 17 00:00:00 2001 From: AaronG1234 <52173294+AaronG1234@users.noreply.github.com> Date: Mon, 18 Nov 2019 20:45:08 -0700 Subject: [PATCH 03/18] cleaned up your hash table, changed to vulndetails, added pagination and max records (#15) * Update Get-PSTenableSeverity.ps1 updated ability for maxrecords and multiple severities * Update Get-PSTenableSeverity.ps1 * Update Get-PSTenableSeverity.ps1 * Update Get-PSTenableSeverity.ps1 --- PSTenable/Public/Get-PSTenableSeverity.ps1 | 150 ++++++++++++++------- 1 file changed, 99 insertions(+), 51 deletions(-) diff --git a/PSTenable/Public/Get-PSTenableSeverity.ps1 b/PSTenable/Public/Get-PSTenableSeverity.ps1 index 67a6769..42cbef6 100755 --- a/PSTenable/Public/Get-PSTenableSeverity.ps1 +++ b/PSTenable/Public/Get-PSTenableSeverity.ps1 @@ -4,30 +4,57 @@ function Get-PSTenableSeverity { 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, - Meidum, or Low. - .EXAMPLE - PS C:\> Get-PSTenableSeverity -Severity "Critical" - Retrieves all criitcal vulnerabilities. + Medium, or Low. + Revision 0.1, Sept 2019, jwmoss + Revision 0.2, Nov 2019, aarong1234 .INPUTS None .PARAMETER Severity - Option for Critical, High, Medium or Low. + 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 - None + 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 = $true)] + [Parameter(Position = 0, Mandatory = $false)] [ValidateSet( 'Critical', 'High', 'Medium', - 'Low' + 'Low', + 'All', + 'All with Info' + )] - [string] - $Severity + [string[]] + $Severity, + + [Parameter(Position = 1, Mandatory = $false)] + [int]$MaxRecords = 0, + + [Parameter(Mandatory = $false)] + [ValidateSet($true,$false)] + [switch] + $Detailed + ) begin { @@ -35,57 +62,78 @@ function Get-PSTenableSeverity { $TokenExpiry = Invoke-PSTenableTokenStatus if ($TokenExpiry -eq $True) {Invoke-PSTenableTokenRenewal} - switch ($Severity) { - "Critical" { $ID = "4" } - "High" { $ID = "3" } - "Medium" { $ID = "2" } - "Low" { $ID = "1" } + 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" } + } } + $ID = $ID | Sort-Object -Descending } process { + $APIresults = @() + $CurrentStartOffset = 0 - $query = @{ - "tool" = "vulnipdetail" - "sortField" = "cveID" - "sortDir" = "ASC" - "type" = "vuln" - "sourceType" = "cumulative" - "query" = @{ - "name" = "" - "description" = "" - "context" = "" - "status" = "-1" - "createdTime" = 0 - "modifiedtime" = 0 - "sourceType" = "cumulative" - "sortDir" = "desc" - "tool" = "listvuln" - "groups" = "[]" - "type" = "vuln" - "startOffset" = 0 - "endOffset" = 5000 - "filters" = [array]@{ - "id" = "severity" - "filterName" = "severity" - "operator" = "=" - "type" = "vuln" - "ispredefined" = $true - "value" = "$ID" + $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 = $_ + + Do { + if ($MaxRecords -ne 0 -and $MaxRecords -lt ($CurrentStartOffset + 2147483647)) { + $CurrentEndOffset = $MaxRecords + } else { + $CurrentEndOffset = ($CurrentStartOffset + 2147483647) } - "vulntool" = "listvuln" - "sortField" = "severity" - } - } - $Splat = @{ - Method = "Post" - Body = $(ConvertTo-Json $query -depth 5) - Endpoint = "/analysis" + $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" + }) + } + + + + $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 + if ($ThisResults) { #non zero records came back + $APIresults += $Thisresults + #move pagination line (if you it is ever hit) + $CurrentStartOffset = $CurrentEndOffset + if ($Maxrecords -and ($CurrentStartOffset -ge $MaxRecords)) {$ThisResults = $Null} # we don't need to loop anymore + } + } While ($ThisResults) } - Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results + $APIresults | Sort-object vprscore,basescore -Descending } From d26d7446b7eed8c7ab55100d3b9e5449bba4717b Mon Sep 17 00:00:00 2001 From: Jonathan Moss Date: Mon, 18 Nov 2019 22:51:08 -0500 Subject: [PATCH 04/18] !deploy --- CHANGELOG.md | 4 ++++ PSTenable/PSTenable.psd1 | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f58efc7..f63f4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [0.2.4] 2019-11-18 + +- Add pagination support, add support for more than 5000 records, and code cleanup for Get-PSTenableSeverity. Thanks [@AaronG1234](https://github.com/AaronG1234)! [#12](https://github.com/jwmoss/PSTenable/issues/12) + ## [0.2.3] 2019-09-01 - Code cleanup [#9](https://github.com/jwmoss/PSTenable/issues/9) diff --git a/PSTenable/PSTenable.psd1 b/PSTenable/PSTenable.psd1 index 121e57c..b6c6196 100644 --- a/PSTenable/PSTenable.psd1 +++ b/PSTenable/PSTenable.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSTenable.psm1' # Version number of this module. - ModuleVersion = '0.2.3' + ModuleVersion = '0.2.4' # Supported PSEditions # CompatiblePSEditions = @() From b83d7f1ec475b5f00726426a8a5f3abfd380bd10 Mon Sep 17 00:00:00 2001 From: Jonathan Moss Date: Mon, 18 Nov 2019 22:59:52 -0500 Subject: [PATCH 05/18] Add examples of retreiving vulnerabilties --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a71a69c..f81fe92 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,10 @@ 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 ``` From b20c7b58fa10e2e3f104edcf941840320c0e98fa Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 12:57:30 -0500 Subject: [PATCH 06/18] Update to the connect-pstenable to include Apikey --- PSTenable/Public/Connect-PSTenable.ps1 | 98 +++++++++++++++++--------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/PSTenable/Public/Connect-PSTenable.ps1 b/PSTenable/Public/Connect-PSTenable.ps1 index 5a8437e..cfacdd5 100755 --- a/PSTenable/Public/Connect-PSTenable.ps1 +++ b/PSTenable/Public/Connect-PSTenable.ps1 @@ -15,6 +15,8 @@ function Connect-PSTenable { Tenable Server Name, tenable.domain.com/rest .PARAMETER Register If specified, this will cache the Credential, TenableServer, Token, and Web Session. + .PARAMETER ApiKey + If specfied PSCredential Object will be treated as a API Key. .INPUTS None .OUTPUTS @@ -35,6 +37,10 @@ function Connect-PSTenable { [Parameter(Position = 2, mandatory = $false)] [switch] + $ApiKey, + + [Parameter(Position = 3, mandatory = $false)] + [switch] $Register ) begin { @@ -43,50 +49,72 @@ function Connect-PSTenable { process { - # Credentials - $APICredential = @{ - username = $Credential.UserName - password = $Credential.GetNetworkCredential().Password - releaseSession = "FALSE" - } + if($ApiKey){ - $SessionSplat = @{ - URI = "$TenableServer/token" - SessionVariable = "SCSession" - Method = "Post" - ContentType = "application/json" - Body = (ConvertTo-Json $APICredential) - ErrorAction = "Stop" - ErrorVariable = "TenableTokenError" - } + $accesskey = $Credential.UserName + $secretkey = $Credential.GetNetworkCredential().Password - $currentProgressPref = $ProgressPreference - $ProgressPreference = "SilentlyContinue" - $currentVersionTls = [Net.ServicePointManager]::SecurityProtocol - $currentSupportableTls = [Math]::Max($currentVersionTls.value__, [Net.SecurityProtocolType]::Tls.value__) - $availableTls = [enum]::GetValues('Net.SecurityProtocolType') | Where-Object { $_ -gt $currentSupportableTls } - $availableTls | ForEach-Object { - [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $_ - } + }Else{ + + # Credentials + $APICredential = @{ + username = $Credential.UserName + password = $Credential.GetNetworkCredential().Password + releaseSession = "FALSE" + } - $Session = Invoke-RestMethod @SessionSplat + $SessionSplat = @{ + URI = "$TenableServer/token" + SessionVariable = "SCSession" + Method = "Post" + ContentType = "application/json" + Body = (ConvertTo-Json $APICredential) + ErrorAction = "Stop" + ErrorVariable = "TenableTokenError" + } - [Net.ServicePointManager]::SecurityProtocol = $currentVersionTls - $ProgressPreference = $currentProgressPref + $currentProgressPref = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + $currentVersionTls = [Net.ServicePointManager]::SecurityProtocol + $currentSupportableTls = [Math]::Max($currentVersionTls.value__, [Net.SecurityProtocolType]::Tls.value__) + $availableTls = [enum]::GetValues('Net.SecurityProtocolType') | Where-Object { $_ -gt $currentSupportableTls } + $availableTls | ForEach-Object { + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $_ + } + + $Session = Invoke-RestMethod @SessionSplat + + [Net.ServicePointManager]::SecurityProtocol = $currentVersionTls + $ProgressPreference = $currentProgressPref + } } end { - Set-PSFconfig -FullName "PSTenable.WebSession" -Value $SCSession - Set-PSFconfig -FullName "PSTenable.Token" -Value $Session.response.token - Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer - Set-PSFconfig -FullName "PSTenable.Credential" -Value $Credential + if($ApiKey){ + + Set-PSFconfig -FullName "PSTenable.accesskey" -Value $accesskey + Set-PSFconfig -FullName "PSTenable.secretkey" -Value $secretkey + Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer + + if ($Register -eq $true) { + Register-PSFConfig -FullName "PSTenable.accesskey" + Register-PSFConfig -FullName "PSTenable.secretkey" + Register-PSFConfig -FullName "PSTenable.Server" + } + + }Else{ + Set-PSFconfig -FullName "PSTenable.WebSession" -Value $SCSession + Set-PSFconfig -FullName "PSTenable.Token" -Value $Session.response.token + Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer + Set-PSFconfig -FullName "PSTenable.Credential" -Value $Credential - if ($Register -eq $true) { - Register-PSFConfig -FullName "PSTenable.WebSession" - Register-PSFConfig -FullName "PSTenable.Token" - Register-PSFConfig -FullName "PSTenable.Server" - Register-PSFConfig -FullName "PSTenable.Token" + if ($Register -eq $true) { + Register-PSFConfig -FullName "PSTenable.WebSession" + Register-PSFConfig -FullName "PSTenable.Token" + Register-PSFConfig -FullName "PSTenable.Server" + Register-PSFConfig -FullName "PSTenable.Token" + } } } } From 1e99c44beb530618db828af1a9b0fc4c37ec93a8 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 15:16:26 -0500 Subject: [PATCH 07/18] Update Invoke-PSTenable.ps1 with API Key logic --- PSTenable/Private/Invoke-PSTenableRest.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/PSTenable/Private/Invoke-PSTenableRest.ps1 b/PSTenable/Private/Invoke-PSTenableRest.ps1 index 66a08d1..fc5aea4 100644 --- a/PSTenable/Private/Invoke-PSTenableRest.ps1 +++ b/PSTenable/Private/Invoke-PSTenableRest.ps1 @@ -36,10 +36,20 @@ function Invoke-PSTenableRest { Begin { + if((Get-PSFConfigValue -FullName "PSTenable.ApiKey" )){ + #Check for API Key + $headers = @{ + "x-apikey" = accesskey=$(Get-PSFConfigValue -FullName 'PSTenable.accesskey'); + secretkey=$(Get-PSFConfigValue -FullName 'PSTenable.secretkey');} + + }else{ + $headers = @{"X-SecurityCenter" = $(Get-PSFConfigValue -FullName 'PSTenable.Token') } + } + $RestMethodParams = @{ URI = $(Get-PSFConfigValue -FullName 'PSTenable.Server') + $Endpoint Method = $Method - Headers = @{"X-SecurityCenter" = $(Get-PSFConfigValue -FullName 'PSTenable.Token') } + Headers = $headers ContentType = "application/json" ErrorAction = "Stop" WebSession = $(Get-PSFConfigValue -FullName "PSTenable.WebSession") From 8a19522fe48bfac39fb56c2340eb6154f335e1b5 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 15:27:26 -0500 Subject: [PATCH 08/18] Update Private\Invoke-PSTenableTokenStatus.ps1 for APIKey logic --- .../Private/Invoke-PSTenableTokenStatus.ps1 | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 index 3385c53..ab13150 100644 --- a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 +++ b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 @@ -19,28 +19,35 @@ Function Invoke-PSTenableTokenStatus { ) - # Credentials - $APICredential = @{ - username = (Get-PSFConfigValue -FullName 'PSTenable.Credential').UserName - password = (Get-PSFConfigValue -FullName 'PSTenable.Credential').GetNetworkCredential().Password - releaseSession = "FALSE" - } + $apiKeyStatus = Get-PSFConfigValue -FullName "PSTenable.ApiKey" - $SessionSplat = @{ - URI = "$(Get-PSFConfigValue -FullName 'PSTenable.Server')/token" - SessionVariable = "SCSession" - Method = "Post" - ContentType = "application/json" - Body = (ConvertTo-Json $APICredential) - ErrorAction = "Stop" - } + if($apiKeyStatus -eq $false){ + + # Credentials + $APICredential = @{ + username = (Get-PSFConfigValue -FullName 'PSTenable.Credential').UserName + password = (Get-PSFConfigValue -FullName 'PSTenable.Credential').GetNetworkCredential().Password + releaseSession = "FALSE" + } + + $SessionSplat = @{ + URI = "$(Get-PSFConfigValue -FullName 'PSTenable.Server')/token" + SessionVariable = "SCSession" + Method = "Post" + ContentType = "application/json" + Body = (ConvertTo-Json $APICredential) + ErrorAction = "Stop" + } - $Session = Invoke-RestMethod @SessionSplat + $Session = Invoke-RestMethod @SessionSplat - if ($Session.response.releaseSession -eq $true) { - Write-Output $true - } else { - Write-Output $false + if ($Session.response.releaseSession -eq $true) { + Write-Output $true + } else { + Write-Output $false + } + }else{ + Write-Output $apiKeyStatus } } From 25e44cbc62033c098630c2c66a0a0ed092dfd53c Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 15:35:49 -0500 Subject: [PATCH 09/18] Add ApiKey value to PSFConfig --- PSTenable/Public/Connect-PSTenable.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PSTenable/Public/Connect-PSTenable.ps1 b/PSTenable/Public/Connect-PSTenable.ps1 index cfacdd5..876130c 100755 --- a/PSTenable/Public/Connect-PSTenable.ps1 +++ b/PSTenable/Public/Connect-PSTenable.ps1 @@ -96,11 +96,13 @@ function Connect-PSTenable { Set-PSFconfig -FullName "PSTenable.accesskey" -Value $accesskey Set-PSFconfig -FullName "PSTenable.secretkey" -Value $secretkey Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer + Set-PSFConfig -FullName "PSTenable.ApiKey" -Value $true if ($Register -eq $true) { Register-PSFConfig -FullName "PSTenable.accesskey" Register-PSFConfig -FullName "PSTenable.secretkey" Register-PSFConfig -FullName "PSTenable.Server" + Register-PSFCallback -FullName "PSTenable.Apikey" } }Else{ @@ -108,12 +110,14 @@ function Connect-PSTenable { Set-PSFconfig -FullName "PSTenable.Token" -Value $Session.response.token Set-PSFConfig -FullName "PSTenable.Server" -Value $TenableServer Set-PSFconfig -FullName "PSTenable.Credential" -Value $Credential + Set-PSFConfig -FullName "PSTenable.ApiKey" -Value $false if ($Register -eq $true) { Register-PSFConfig -FullName "PSTenable.WebSession" Register-PSFConfig -FullName "PSTenable.Token" Register-PSFConfig -FullName "PSTenable.Server" Register-PSFConfig -FullName "PSTenable.Token" + Set-PSFConfig -FullName "PSTenable.ApiKey" } } } From 735c6c41c0fc63130ddb3b7f9c8f79fcad5a7bb8 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 16:23:46 -0500 Subject: [PATCH 10/18] Fix typo line 105 --- PSTenable/Public/Connect-PSTenable.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSTenable/Public/Connect-PSTenable.ps1 b/PSTenable/Public/Connect-PSTenable.ps1 index 876130c..30e9a5b 100755 --- a/PSTenable/Public/Connect-PSTenable.ps1 +++ b/PSTenable/Public/Connect-PSTenable.ps1 @@ -102,7 +102,7 @@ function Connect-PSTenable { Register-PSFConfig -FullName "PSTenable.accesskey" Register-PSFConfig -FullName "PSTenable.secretkey" Register-PSFConfig -FullName "PSTenable.Server" - Register-PSFCallback -FullName "PSTenable.Apikey" + Register-PSFConfig -FullName "PSTenable.Apikey" } }Else{ From f6b8d911cd9d801fa461a90bf5056c5bf07061b7 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Mar 2020 16:45:03 -0500 Subject: [PATCH 11/18] Add launch.json to .gitignore. Debug invoke-pstenabletokenstatus --- .gitignore | 1 + PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e57eaa7..9ef9fee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Don't check in the Output dir Output/ .DS_Store +launch.json diff --git a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 index ab13150..459c8b0 100644 --- a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 +++ b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 @@ -21,6 +21,8 @@ Function Invoke-PSTenableTokenStatus { $apiKeyStatus = Get-PSFConfigValue -FullName "PSTenable.ApiKey" + Write-Debug "$apikeystatus" + if($apiKeyStatus -eq $false){ # Credentials @@ -47,7 +49,6 @@ Function Invoke-PSTenableTokenStatus { Write-Output $false } }else{ - Write-Output $apiKeyStatus + Write-Output $true } } - From 6df5cb01c43a7a41294f26fc99f54d7328ca644a Mon Sep 17 00:00:00 2001 From: Harris Date: Mon, 16 Mar 2020 13:46:22 -0500 Subject: [PATCH 12/18] update api logic in auth - Fix bug --- PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 index 459c8b0..cb8678b 100644 --- a/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 +++ b/PSTenable/Private/Invoke-PSTenableTokenStatus.ps1 @@ -49,6 +49,6 @@ Function Invoke-PSTenableTokenStatus { Write-Output $false } }else{ - Write-Output $true + Write-Output $false } } From 2c8ad674a46b74e10117aaa66455dcf443a616ce Mon Sep 17 00:00:00 2001 From: Harris Date: Mon, 16 Mar 2020 13:48:49 -0500 Subject: [PATCH 13/18] Update Invoke-PsTenableRest.ps1 for API endpoint authentication --- PSTenable/Private/Invoke-PSTenableRest.ps1 | 34 +++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/PSTenable/Private/Invoke-PSTenableRest.ps1 b/PSTenable/Private/Invoke-PSTenableRest.ps1 index fc5aea4..2bdacca 100644 --- a/PSTenable/Private/Invoke-PSTenableRest.ps1 +++ b/PSTenable/Private/Invoke-PSTenableRest.ps1 @@ -38,21 +38,33 @@ function Invoke-PSTenableRest { if((Get-PSFConfigValue -FullName "PSTenable.ApiKey" )){ #Check for API Key + $accessKey = Get-PSFConfigValue -FullName 'PSTenable.accesskey' + $secretkey= Get-PSFConfigValue -FullName 'PSTenable.secretkey' + + $authString ="accesskey = $accessKey;secretkey=$secretkey;" + $headers = @{ - "x-apikey" = accesskey=$(Get-PSFConfigValue -FullName 'PSTenable.accesskey'); - secretkey=$(Get-PSFConfigValue -FullName 'PSTenable.secretkey');} + "x-apikey" = $authString + } + $RestMethodParams = @{ + URI = $(Get-PSFConfigValue -FullName 'PSTenable.Server') + '/rest'+ $Endpoint + Method = $Method + Headers = $headers + ContentType = "application/json" + ErrorAction = "Stop" + } }else{ $headers = @{"X-SecurityCenter" = $(Get-PSFConfigValue -FullName 'PSTenable.Token') } - } - $RestMethodParams = @{ - URI = $(Get-PSFConfigValue -FullName 'PSTenable.Server') + $Endpoint - Method = $Method - Headers = $headers - ContentType = "application/json" - ErrorAction = "Stop" - WebSession = $(Get-PSFConfigValue -FullName "PSTenable.WebSession") + $RestMethodParams = @{ + URI = $(Get-PSFConfigValue -FullName 'PSTenable.Server') + $Endpoint + Method = $Method + Headers = $headers + ContentType = "application/json" + ErrorAction = "Stop" + WebSession = $(Get-PSFConfigValue -FullName "PSTenable.WebSession") + } } if ($PSBoundParameters.ContainsKey('Body')) { @@ -72,6 +84,8 @@ function Invoke-PSTenableRest { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $_ } + Write-Debug $RestMethodParams.uri + Invoke-RestMethod @RestMethodParams } From c1b551e15f0cbac9ded9bccce1481eae76f5c58d Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 17 Mar 2020 11:18:45 -0500 Subject: [PATCH 14/18] Update Verision Number --- PSTenable/PSTenable.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSTenable/PSTenable.psd1 b/PSTenable/PSTenable.psd1 index b6c6196..7be3fd8 100644 --- a/PSTenable/PSTenable.psd1 +++ b/PSTenable/PSTenable.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSTenable.psm1' # Version number of this module. - ModuleVersion = '0.2.4' + ModuleVersion = '0.2.5' # Supported PSEditions # CompatiblePSEditions = @() From 2b21f2f4438fcb0748e35648efff0bccb1fa334b Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 17 Mar 2020 16:56:47 -0500 Subject: [PATCH 15/18] Update to add Query Cmdlets --- PSTenable/Public/Get-PSTenableQuery.ps1 | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 PSTenable/Public/Get-PSTenableQuery.ps1 diff --git a/PSTenable/Public/Get-PSTenableQuery.ps1 b/PSTenable/Public/Get-PSTenableQuery.ps1 new file mode 100644 index 0000000..6dab3b6 --- /dev/null +++ b/PSTenable/Public/Get-PSTenableQuery.ps1 @@ -0,0 +1,70 @@ +function Get-PSTenableQuery { + <# + .SYNOPSIS + Retrieves all Queries in Tenable.SC server. + .DESCRIPTION + This function provides a way to retrieve all queries. + .EXAMPLE + PS C:\> Get-PSTenableQuery -Type vuln + This requests all vuln queries currently saved. + + .PARAMETER QueryID + Get Specfic Query by ID + .PARAMETER Type + Get All Queryies of a Specfic type + .INPUTS + None + .OUTPUTS + None + .NOTES + You can pass one or multiple PluginID's in an array. + #> + [CmdletBinding()] + param ( + [parameter(Position = 0, + mandatory = $true, + ValueFromPipeline = $true)] + [string] + [ValidateSet("alert","all", "lce","mobile","ticket","user","vuln")] + $Type + ) + + begin { + $TokenExpiry = Invoke-PSTenableTokenStatus + if ($TokenExpiry -eq $True) {Invoke-PSTenableTokenRenewal} + } + + process { + + $output = + + $Endpoint = "/query" + + if($Type){ + + $Endpoint = $Endpoint + "?=$Type" + } + + $EndPoint = $Endpoint.ToLower() + + $Splat = @{ + Method = "Get" + Endpoint = $Endpoint + } + + $response = Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response + + $managedOnly = $response.manageable|where{$_.canUse -eq $false} + + if(($managedOnly|measure).Sum -gt 0){ + + return $managedOnly + } + + return $response.usable + } + + end { + $Output + } +} From 5fe79f62360e063264130e0fe0f1ec98ef619529 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 17 Mar 2020 16:56:59 -0500 Subject: [PATCH 16/18] Update to add Query Cmdlets --- .../Public/Get-PSTenableQueryAnalysis.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 PSTenable/Public/Get-PSTenableQueryAnalysis.ps1 diff --git a/PSTenable/Public/Get-PSTenableQueryAnalysis.ps1 b/PSTenable/Public/Get-PSTenableQueryAnalysis.ps1 new file mode 100644 index 0000000..1c613e6 --- /dev/null +++ b/PSTenable/Public/Get-PSTenableQueryAnalysis.ps1 @@ -0,0 +1,64 @@ +function Get-PSTenableQueryAnalysis { + <# + .SYNOPSIS + Retrieves all devices that are affected by PluginID. + .DESCRIPTION + This function provides a way to retrieve all devices affected by a specific PluginID that is passed to the function. + .EXAMPLE + PS C:\> Get-PSTenablePlugin -ID "20007" + This passes PluginID 20007 CVE's to the fucntion and returns and all devices affected by the PluginID 20007. + + PS C:\> @("20007","31705") Get-PSTenablePlugin + This passes PluginID 20007 CVE's to the fucntion and returns and all devices affected by the PluginID 20007. + .PARAMETER ID + PluginID from Tenable + .INPUTS + None + .OUTPUTS + None + .NOTES + You can pass one or multiple PluginID's in an array. + #> + [CmdletBinding()] + param ( + [parameter(Position = 0, + mandatory = $true, + ValueFromPipeline = $true)] + [string] + $ID + ) + + begin { + $TokenExpiry = Invoke-PSTenableTokenStatus + if ($TokenExpiry -eq $True) {Invoke-PSTenableTokenRenewal} + } + + process { + + $output = foreach ($queryID in $ID) { + $query = @{ + "type" = "vuln" + "sourceType" = "cumulative" + "query" = @{ + "tool" = "listvuln" + "type" = "vuln" + "id" = "$queryID" + } + } + + $Splat = @{ + Method = "Post" + Body = $(ConvertTo-Json $query -depth 5) + Endpoint = "/analysis" + } + + Invoke-PSTenableRest @Splat | Select-Object -ExpandProperty Response | Select-Object -ExpandProperty Results + + } + + } + + end { + $Output + } +} From 065e44b6c9563ee86a9617d5778b4b922765c943 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 17 Mar 2020 17:00:21 -0500 Subject: [PATCH 17/18] Move the version up 1 minor version --- PSTenable/PSTenable.psd1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PSTenable/PSTenable.psd1 b/PSTenable/PSTenable.psd1 index 7be3fd8..d471f93 100644 --- a/PSTenable/PSTenable.psd1 +++ b/PSTenable/PSTenable.psd1 @@ -77,7 +77,9 @@ 'Get-PSTenablePluginFamilyWindows', 'Get-PSTenableSeverity', 'Get-PSTenableWindowsServerJava' - 'Invoke-PSTenableRest' + 'Invoke-PSTenableRest', + 'Get-PSTenableQuery', + 'Get-PSTenableQueryAnalysis' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. From d61de7cc553bc0b6979b3bbeb1f281fae1eaaea9 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 17 Mar 2020 17:01:35 -0500 Subject: [PATCH 18/18] Fixed a bug where Get-PStenableSeverity wasn't properly looping. Changed so max records is per Serverity --- PSTenable/Public/Get-PSTenableSeverity.ps1 | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/PSTenable/Public/Get-PSTenableSeverity.ps1 b/PSTenable/Public/Get-PSTenableSeverity.ps1 index 42cbef6..a97b3cc 100755 --- a/PSTenable/Public/Get-PSTenableSeverity.ps1 +++ b/PSTenable/Public/Get-PSTenableSeverity.ps1 @@ -4,9 +4,9 @@ function Get-PSTenableSeverity { 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. + Medium, or Low. Revision 0.1, Sept 2019, jwmoss - Revision 0.2, Nov 2019, aarong1234 + Revision 0.2, Nov 2019, aarong1234 .INPUTS None .PARAMETER Severity @@ -25,7 +25,7 @@ function Get-PSTenableSeverity { .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 + .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 @@ -46,7 +46,7 @@ function Get-PSTenableSeverity { )] [string[]] $Severity, - + [Parameter(Position = 1, Mandatory = $false)] [int]$MaxRecords = 0, @@ -54,7 +54,7 @@ function Get-PSTenableSeverity { [ValidateSet($true,$false)] [switch] $Detailed - + ) begin { @@ -69,7 +69,7 @@ function Get-PSTenableSeverity { } 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 = @() + $ID = @() switch ($Severity) { "Critical" { $ID += "4" } "High" { $ID += "3" } @@ -83,13 +83,17 @@ function Get-PSTenableSeverity { process { $APIresults = @() - $CurrentStartOffset = 0 + + + $idCount = 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 = $_ + $CurrentStartOffset = 0 Do { - if ($MaxRecords -ne 0 -and $MaxRecords -lt ($CurrentStartOffset + 2147483647)) { + $idCount++ + if ($MaxRecords -ne 0 -and $MaxRecords -lt ($CurrentStartOffset + 2147483647)) { $CurrentEndOffset = $MaxRecords } else { $CurrentEndOffset = ($CurrentStartOffset + 2147483647) @@ -107,22 +111,22 @@ function Get-PSTenableSeverity { "tool" = & {if ($Detailed) {"vulndetails"} else {"listvuln"}} } } - if ($ID) { + if ($ID) { $PreJSON.query.add("filters",[array]@{ "filterName" = "severity" "operator" = "=" "value" = "$Sev" }) - } - - + } + + $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) + #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 if ($ThisResults) { #non zero records came back $APIresults += $Thisresults @@ -130,7 +134,7 @@ function Get-PSTenableSeverity { $CurrentStartOffset = $CurrentEndOffset if ($Maxrecords -and ($CurrentStartOffset -ge $MaxRecords)) {$ThisResults = $Null} # we don't need to loop anymore } - } While ($ThisResults) + } While ( (($id|measure).count - $idCount) -eq 0) } $APIresults | Sort-object vprscore,basescore -Descending