Using the NVD Database and API to Keep Up with Vulnerabilities and Patches - Tool Drop: CVEScan (Part 3 of 3)

Published: 2021-01-11
Last Updated: 2021-01-11 16:27:46 UTC
by Rob VandenBrink (Version: 1)
0 comment(s)

Now with a firm approach to or putting an inventory and using the NVD API (https://isc.sans.edu/forums/diary/Using+the+NIST+Database+and+API+to+Keep+Up+with+Vulnerabilities+and+Patches+Part+1+of+3/26958/ and https://isc.sans.edu/forums/diary/Using+the+NIST+Database+and+API+to+Keep+Up+with+Vulnerabilities+and+Patches+Playing+with+Code+Part+2+of+3/26964/), for any client I typically create 4 inventories:

  • Devices/appliances and applications that are exposed at the perimeter (public internet or other firewalled trust boundary)
  • Server applications and devices/appliances
  • Workstation  applications
  • IOT devices (in workstation or dedicated VLANs/subnets)

As we've noted, you can use nmap as a short-cut for a first draft of any products that have listening ports.  It's not perfect, but it gives you a decent starting point, or something to "diff" against from one scan to the next.  To just get your the CPE list for a subnet or range of IPs, this does the trick nicely:

nmap -p <ports> -sV --open <subnet> | grep -i cpe | awk -F" "  "{print $NF}" | sort | uniq

(-F gives the delimiter, the print command prints the $NF field.  Since $NF is the number of fields, it prints the last one, which happens to be the CPE).

For a recent client, a scan of just the server subnet gave me this:

cpe:/h:dell:idrac6
cpe:/h:hp:integrated_lights-out
cpe:/h:hp:laserjet_p4014
cpe:/h:konicaminolta:bizhub_c452
cpe:/h:kyocera:taskalfa_3553ci
cpe:/h:ricoh:aficio_mp_161
cpe:/o:apc:aos
cpe:/o:cisco:ios
cpe:/o:cisco:wireless_lan_controller_software
cpe:/o:linux:linux_kernel
cpe:/o:microsoft:windows
cpe:/o:vmware:ESXi:6.0.0

.. so a decent starting point, printers, UPS's, iLo or idrac are almost never patched for instance, or sadly very often not hypervisors either.  Note that nmap didn't give me CPEs for any of the 3 different databases on that subnet.

Let's focus on the first one of these four target lists - perimeter services for an actual customer.
Starting with Cisco FTD (Firepower Threat Defense), we see that even the titles vary from version to version, and the versions are very granular for this product
>type official-cpe-dictionary_v2.3.xml | grep -i title | grep -i cisco | grep -i firepower | grep -i -v management | grep -i "threat defense"
( excerpt only)

    <title xml:lang="en-US">Cisco Firepower Threat Defense (FTD) 6.5.0.3</title>
    <title xml:lang="en-US">Cisco Firepower Threat Defense 6.5.0.5</title>
    <title xml:lang="en-US">Cisco Firepower Threat Defense 6.6.0</title>
    <title xml:lang="en-US">Cisco Firepower Threat Defense (FTD) 6.6.1</title>

Since this is such a lengthy (and version-specific) list, let's try to consolidate.  From cisco's download site, the latest and recommended version (as of today) is 6.6.1.  Knowing that this client will be "close to current" on this, a quick look for FTD 6.6:

"cpe:2.3:a:cisco:firepower_threat_defense:6.6.*:*:*:*:*:*:*:*"

gives us these hits:

cpe:2.3:a:cisco:firepower_threat_defense:6.6.0:*:*:*:*:*:*:*
cpe:2.3:a:cisco:firepower_threat_defense:6.6.1:*:*:*:*:*:*:*

So our final input data file has the following (hostname followed by the cpe "blanket" query):

"dc01-fw01","cpe:2.3:a:cisco:firepower_threat_defense:6.6.*"

Let's add in the Citrix Netscaler Gateway (now called ADC).  The ADC is a pretty versatile appliance, it can be a load balancer, a firewall, a front-end for a Citrix farm, or (just like everyone else these days) and SD-WAN solution.  In our case it's a front-end for a Citrix XenServer farm.
The current version is 13.x, so let's search for all of 13.*:

cpe:2.3:o:citrix:application_delivery_controller_firmware:13.*

Finally, this client also has an application that uses Apache Struts, which they have been very particular about monitoring since the Equifax breach:
The current stable version is 2.5.26, let's hunt for cpe:

cpe:2.3:a:apache:struts:2.5.*

So our perimeter input file will look like this (again, the fields are hostname,cpe):

"dc01-fw01","cpe:2.3:a:cisco:firepower_threat_defense:6.6.*"
"dc01-adc01","cpe:2.3:o:citrix:application_delivery_controller_firmware:13.*"
"dc01-appsrv01","cpe:2.3:a:apache:struts:2.5.*"

We'll call our code with (note the input filename):

cvescan.ps1 -i Customername.Perimeter.in -d 90

This will give us the CVEs for the indicated platforms, for the last 90 days, sorted from high severity to low.

And our code will look like the listing below (maintained at https://github.com/robvandenbrink/CVEScan ):

##########################################################################

# CVESCAN

# Version 1.iscisc0

# Assess an inventoried infrastructure from pre-inventoried CPEs and published CVEs

#

# Hosted at https://github.com/robvandenbrink/CVEScan

#

# Further documentation at:

#         https://isc.sans.edu/forums/diary/Using+the+NIST+Database+and+API+to+Keep+Up+with+Vulnerabilities+and+Patches+Part+1+of+3/26958/

#         https://isc.sans.edu/forums/diary/Using+the+NIST+Database+and+API+to+Keep+Up+with+Vulnerabilities+and+Patches+Playing+with+Code+Part+2+of+3/26964/

#         https://isc.sans.edu/forums/diary/Using+the+NVD+Database+and+API+to+Keep+Up+with+Vulnerabilities+and+Patches+Tool+Drop+CVEScan+Part+3+of+3/26974/

#

# Syntax:

#         CVEScan.ps1  -i <input file> -d <how many days back to look>

##########################################################################

 

param (

[alias("i")]

$infile,

[alias("d")]

$daterange

)

 

function helpsyntax {

write-host "CVESCAN: Assess a known inventory against current CVEs"

write-host "Parameters:"

write-host "    -i          <input file name>"

write-host "Optional Parameters:"

write-host "    -d          <CVEs for last "n" days>"

write-host "cvescan -i perimeterdevices.in -d 60"

exit

}

 

if ($daterange -eq 0) { write-host "ERROR: Must specify input filename and date range`n" ; helpsyntax }

 

# setup

$allCVEs = @()

$CVEDetails = @()

 

$apps = Import-Csv -path $infile

$now = get-date

$outfile = $infile.replace(".in",$now.tostring("yyyy-MM-dd_hh-mm")+"_"+$daterange+"-days.html")

$StartDate = $now.adddays(-$daterange).tostring("yyyy-MM-dd")+ "T00:00:00:000%20UTC-00:00"

 

# Collect host to CVEs table

foreach ($app in $apps) {

    $request = "https://services.nvd.nist.gov/rest/json/cves/1.0?modStartDate=" + $StartDate + "&cpeMatchString=" + $app.cpe

    $CVEs = (invoke-webrequest $request | ConvertFrom-Json).result.CVE_items.cve.CVE_data_meta.id

    foreach ($CVE in $CVEs) {

        $tempobj = [pscustomobject]@{

            Hostname = $app.hostname

            CVE = $CVE

           }

        $allCVEs += $tempobj

        }

    }

 

$Header = @"

<style>

TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}

TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #6495ED;}

TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;VERTICAL-ALIGN: TOP; font-size: 15px}

