mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 21:21:31 +00:00
New script tftp-version. Closes #548
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
#Nmap Changelog ($Id$); -*-text-*-
|
#Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE][GH#548] New script tftp-version requests a nonexistent file from a TFTP
|
||||||
|
server and matches the error message to a database of known software.
|
||||||
|
[Mak Kolybabi]
|
||||||
|
|
||||||
o [Ncat][GH#1026][GH#2426] Speed up transfers on Windows by avoiding a 125ms
|
o [Ncat][GH#1026][GH#2426] Speed up transfers on Windows by avoiding a 125ms
|
||||||
wait for every read from STDIN. [scriptjunkie]
|
wait for every read from STDIN. [scriptjunkie]
|
||||||
|
|
||||||
|
|||||||
146
nselib/data/tftp-fingerprints.lua
Normal file
146
nselib/data/tftp-fingerprints.lua
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
--[[
|
||||||
|
This is compiled list of known TFTP responses.
|
||||||
|
]]
|
||||||
|
|
||||||
|
author = {"Mak Kolybabi <mak@kolybabi.com>"}
|
||||||
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
|
-- Fingerprints by opcode
|
||||||
|
fingerprints = {
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Example Fingerprint
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Based on the format of the 'match' directive used in service probes.
|
||||||
|
-- https://nmap.org/book/vscan-fileformat.html#vscan-tbl-versioninfo
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- {
|
||||||
|
-- -- Optional:
|
||||||
|
-- rport = REMOTE_PORT_NUMBER, -- some software uses same port, some uses a data port
|
||||||
|
-- length = number, -- length of entire TFTP packet
|
||||||
|
-- errcode = TFTP_ERRCODE, -- also interpreted as DATA.block
|
||||||
|
-- -- Very unlikely, response is a RRQ or WRQ
|
||||||
|
-- mode = "mode",
|
||||||
|
-- -- Mandatory:
|
||||||
|
-- errmsg = "TFTP_ERRMSG", -- also interpreted as DATA.data or RRQ/WRQ.filename
|
||||||
|
-- product = {
|
||||||
|
-- p = "Product",
|
||||||
|
-- v = "Version",
|
||||||
|
-- i = "Extra Info",
|
||||||
|
-- h = "Hostname",
|
||||||
|
-- o = "Operating System",
|
||||||
|
-- d = "Device Type",
|
||||||
|
-- cpe = {"CPE", ...},
|
||||||
|
-- }}
|
||||||
|
|
||||||
|
-- opcode RRQ
|
||||||
|
[1] = {},
|
||||||
|
-- opcode WRQ
|
||||||
|
[2] = {},
|
||||||
|
-- opcode DATA
|
||||||
|
[3] = {},
|
||||||
|
-- opcode ACK
|
||||||
|
[4] = {},
|
||||||
|
-- opcode ERROR
|
||||||
|
[5] = {
|
||||||
|
-- SolarWinds
|
||||||
|
{ errcode = 2, errmsg = "The IP address is not in the range of allowable addresses.",
|
||||||
|
product = {
|
||||||
|
p = "SolarWinds tftpd",
|
||||||
|
e = "IP disallowed",
|
||||||
|
o = "Windows",
|
||||||
|
cpe = {"cpe:/a:solarwinds:tftp_server", "cpe:/o:microsoft:windows"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Cisco
|
||||||
|
{ errcode = 0, errmsg = "Invalid TFTP Opcode",
|
||||||
|
product = {
|
||||||
|
p = "Cisco tftpd",
|
||||||
|
cpe = {"cpe:/a:cisco:tftp_server"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Plan 9
|
||||||
|
{ errcode = 4, errmsg = "Illegal TFTP operation",
|
||||||
|
product = {
|
||||||
|
p = "Plan 9 tftpd",
|
||||||
|
o = "Plan 9",
|
||||||
|
cpe = {"cpe:/o:belllabs:plan_9"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Zoom X5 ADSL Modem
|
||||||
|
{ errcode = 4, errmsg = "Error: Illegal TFTP Operation",
|
||||||
|
product = {
|
||||||
|
p = "Zoom X5 ADSL modem tftpd",
|
||||||
|
d = "broadband router",
|
||||||
|
cpe = {"cpe:/h:zoom:x5"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Cisco Router
|
||||||
|
{ errcode = 4, errmsg = "Illegal operation",
|
||||||
|
product = {
|
||||||
|
p = "Cisco router tftpd",
|
||||||
|
o = "IOS",
|
||||||
|
d = "router",
|
||||||
|
cpe = {"cpe:/a:cisco:tftp_server", "cpe:/o:cisco:ios"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Microsoft Windows Deployment Services
|
||||||
|
{ errcode = 4, errmsg = "Illegal operation error.",
|
||||||
|
product = {
|
||||||
|
p = "Microsoft Windows Deployment Services tftpd",
|
||||||
|
o = "Windows",
|
||||||
|
cpe = {"cpe:/o:microsoft:windows"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- SolarWinds Free
|
||||||
|
{ errcode = 4, errmsg = "Unknown operatation code: 0 received from",
|
||||||
|
product = {
|
||||||
|
p = "SolarWinds Free tftpd",
|
||||||
|
cpe = {"cpe:/a:solarwinds:tftp_server"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{ errcode = 4, errmsg = "Could not find file '",
|
||||||
|
product = {
|
||||||
|
p = "SolarWinds Free tftpd",
|
||||||
|
cpe = {"cpe:/a:solarwinds:tftp_server"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Brother MFC-9340CDW
|
||||||
|
{ errcode = 4, errmsg = "illegal (unrecognized) tftp operation",
|
||||||
|
product = {
|
||||||
|
p = "Brother printer tftpd",
|
||||||
|
d = "printer",
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- HP Intelligent Management Center
|
||||||
|
{ errcode = 0, errmsg = "Not defined, see error message(if any).",
|
||||||
|
product = {
|
||||||
|
p = "HP Intelligent Management Center tftpd",
|
||||||
|
cpe = {"cpe:/a:hp:intelligent_management_center"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Windows 2003 Server Deployment Service
|
||||||
|
{ errcode = 4, errmsg = "Illegal TFTP operation",
|
||||||
|
product = {
|
||||||
|
p = "Windows 2003 Server Deployment Service",
|
||||||
|
o = "Windows",
|
||||||
|
cpe = {"cpe:/o:microsoft:windows_server_2003"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Enistic Zone Controller
|
||||||
|
{ errcode = 1, errmsg = "File not found.",
|
||||||
|
product = {
|
||||||
|
p = "Enistic zone controller tftpd",
|
||||||
|
}},
|
||||||
|
|
||||||
|
-- Netkit
|
||||||
|
{ errcode = 1, errmsg = "File not found",
|
||||||
|
product = {
|
||||||
|
p = "Netkit tftpd or atftpd",
|
||||||
|
cpe = {"cpe:/a:netkit:netkit", "cpe:/a:lefebvre:atftpd"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return fingerprints
|
||||||
@@ -567,6 +567,7 @@ Entry { filename = "telnet-brute.nse", categories = { "brute", "intrusive", } }
|
|||||||
Entry { filename = "telnet-encryption.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "telnet-encryption.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "telnet-ntlm-info.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "telnet-ntlm-info.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "tftp-enum.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "tftp-enum.nse", categories = { "discovery", "intrusive", } }
|
||||||
|
Entry { filename = "tftp-version.nse", categories = { "default", "safe", "version", } }
|
||||||
Entry { filename = "tls-alpn.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "tls-alpn.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "tls-nextprotoneg.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "tls-nextprotoneg.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "tls-ticketbleed.nse", categories = { "safe", "vuln", } }
|
Entry { filename = "tls-ticketbleed.nse", categories = { "safe", "vuln", } }
|
||||||
|
|||||||
321
scripts/tftp-version.nse
Normal file
321
scripts/tftp-version.nse
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
local nmap = require "nmap"
|
||||||
|
local rand = require "rand"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local table = require "table"
|
||||||
|
local ipOps = require "ipOps"
|
||||||
|
local packet = require "packet"
|
||||||
|
local tftp = require "tftp"
|
||||||
|
|
||||||
|
description=[[
|
||||||
|
Obtains information (such as vendor and device type where available) from a
|
||||||
|
TFTP service by requesting a random filename. Software vendor information is
|
||||||
|
determined by matching the error message against a database of known software.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage nmap -sU -p 69 --script tftp-version
|
||||||
|
-- @usage nmap -sV -p 69
|
||||||
|
--
|
||||||
|
-- @args tftp-version.socket Use a listening UDP socket to recieve error messages. This
|
||||||
|
-- method is frequently blocked by client firewalls and NAT
|
||||||
|
-- devices, so the default is to use packet capture instead.
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 69/udp open tftp
|
||||||
|
-- | tftp-version:
|
||||||
|
-- | If you know the name or version of the software running on this port, please submit
|
||||||
|
-- it to dev@nmap.org along with the following information:
|
||||||
|
-- | opcode: 5
|
||||||
|
-- | errcode: 1
|
||||||
|
-- | length: 20
|
||||||
|
-- | rport: 69
|
||||||
|
-- |_ errmsg: can't open file
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE VERSION
|
||||||
|
-- 69/udp open tftp Brother printer tftpd
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- 69/udp open tftp
|
||||||
|
-- | tftp-version:
|
||||||
|
-- | d: printer
|
||||||
|
-- |_ p: Brother printer tftpd
|
||||||
|
--
|
||||||
|
--
|
||||||
|
--@xmloutput
|
||||||
|
--<table key="If you know the name or version of the software running on this port, please
|
||||||
|
--submit it to dev@nmap.org along with the following information">
|
||||||
|
-- <elem key="opcode">5</elem>
|
||||||
|
-- <elem key="errcode">2</elem>
|
||||||
|
-- <elem key="length">21</elem>
|
||||||
|
-- <elem key="rport">14571</elem>
|
||||||
|
-- <elem key="errmsg">Access violation</elem>
|
||||||
|
--</table>
|
||||||
|
--
|
||||||
|
author = "Mak Kolybabi <mak@kolybabi.com>"
|
||||||
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"default", "safe", "version"}
|
||||||
|
|
||||||
|
portrule = shortport.version_port_or_service(69, "tftp", "udp")
|
||||||
|
|
||||||
|
local load_fingerprints = function()
|
||||||
|
-- Check if fingerprints are cached.
|
||||||
|
if nmap.registry.tftp_fingerprints ~= nil then
|
||||||
|
stdnse.debug1("Loading cached TFTP fingerprints...")
|
||||||
|
return nmap.registry.tftp_fingerprints
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load the fingerprints.
|
||||||
|
local path = nmap.fetchfile("nselib/data/tftp-fingerprints.lua")
|
||||||
|
stdnse.debug1("Loading TFTP fingerprint from files: %s", path)
|
||||||
|
local file = loadfile(path, "t")
|
||||||
|
if not file then
|
||||||
|
stdnse.debug1("Couldn't load the file: %s", path)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local fingerprints = file()
|
||||||
|
|
||||||
|
-- Check there are fingerprints to use
|
||||||
|
if not fingerprints or #fingerprints == 0 then
|
||||||
|
stdnse.debug1("No fingerprints were loaded from file: %s", path)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return fingerprints
|
||||||
|
end
|
||||||
|
|
||||||
|
local parse = function(buf, rport)
|
||||||
|
-- Every TFTP packet is at least 4 bytes.
|
||||||
|
if #buf < 4 then
|
||||||
|
stdnse.debug1("Packet was %d bytes, but TFTP packets are a minimum of 4 bytes.", #buf)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local opcode, num, pos = (">I2I2"):unpack(buf)
|
||||||
|
local ret = stdnse.output_table()
|
||||||
|
ret.opcode = opcode
|
||||||
|
ret.errcode = num
|
||||||
|
ret.length = #buf
|
||||||
|
ret.rport = rport
|
||||||
|
|
||||||
|
if opcode == tftp.OpCode.DATA then
|
||||||
|
-- The block number, which must be one.
|
||||||
|
if num ~= 1 then
|
||||||
|
stdnse.debug1("DATA packet should have a block number of 1, not %d.", num)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The data remaining in the response must be from 0 to 512 bytes in length.
|
||||||
|
if #buf > 2 + 2 + 512 then
|
||||||
|
stdnse.debug1("DATA packet should be 0 to 512 bytes, but is %d bytes.", #buf)
|
||||||
|
else
|
||||||
|
ret.errmsg = buf:sub(pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif opcode == tftp.OpCode.ERROR
|
||||||
|
-- ACK extremely unlikely, but we should be thorough.
|
||||||
|
or opcode == tftp.OpCode.ACK then
|
||||||
|
-- Extract the error message, if there is one.
|
||||||
|
ret.errmsg, pos = ("z"):unpack(buf, pos)
|
||||||
|
-- The last byte in the packet must be zero to terminate the error message.
|
||||||
|
if pos ~= #buf + 1 then -- catch both short and long packets
|
||||||
|
stdnse.debug1("ERROR packet does not end with a zero byte.")
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif opcode == tftp.OpCode.RRQ or opcode == tftp.OpCode.WRQ then
|
||||||
|
ret.errmsg, pos = ("z"):unpack(buf, pos - 2)
|
||||||
|
if pos < #buf then
|
||||||
|
ret.mode = ("z"):unpack(buf, pos)
|
||||||
|
end
|
||||||
|
if pos ~= #buf + 1 then -- catch both short and long packets
|
||||||
|
stdnse.debug1("RRQ/WRQ packet does not contain 2 zero-terminated strings")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Any other opcode, defined or otherwise, should not be coming back from the
|
||||||
|
-- service, so we treat it as an error.
|
||||||
|
stdnse.debug1("Unexpected opcode %d received.", opcode)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This works, as does using the same socket without calling connect(), but
|
||||||
|
-- firewalls frequently block the incoming data connection since it isn't on an
|
||||||
|
-- established local:remote port pair. Better to use pcap, but we'll let users
|
||||||
|
-- try it out if they really want to.
|
||||||
|
local socket_listen = function (lhost, lport, host)
|
||||||
|
local bind_socket = nmap.new_socket("udp")
|
||||||
|
bind_socket:set_timeout(stdnse.get_timeout(host))
|
||||||
|
bind_socket:bind(lhost, lport)
|
||||||
|
|
||||||
|
local status, res = bind_socket:receive()
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1("Failed to receive response from server: %s", res)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, err, _, rhost, rport = bind_socket:get_info()
|
||||||
|
bind_socket:close()
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1("Failed to determine source of response: %s", err)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return res, rhost, rport
|
||||||
|
end
|
||||||
|
|
||||||
|
local pcap_listen = function (lhost, lport, host)
|
||||||
|
local pcap = nmap.new_socket()
|
||||||
|
pcap:pcap_open(host.interface, 256, false,
|
||||||
|
("udp and dst host %s and dst port %d"):format(lhost, lport))
|
||||||
|
pcap:set_timeout(stdnse.get_timeout(host))
|
||||||
|
|
||||||
|
local status, length, layer2, layer3 = pcap:pcap_receive()
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1("Failed to get a response: %s", length)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local p = packet.Packet:new(layer3, length)
|
||||||
|
if not p or not p.udp then
|
||||||
|
stdnse.debug1("Error parsing packet.")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local res = layer3:sub(p.udp_offset + 8 + 1) -- packet.lua uses 0-offsets
|
||||||
|
local rhost = p.ip_src
|
||||||
|
local rport = p.udp_sport
|
||||||
|
pcap:pcap_close()
|
||||||
|
return res, rhost, rport
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_listen_func = function (use_socket)
|
||||||
|
if use_socket then
|
||||||
|
return socket_listen
|
||||||
|
else
|
||||||
|
if nmap.is_privileged() then
|
||||||
|
return pcap_listen
|
||||||
|
else
|
||||||
|
stdnse.verbose("Can't use pcap; will try listening with socket.")
|
||||||
|
return socket_listen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local output = stdnse.output_table()
|
||||||
|
local listenfunc = get_listen_func(stdnse.get_script_args(SCRIPT_NAME .. '.socket'))
|
||||||
|
|
||||||
|
-- Generate a random, unlikely filename in a format unlikely to be rejected,
|
||||||
|
-- specifically DOS 8.3 format.
|
||||||
|
local name = rand.random_string(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")
|
||||||
|
local extn = rand.random_string(3, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||||
|
local path = name .. "." .. extn
|
||||||
|
|
||||||
|
-- Create and connect a socket.
|
||||||
|
local socket = nmap.new_socket("udp")
|
||||||
|
socket:set_timeout(stdnse.get_timeout(host))
|
||||||
|
socket:connect(host, port)
|
||||||
|
local status, lhost, lport, rhost, rport = socket:get_info()
|
||||||
|
|
||||||
|
-- Generate a Read Request.
|
||||||
|
local req = (">Hzz"):pack(tftp.OpCode.RRQ, path, "octet")
|
||||||
|
|
||||||
|
-- Send the Read Request.
|
||||||
|
socket:sendto(host, port, req)
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
-- Listen for a response, but if nothing comes back we have to assume that
|
||||||
|
-- this is not a TFTP service and exit quietly.
|
||||||
|
--
|
||||||
|
-- We don't have to worry about other instance of this script running on other
|
||||||
|
-- ports of the same host confounding our results, because TFTP services
|
||||||
|
-- should respond back to the port matching the sending script.
|
||||||
|
local res, rhost, rport = listenfunc(lhost, lport, host)
|
||||||
|
if not res then
|
||||||
|
stdnse.debug1("Failed to receive response from server")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if rhost ~= host.ip then
|
||||||
|
stdnse.debug1("UDP response came from unexpected host: %s (expected %s)", rhost, host.ip)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Parse the response.
|
||||||
|
local pkt = parse(res, rport)
|
||||||
|
if not pkt then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We're sure this is a TFTP server by this point..
|
||||||
|
nmap.set_port_state(host, port, "open")
|
||||||
|
port.version = port.version or {}
|
||||||
|
port.version.service = "tftp"
|
||||||
|
|
||||||
|
local fingerprints = load_fingerprints()
|
||||||
|
if not fingerprints then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try to match the packet against our table of responses, falling back to
|
||||||
|
-- encouraging the user to submit a fingerprint to Nmap.
|
||||||
|
local sw = nil
|
||||||
|
for _, fp in ipairs(fingerprints[pkt.opcode]) do
|
||||||
|
if pkt.errcode == fp.errcode and pkt.errmsg == fp.errmsg
|
||||||
|
and not (fp.rport and pkt.rport ~= fp.rport) then
|
||||||
|
sw = fp.product
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not sw then
|
||||||
|
nmap.set_port_version(host, port, "hardmatched")
|
||||||
|
return {["If you know the name or version of the software running on this port, please submit it to dev@nmap.org along with the following information"]= pkt}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Our goal is to avoid printing output when run with -sV unless it differs.
|
||||||
|
-- When selected by name, always print output
|
||||||
|
local emit_output = nmap.verbosity() > 0
|
||||||
|
|
||||||
|
for _, keypair in ipairs({
|
||||||
|
{"product", "p"},
|
||||||
|
{"version", "v"},
|
||||||
|
{"extrainfo", "i"},
|
||||||
|
{"hostname", "h"},
|
||||||
|
{"ostype", "o"},
|
||||||
|
{"devicetype", "d"},
|
||||||
|
}) do
|
||||||
|
local pv = port.version[keypair[1]]
|
||||||
|
local sv = sw[keypair[2]]
|
||||||
|
if not pv then
|
||||||
|
port.version[keypair[1]] = sv
|
||||||
|
elseif sv and pv ~= sv then
|
||||||
|
emit_output = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Only add CPEs if they aren't there already, to avoid doubling-up.
|
||||||
|
if sw.cpe then
|
||||||
|
local seen = {}
|
||||||
|
if port.version.cpe then
|
||||||
|
for _, cpe in ipairs(port.version.cpe) do
|
||||||
|
seen[cpe] = 1
|
||||||
|
end
|
||||||
|
for _, cpe in ipairs(sw.cpe) do
|
||||||
|
if not seen[cpe] then
|
||||||
|
table.insert(port.version.cpe, cpe)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
port.version.cpe = {table.unpack(sw.cpe)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nmap.set_port_version(host, port, "hardmatched")
|
||||||
|
|
||||||
|
if emit_output then
|
||||||
|
return sw
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user