diff --git a/example.png b/example.png index 10137ab..ae92f16 100644 Binary files a/example.png and b/example.png differ diff --git a/vulners.nse b/vulners.nse index 94eed8d..47150ff 100644 --- a/vulners.nse +++ b/vulners.nse @@ -10,7 +10,7 @@ Its work is pretty simple: --- -- @usage --- nmap -sV --script vulners +-- nmap -sV --script vulners [--script-args mincvss=] -- -- @output -- @@ -36,12 +36,13 @@ local json = require "json" local string = require "string" local table = require "table" -local api_version="1.1" +local api_version="1.2" +local mincvss=nmap.registry.args.mincvss and tonumber(nmap.registry.args.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 @@ -51,34 +52,38 @@ end -- @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_str="" + local is_exploit=false + local cvss_score="" - -- 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 + -- 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) - end + local vulns_result = {} + for _, v in ipairs(vulns.data.search) do + table.insert(vulns_result, v) + 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 ("\t\t" .. vuln._source.cvss.score) or "" - output_str = string.format("%s\n\t%s", output_str, vuln._source.id .. cvss_score .. '\t\thttps://vulners.com/' .. vuln._source.type .. '/' .. vuln._source.id .. (is_exploit and '\t\t*EXPLOIT*' or '')) + 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 "" + + -- 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 '')) end - - return output_str + end + + return output_str end @@ -90,41 +95,38 @@ 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 vulns - local option={header={}} - - option['header']['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version) - - path = '/api/v3/burp/software/' .. '?software=' .. what .. '&version=' .. vers .. '&type=' .. type - - response = http.get(v_host, v_port, path, option) - - 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 == 418 then - -- Too many requests - return "You are doing it too fast. Lower the rate or contact isox AT vulners DOT com." - elseif status ~= 200 then - -- Again just die silently - return "" - end + local v_host="vulners.com" + local v_port=443 + local response, path + local status, error + local vulns + local option={header={}} - status, vulns = json.parse(response.body) + option['header']['User-Agent'] = string.format('Vulners NMAP Plugin %s', api_version) - if status == true then - if vulns.result == "OK" then - return make_links(vulns) - end - end + path = '/api/v3/burp/software/' .. '?software=' .. what .. '&version=' .. vers .. '&type=' .. type + + response = http.get(v_host, v_port, path, option) + 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 + + return "" end @@ -137,7 +139,7 @@ end -- @param version string, the software version -- function get_vulns_by_software(software, version) - return get_results(software, version, "software") + return get_results(software, version, "software") end @@ -152,61 +154,61 @@ 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) + 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) + -- NOTE[gmedian]: take only the numeric part of the version + _, _, vers = cpe:find(vers_regexp) - if not vers then - return "" - end + if not vers then + return "" + end - output_str = get_results(cpe, vers, "cpe") + output_str = get_results(cpe, vers, "cpe") - if output_str == "" then - local new_cpe + if output_str == "" then + local new_cpe - new_cpe = cpe:gsub(vers_regexp, ":%1:%2") - output_str = get_results(new_cpe, vers, "cpe") - end - - return output_str + new_cpe = cpe:gsub(vers_regexp, ":%1:%2") + output_str = get_results(new_cpe, vers, "cpe") + end + + return output_str end action = function(host, port) - local tab={} - local changed=false - local response - local output_str="" - - 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 - 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_str = get_vulns_by_software(port.version.product, port.version.version) - if output_str ~= "" then - tab[vendor_version] = output_str - changed = true - end - end - - if (not changed) then - return - end - return tab + local tab={} + local changed=false + local response + local output_str="" + + 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 + 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_str = get_vulns_by_software(port.version.product, port.version.version) + if output_str ~= "" then + tab[vendor_version] = output_str + changed = true + end + end + + if (not changed) then + return + end + return tab end