Vulnerability/Get-NVDCveData.ps1
|
function Get-NVDCveData { <# .SYNOPSIS Queries the NVD API 2.0 for real CVE data .DESCRIPTION Queries the National Vulnerability Database (NVD) API 2.0 by keyword or CPE name and returns normalized CVE objects with severity, CVSS score, dates and reference URLs. Handles pagination, NVD rate limits and network failures gracefully (empty result on unreachable feed). .PARAMETER Keyword Free-text keyword search (keywordSearch parameter), e.g. 'Microsoft Edge' .PARAMETER CpeName CPE 2.3 name to match exactly (cpeName parameter) .PARAMETER ApiKey Optional NVD API key. Sent only as the 'apiKey' request header, never logged. .PARAMETER ResultsPerPage Results per page (default: 20, max: 2000) .EXAMPLE Get-NVDCveData -Keyword "Google Chrome" -ResultsPerPage 20 Get-NVDCveData -CpeName "cpe:2.3:a:microsoft:edge:*:*:*:*:*:*:*:*" -ApiKey $key .OUTPUTS PSCustomObject list with CVEId, Description, Severity, CvssScore, PublishedDate, LastModified and References #> [CmdletBinding()] param( [Parameter()] [string]$Keyword, [Parameter()] [string]$CpeName, [Parameter()] [string]$ApiKey, [Parameter()] [ValidateRange(1, 2000)] [int]$ResultsPerPage = 20 ) begin { Write-Host "=== NVD CVE LOOKUP ===" -ForegroundColor Cyan Write-Host "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow if ($Keyword) { Write-Host "Keyword: $Keyword" -ForegroundColor Yellow } if ($CpeName) { Write-Host "CPE Name: $CpeName" -ForegroundColor Yellow } Write-Host "API Key: $(if($ApiKey){'Provided'}else{'Not provided (public rate limits apply)'})" -ForegroundColor Yellow if (-not $Keyword -and -not $CpeName) { Write-Error "Either -Keyword or -CpeName must be specified" throw "Either -Keyword or -CpeName must be specified" } # Ensure TLS 1.2 on Windows PowerShell 5.1 (idempotent, only if the type exists) try { if ($null -ne ([Net.SecurityProtocolType] -as [type])) { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } } catch { Write-Verbose "Could not enforce TLS 1.2: $_" } $nvdApiBase = 'https://services.nvd.nist.gov/rest/json/cves/2.0' $requestHeaders = @{} if ($ApiKey) { $requestHeaders['apiKey'] = $ApiKey } # NVD rate limits: 5 requests / 30s public, 50 requests / 30s with API key $sleepSeconds = if ($ApiKey) { 0.6 } else { 6 } $cveList = @() } process { try { $startIndex = 0 $totalResults = 0 $pageCount = 0 do { if ($pageCount -gt 0) { Write-Verbose "Rate limit pause: $sleepSeconds second(s) between paged requests" Start-Sleep -Seconds $sleepSeconds } $queryParams = @("resultsPerPage=$ResultsPerPage", "startIndex=$startIndex") if ($Keyword) { $queryParams += "keywordSearch=$([System.Uri]::EscapeDataString($Keyword))" } if ($CpeName) { $queryParams += "cpeName=$([System.Uri]::EscapeDataString($CpeName))" } $requestUri = "$nvdApiBase`?$($queryParams -join '&')" Write-Host " Querying NVD (page $($pageCount + 1), start index $startIndex)..." -ForegroundColor Gray $response = $null try { $response = Invoke-RestMethod -Uri $requestUri -Method Get -Headers $requestHeaders -TimeoutSec 60 -ErrorAction Stop } catch { Write-Warning "NVD API request failed: $($_.Exception.Message)" return } if ($null -eq $response -or $null -eq $response.PSObject.Properties['vulnerabilities'] -or $null -eq $response.vulnerabilities) { Write-Warning "NVD API returned no vulnerability data" return } if ($null -ne $response.PSObject.Properties['totalResults'] -and $null -ne $response.totalResults) { $totalResults = [int]$response.totalResults } $vulnerabilities = @($response.vulnerabilities) foreach ($item in $vulnerabilities) { try { if ($null -eq $item.PSObject.Properties['cve'] -or $null -eq $item.cve) { continue } $cve = $item.cve $cveId = 'Unknown' if ($null -ne $cve.PSObject.Properties['id'] -and $null -ne $cve.id) { $cveId = [string]$cve.id } $description = '' if ($null -ne $cve.PSObject.Properties['descriptions'] -and $null -ne $cve.descriptions) { $englishDescription = @($cve.descriptions) | Where-Object { $null -ne $_.PSObject.Properties['lang'] -and $_.lang -eq 'en' } | Select-Object -First 1 if ($null -ne $englishDescription -and $null -ne $englishDescription.PSObject.Properties['value'] -and $null -ne $englishDescription.value) { $description = [string]$englishDescription.value } elseif (@($cve.descriptions).Count -gt 0) { $firstDescription = @($cve.descriptions)[0] if ($null -ne $firstDescription.PSObject.Properties['value'] -and $null -ne $firstDescription.value) { $description = [string]$firstDescription.value } } } $severity = 'Unknown' $cvssScore = $null if ($null -ne $cve.PSObject.Properties['metrics'] -and $null -ne $cve.metrics) { $metrics = $cve.metrics if ($null -ne $metrics.PSObject.Properties['cvssMetricV31'] -and $null -ne $metrics.cvssMetricV31 -and @($metrics.cvssMetricV31).Count -gt 0) { $metric = @($metrics.cvssMetricV31)[0] if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) { if ($null -ne $metric.cvssData.PSObject.Properties['baseSeverity'] -and $null -ne $metric.cvssData.baseSeverity) { $severity = [string]$metric.cvssData.baseSeverity } if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and $null -ne $metric.cvssData.baseScore) { $cvssScore = [double]$metric.cvssData.baseScore } } } elseif ($null -ne $metrics.PSObject.Properties['cvssMetricV30'] -and $null -ne $metrics.cvssMetricV30 -and @($metrics.cvssMetricV30).Count -gt 0) { $metric = @($metrics.cvssMetricV30)[0] if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) { if ($null -ne $metric.cvssData.PSObject.Properties['baseSeverity'] -and $null -ne $metric.cvssData.baseSeverity) { $severity = [string]$metric.cvssData.baseSeverity } if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and $null -ne $metric.cvssData.baseScore) { $cvssScore = [double]$metric.cvssData.baseScore } } } elseif ($null -ne $metrics.PSObject.Properties['cvssMetricV2'] -and $null -ne $metrics.cvssMetricV2 -and @($metrics.cvssMetricV2).Count -gt 0) { $metric = @($metrics.cvssMetricV2)[0] if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) { if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and $null -ne $metric.cvssData.baseScore) { $cvssScore = [double]$metric.cvssData.baseScore } } if ($null -ne $metric.PSObject.Properties['baseSeverity'] -and $null -ne $metric.baseSeverity) { $severity = [string]$metric.baseSeverity } elseif ($null -ne $cvssScore) { if ($cvssScore -ge 7.0) { $severity = 'HIGH' } elseif ($cvssScore -ge 4.0) { $severity = 'MEDIUM' } else { $severity = 'LOW' } } } } $publishedDate = $null if ($null -ne $cve.PSObject.Properties['published'] -and $null -ne $cve.published) { if ($cve.published -is [DateTime]) { $publishedDate = $cve.published.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff") } else { $publishedDate = [string]$cve.published } } $lastModified = $null if ($null -ne $cve.PSObject.Properties['lastModified'] -and $null -ne $cve.lastModified) { if ($cve.lastModified -is [DateTime]) { $lastModified = $cve.lastModified.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff") } else { $lastModified = [string]$cve.lastModified } } $references = @() if ($null -ne $cve.PSObject.Properties['references'] -and $null -ne $cve.references) { foreach ($reference in @($cve.references)) { if ($null -ne $reference.PSObject.Properties['url'] -and $null -ne $reference.url) { $references += [string]$reference.url } } } $cveList += [PSCustomObject]@{ CVEId = $cveId Description = $description Severity = $severity CvssScore = $cvssScore PublishedDate = $publishedDate LastModified = $lastModified References = $references } } catch { Write-Warning "Could not parse CVE entry: $_" } } $pageCount++ $startIndex += @($vulnerabilities).Count } while (@($vulnerabilities).Count -gt 0 -and $startIndex -lt $totalResults) Write-Host " Retrieved $($cveList.Count) CVE(s) of $totalResults total result(s)" -ForegroundColor Gray } catch { Write-Error "Error during NVD CVE lookup: $_" throw } } end { Write-Host "=== NVD CVE LOOKUP COMPLETE ===" -ForegroundColor $(if ($cveList.Count -gt 0) { 'Green' } else { 'Gray' }) Write-Host " CVEs returned: $($cveList.Count)" -ForegroundColor White return $cveList } } |