summaryrefslogtreecommitdiffstats
path: root/scripts/vulners.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/vulners.nse')
-rw-r--r--scripts/vulners.nse236
1 files changed, 236 insertions, 0 deletions
diff --git a/scripts/vulners.nse b/scripts/vulners.nse
new file mode 100644
index 0000000..1508d8f
--- /dev/null
+++ b/scripts/vulners.nse
@@ -0,0 +1,236 @@
+description = [[
+For each available CPE the script prints out known vulns (links to the correspondent info) and correspondent CVSS scores.
+
+Its work is pretty simple:
+* work only when some software version is identified for an open port
+* take all the known CPEs for that software (from the standard nmap -sV output)
+* make a request to a remote server (vulners.com API) to learn whether any known vulns exist for that CPE
+* if no info is found this way, try to get it using the software name alone
+* print the obtained info out
+
+NB:
+Since the size of the DB with all the vulns is more than 250GB there is no way to use a local db.
+So we do make requests to a remote service. Still all the requests contain just two fields - the
+software name and its version (or CPE), so one can still have the desired privacy.
+]]
+
+---
+-- @usage
+-- nmap -sV --script vulners [--script-args mincvss=<arg_val>] <target>
+--
+-- @args vulners.mincvss Limit CVEs shown to those with this CVSS score or greater.
+--
+-- @output
+--
+-- 53/tcp open domain ISC BIND DNS
+-- | vulners:
+-- | ISC BIND DNS:
+-- | CVE-2012-1667 8.5 https://vulners.com/cve/CVE-2012-1667
+-- | CVE-2002-0651 7.5 https://vulners.com/cve/CVE-2002-0651
+-- | CVE-2002-0029 7.5 https://vulners.com/cve/CVE-2002-0029
+-- | CVE-2015-5986 7.1 https://vulners.com/cve/CVE-2015-5986
+-- | CVE-2010-3615 5.0 https://vulners.com/cve/CVE-2010-3615
+-- | CVE-2006-0987 5.0 https://vulners.com/cve/CVE-2006-0987
+-- |_ CVE-2014-3214 5.0 https://vulners.com/cve/CVE-2014-3214
+--
+-- @xmloutput
+-- <table key="cpe:/a:isc:bind:9.8.2rc1">
+-- <table>
+-- <elem key="is_exploit">false</elem>
+-- <elem key="cvss">8.5</elem>
+-- <elem key="id">CVE-2012-1667</elem>
+-- <elem key="type">cve</elem>
+-- </table>
+-- <table>
+-- <elem key="is_exploit">false</elem>
+-- <elem key="cvss">7.8</elem>
+-- <elem key="id">CVE-2015-4620</elem>
+-- <elem key="type">cve</elem>
+-- </table>
+-- </table>
+
+author = 'gmedian AT vulners DOT com'
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"vuln", "safe", "external"}
+
+
+local http = require "http"
+local json = require "json"
+local string = require "string"
+local table = require "table"
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+
+local api_version="1.2"
+local mincvss=stdnse.get_script_args("vulners.mincvss")
+mincvss = tonumber(mincvss) or 0.0
+
+portrule = function(host, port)
+ local vers=port.version
+ return vers ~= nil and vers.version ~= nil
+end
+
+local cve_meta = {
+ __tostring = function(me)
+ return ("\t%s\t%s\thttps://vulners.com/%s/%s%s"):format(me.id, me.cvss or "", me.type, me.id, me.is_exploit and '\t*EXPLOIT*' or '')
+ end,
+}
+
+---
+-- Return a string with all the found cve's and correspondent links
+--
+-- @param vulns a table with the parsed json response from the vulners server
+--
+function make_links(vulns)
+ local output = {}
+
+ if not vulns or not vulns.data or not vulns.data.search then
+ return
+ end
+
+ for _, vuln in ipairs(vulns.data.search) do
+ local v = {
+ id = vuln._source.id,
+ type = vuln._source.type,
+ -- Mark the exploits out
+ is_exploit = vuln._source.bulletinFamily:lower() == "exploit",
+ -- Sometimes it might happen, so check the score availability
+ cvss = tonumber(vuln._source.cvss.score),
+ }
+
+ -- NOTE[gmedian]: exploits seem to have cvss == 0, so print them anyway
+ if not v.cvss or (v.cvss == 0 and v.is_exploit) or mincvss <= v.cvss then
+ setmetatable(v, cve_meta)
+ output[#output+1] = v
+ end
+ end
+
+ if #output > 0 then
+ -- Sort the acquired vulns by the CVSS score
+ table.sort(output, function(a, b)
+ return a.cvss > b.cvss or (a.cvss == b.cvss and a.id > b.id)
+ end)
+ return output
+ end
+end
+
+
+---
+-- Issues the requests, receives json and parses it, calls <code>make_links</code> when successfull
+--
+-- @param what string, future value for the software query argument
+-- @param vers string, the version query argument
+-- @param type string, the type query argument
+--
+function get_results(what, vers, type)
+ local api_endpoint = "https://vulners.com/api/v3/burp/software/"
+ local vulns
+ local option={
+ header={
+ ['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version)
+ },
+ any_af = true,
+ }
+
+ local response = http.get_url(('%s?software=%s&version=%s&type=%s'):format(api_endpoint, what, vers, type), option)
+
+ local status = response.status
+ if status == nil then
+ -- Something went really wrong out there
+ -- According to the NSE way we will die silently rather than spam user with error messages
+ return
+ elseif status ~= 200 then
+ -- Again just die silently
+ return
+ end
+
+ status, vulns = json.parse(response.body)
+
+ if status == true then
+ if vulns.result == "OK" then
+ return make_links(vulns)
+ end
+ end
+end
+
+
+---
+-- Calls <code>get_results</code> for type="software"
+--
+-- It is called from <code>action</code> when nothing is found for the available cpe's
+--
+-- @param software string, the software name
+-- @param version string, the software version
+--
+function get_vulns_by_software(software, version)
+ return get_results(software, version, "software")
+end
+
+
+---
+-- Calls <code>get_results</code> for type="cpe"
+--
+-- Takes the version number from the given <code>cpe</code> and tries to get the result.
+-- If none found, changes the given <code>cpe</code> a bit in order to possibly separate version number from the patch version
+-- And makes another attempt.
+-- Having failed returns an empty string.
+--
+-- @param cpe string, the given cpe
+--
+function get_vulns_by_cpe(cpe)
+ local vers_regexp=":([%d%.%-%_]+)([^:]*)$"
+
+ -- TODO[gmedian]: add check for cpe:/a as we might be interested in software rather than in OS (cpe:/o) and hardware (cpe:/h)
+ -- TODO[gmedian]: work not with the LAST part but simply with the THIRD one (according to cpe doc it must be version)
+
+ -- NOTE[gmedian]: take only the numeric part of the version
+ local _, _, vers = cpe:find(vers_regexp)
+
+
+ if not vers then
+ return
+ end
+
+ local output = get_results(cpe, vers, "cpe")
+
+ if not output then
+ local new_cpe
+
+ new_cpe = cpe:gsub(vers_regexp, ":%1:%2")
+ output = get_results(new_cpe, vers, "cpe")
+ end
+
+ return output
+end
+
+
+action = function(host, port)
+ local tab=stdnse.output_table()
+ local changed=false
+ local response
+ local output
+
+ for i, cpe in ipairs(port.version.cpe) do
+ output = get_vulns_by_cpe(cpe, port.version)
+ if output then
+ tab[cpe] = output
+ changed = true
+ end
+ end
+
+ -- NOTE[gmedian]: issue request for type=software, but only when nothing is found so far
+ if not changed then
+ local vendor_version = port.version.product .. " " .. port.version.version
+ output = get_vulns_by_software(port.version.product, port.version.version)
+ if output then
+ tab[vendor_version] = output
+ changed = true
+ end
+ end
+
+ if (not changed) then
+ return
+ end
+ return tab
+end
+