236 lines
7.2 KiB
Lua
236 lines
7.2 KiB
Lua
description = [[
|
|
NSE script using some well-known service to provide info on vulnerabilities
|
|
For each available cpe it prints the known vulns (links to the correspondent info).
|
|
|
|
Its work is pretty simple:
|
|
- work only when some software version is identified for an open port
|
|
- take all the known cpe for that software (from the standard nmap output)
|
|
- ask whether some known vulns exist for that cpe
|
|
- print that info out
|
|
|
|
https://github.com/vulnersCom/nmap-vulners/releases/latest
|
|
|
|
Dependencies:
|
|
nmap libraries:
|
|
http
|
|
json
|
|
string
|
|
|
|
The only thing you should always keep in mind is that the script depends on having software versions at hand, so it only works with -sV flag.
|
|
|
|
Installation
|
|
locate, where your nmap scripts are located in your system
|
|
for *nix system it might be ~/.nmap/scripts/ or $NMAPDIR
|
|
for Mac it might be /usr/local/Cellar/nmap/<version>/share/nmap/scripts/
|
|
for Windows you have to find it yourself
|
|
copy the provided script (vulners.nse) into that directory
|
|
|
|
Usage
|
|
Use it as straightforward as you can:
|
|
nmap -sV --script vulners <target>
|
|
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap -sV --script vulners <target>
|
|
--
|
|
-- @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
|
|
--
|
|
|
|
author = 'gmedian AT vulners DOT com'
|
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
|
categories = {"vuln", "safe"}
|
|
|
|
|
|
local http = require "http"
|
|
local json = require "json"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
|
|
local api_version="1.1"
|
|
|
|
|
|
portrule = function(host, port)
|
|
local vers=port.version
|
|
return vers ~= nil and vers.version ~= nil
|
|
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_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
|
|
|
|
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 ''))
|
|
end
|
|
|
|
return output_str
|
|
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 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
|
|
|
|
status, vulns = json.parse(response.body)
|
|
|
|
if status == true then
|
|
if vulns.result == "OK" then
|
|
return make_links(vulns)
|
|
end
|
|
end
|
|
|
|
return ""
|
|
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
|
|
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)
|
|
|
|
|
|
if not vers then
|
|
return ""
|
|
end
|
|
|
|
output_str = get_results(cpe, vers, "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
|
|
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
|
|
end
|
|
|