</style>

"@

 

$filepath = gci $infile

 

$Title = @()

$Title += [pscustomobject]@{ Organization="Scope";bbb=$filepath.basename.split(".")[1] }

$Title += [pscustomobject]@{ Organization="From Date:"; bbb=($now.adddays(-$daterange).tostring("yyyy-MM-dd")) }

$Title += [pscustomobject]@{ Organization="To Date:";bbb=$now.tostring("yyyy-MM-dd") }

 

(($Title | convertto-HTML -title "CVE Summary" -Head $header) + "<br><br><br>").replace("bbb",$filepath.basename.split(".")[0]) | out-file  $outfile

 

(($allCVEs | Convertto-HTML -Head $header) + "<br><br>") | out-file -append $outfile

 

#parse out just the CVEs

$justCVEs = $allCVEs | select CVE | Sort-Object | Get-Unique -AsString

 

# collect CVE info

foreach ($CVE in $justCVEs) {

    $h = ""

    $request = "https://services.nvd.nist.gov/rest/json/cve/1.0/" + $CVE.CVE

    $cvemetadata = (invoke-webrequest $request) | convertfrom-json

    $CVEURLs = $cvemetadata.result.cve_items.cve.references.reference_data.url

    $affectedApps = ($cvemetadata.result.CVE_items.configurations.nodes.children.cpe_match) | where {$_.vulnerable -eq "true" } | select cpe23Uri,versionendincluding

 

    # add the affected hosts back into the detailed listing

    # write-host $CVE.CVE

    foreach ($ac in $allCVEs) {

        if ($ac.CVE -eq $CVE.CVE) {

            $h += ($ac.Hostname + "<br>")

            }

        }

 

    $tempobj = [pscustomobject]@{

        CVE = $CVE.CVE

        Hosts = $h

        # Just the datestamp, remove the clock time

        "Published Date" = ($cvemetadata.result.cve_items.publishedDate).split("T")[0]

        "CVE Description" = $cvemetadata.result.cve_items.cve.description.description_data.value

        Vector = $cvemetadata.result.CVE_items.impact.basemetricv3.cvssv3.attackVector

        "Attack Complexity" = $cvemetadata.result.CVE_items.impact.basemetricv3.cvssv3.attackComplexity

        "User Interaction" = $cvemetadata.result.CVE_items.impact.basemetricv3.cvssv3.userInteraction

        "Base Score" = $cvemetadata.result.CVE_items.impact.basemetricv3.cvssv3.baseScore

        "Severity" = $cvemetadata.result.CVE_items.impact.basemetricv3.cvssv3.baseSeverity

        "Reference URLs" = ($CVEURLs | ft -hidetableheaders | out-string).replace("`n","`n<br>")

        "Affected Apps" = ($affectedapps | ft -HideTableHeaders | out-string).replace("`n","`n<br>")

        }

    $CVEDetails += $tempobj

    }

 

