From 4facc431431f513e4873bcb04d657eebb233ede0 Mon Sep 17 00:00:00 2001 From: Ilya Govorkov Date: Thu, 27 Jun 2019 10:09:38 +0300 Subject: [PATCH] Update the main script from official nmap repo. Thanks to the guys from nmap for making some adjustments to the script. Just want to be consistent between repositories. --- vulners.nse | 184 ++++++++++++++++++++++++++++------------------------ 1 file changed, 100 insertions(+), 84 deletions(-) diff --git a/vulners.nse b/vulners.nse index 284b2be..091de94 100644 --- a/vulners.nse +++ b/vulners.nse @@ -2,35 +2,52 @@ 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 +* 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 +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 +-- @usage -- nmap -sV --script vulners [--script-args mincvss=] -- +-- @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 +-- | 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 +-- +--
+-- false +-- 8.5 +-- CVE-2012-1667 +-- cve +--
+-- +-- false +-- 7.8 +-- CVE-2015-4620 +-- cve +--
+-- author = 'gmedian AT vulners DOT com' license = "Same as Nmap--See https://nmap.org/book/man-legal.html" @@ -41,55 +58,60 @@ 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=nmap.registry.args.mincvss and tonumber(nmap.registry.args.mincvss) or 0.0 - +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 + 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 +-- +-- @param vulns a table with the parsed json response from the vulners server -- function make_links(vulns) - local output_str="" - local is_exploit=false - local cvss_score="" + local output = {} - -- NOTE[gmedian]: data.search is a "list" already, so just use table.sort with a custom compare function - -- However, for the future it might be wiser to create a copy rather than do it in-place - - local vulns_result = {} - for _, v in ipairs(vulns.data.search) do - table.insert(vulns_result, v) + if not vulns or not vulns.data or not vulns.data.search then + return end - -- Sort the acquired vulns by the CVSS score - table.sort(vulns_result, function(a, b) - return a._source.cvss.score > b._source.cvss.score - end - ) - - for _, vuln in ipairs(vulns_result) do - -- Mark the exploits out - is_exploit = vuln._source.bulletinFamily:lower() == "exploit" - - -- Sometimes it might happen, so check the score availability - cvss_score = vuln._source.cvss and (type(vuln._source.cvss.score) == "number") and (vuln._source.cvss.score) or "" + 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 is_exploit or (cvss_score ~= "" and mincvss <= tonumber(cvss_score)) then - output_str = string.format("%s\n\t%s", output_str, vuln._source.id .. "\t\t" .. cvss_score .. '\t\thttps://vulners.com/' .. vuln._source.type .. '/' .. vuln._source.id .. (is_exploit and '\t\t*EXPLOIT*' or '')) + if v.is_exploit or (v.cvss and mincvss <= v.cvss) then + setmetatable(v, cve_meta) + output[#output+1] = v end end - - return output_str + + 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 @@ -101,27 +123,25 @@ end -- @param type string, the type query argument -- function get_results(what, vers, type) - local v_host="vulners.com" - local v_port=443 - local response, path - local status, error + local api_endpoint = "https://vulners.com/api/v3/burp/software/" local vulns - local option={header={}} - - option['header']['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version) + local option={ + header={ + ['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version) + }, + any_af = true, + } - path = '/api/v3/burp/software/' .. '?software=' .. what .. '&version=' .. vers .. '&type=' .. type + local response = http.get_url(('%s?software=%s&version=%s&type=%s'):format(api_endpoint, what, vers, type), option) - response = http.get(v_host, v_port, path, option) - - status = response.status + 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 "" + return elseif status ~= 200 then -- Again just die silently - return "" + return end status, vulns = json.parse(response.body) @@ -131,15 +151,13 @@ function get_results(what, vers, type) return make_links(vulns) end end - - return "" end --- -- Calls get_results for type="software" --- --- It is called from action when nothing is found for the available cpe's +-- +-- It is called from action when nothing is found for the available cpe's -- -- @param software string, the software name -- @param version string, the software version @@ -151,7 +169,7 @@ end --- -- Calls get_results for type="cpe" --- +-- -- Takes the version number from the given cpe and tries to get the result. -- If none found, changes the given cpe a bit in order to possibly separate version number from the patch version -- And makes another attempt. @@ -160,44 +178,42 @@ end -- @param cpe string, the given cpe -- function get_vulns_by_cpe(cpe) - local vers local vers_regexp=":([%d%.%-%_]+)([^:]*)$" - local output_str="" - + -- 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 - _, _, vers = cpe:find(vers_regexp) + local _, _, vers = cpe:find(vers_regexp) if not vers then - return "" + return end - output_str = get_results(cpe, vers, "cpe") + local output = get_results(cpe, vers, "cpe") - if output_str == "" then + if not output then local new_cpe new_cpe = cpe:gsub(vers_regexp, ":%1:%2") - output_str = get_results(new_cpe, vers, "cpe") + output = get_results(new_cpe, vers, "cpe") end - - return output_str + + return output end action = function(host, port) - local tab={} + local tab=stdnse.output_table() local changed=false local response - local output_str="" + local output - for i, cpe in ipairs(port.version.cpe) do - output_str = get_vulns_by_cpe(cpe, port.version) - if output_str ~= "" then - tab[cpe] = output_str + 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 @@ -205,13 +221,13 @@ action = function(host, port) -- 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_str = get_vulns_by_software(port.version.product, port.version.version) - if output_str ~= "" then - tab[vendor_version] = output_str + 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