# to just view the detailed output

# $CVEDetails | out-gridview

 

# to output to HTML

$Header = @"

<style>

TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}

TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #6495ED;}

TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;VERTICAL-ALIGN: TOP; font-size: 15px}

</style>

"@

 

# Note that the <br> tags get escaped, these are un-escaped below

# this is a horrible hack, but I can't find a decent "elegant" way to do this

# ... in less than 5x the time it took me to do it the ugly way  :-)

 

 

(($CVEDetails | sort -descending -property "Base Score" )| Convertto-HTML -Head $header) -replace '&lt;br&gt;', '<br>' | out-file  -append $outfile

 

Our output is dumped into: Customername.Perimeter-dateandtime-days.html, so for this example and today's date: Customername.Perimeter2021-01-11_09-50_90-days.html (note that the output filename mirrors the input filename - change that if you need)

Note also in the output that I had to un-escape all of the line breaks that were in the output (sometimes the quick and dirty methods win over perfect code)

Looking at that file, our output (truncated) looks as below.  The lead in is the customer and date range info, followed by the CVE's found on which host.  The final table contains all the CVE details, in descending / unique order of "Base Score" of Severity:

 


 As mentioned, the code is on my github - use it or modify it to suit your needs.  For the most part it's a short list of API requests, with parsing, formatting and I/O bolted on - so if you'd prefer this to be in a different language of course feel free!

If you were able to head off a "situation" in your environment, or if that nmap trick finds something unexpected in your environment, please do post to our comment form (subject to NDA's of course)

===============
Rob VandenBrink
rob@coherentsecurity.com

Keywords: CPE CVE CVEScan NVD
0 comment(s)

Comments


Diary Archives