mirror of
https://github.com/nmap/nmap.git
synced 2025-12-14 11:49:01 +00:00
CoAP scripts, payloads, and service probe for CoAP by Mak Kolybabi
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE][GH#369] New script: coap-resources grabs the list of available
|
||||||
|
resources from CoAP endpoints. [Mak Kolybabi]
|
||||||
|
|
||||||
o [NSE] New script: ipmi-version retrieves protocol version and authentication
|
o [NSE] New script: ipmi-version retrieves protocol version and authentication
|
||||||
options from ASF-RMCP (IPMI) services. [Claudiu Perta]
|
options from ASF-RMCP (IPMI) services. [Claudiu Perta]
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ udp 5353
|
|||||||
"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
|
"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
|
||||||
"\x09_services\x07_dns-sd\x04_udp\x05local\x00\x00\x0C\x00\x01"
|
"\x09_services\x07_dns-sd\x04_udp\x05local\x00\x00\x0C\x00\x01"
|
||||||
|
|
||||||
|
# CoAP GET .well-known/core
|
||||||
|
udp 5683 "@\x01\x01\xce\xbb.well-known\x04core"
|
||||||
|
|
||||||
# Amanda backup service noop request. I think that this does nothing on the
|
# Amanda backup service noop request. I think that this does nothing on the
|
||||||
# server but only asks it to send back its feature list. In reply we expect an
|
# server but only asks it to send back its feature list. In reply we expect an
|
||||||
# ACK or (more likely) an ERROR. I couldn't find good online documentation of
|
# ACK or (more likely) an ERROR. I couldn't find good online documentation of
|
||||||
|
|||||||
@@ -14989,3 +14989,12 @@ rarity 9
|
|||||||
ports 623
|
ports 623
|
||||||
|
|
||||||
softmatch asf-rmcp m|^\x06\0\xff\x07\0\0\0\0\0\0\0\0\0\x10|
|
softmatch asf-rmcp m|^\x06\0\xff\x07\0\0\0\0\0\0\0\0\0\x10|
|
||||||
|
|
||||||
|
##############################NEXT PROBE##############################
|
||||||
|
# CoAP GET .well-known/core
|
||||||
|
Probe UDP coap-request q|@\x01\x01\xce\xbb.well-known\x04core|
|
||||||
|
rarity 9
|
||||||
|
ports 5683
|
||||||
|
sslports 5684
|
||||||
|
|
||||||
|
softmatch coap m|^`E|
|
||||||
|
|||||||
@@ -4477,6 +4477,8 @@ rrac 5678/tcp 0.000228 # Remote Replication Agent Connection
|
|||||||
activesync 5679/tcp 0.000590 # Microsoft ActiveSync PDY synchronization
|
activesync 5679/tcp 0.000590 # Microsoft ActiveSync PDY synchronization
|
||||||
canna 5680/tcp 0.000151 # Canna (Japanese Input)
|
canna 5680/tcp 0.000151 # Canna (Japanese Input)
|
||||||
brightcore 5682/udp 0.000330 # BrightCore control & data transfer exchange
|
brightcore 5682/udp 0.000330 # BrightCore control & data transfer exchange
|
||||||
|
coap 5683/udp 0.000330 # Constrained Application Protocol
|
||||||
|
coaps 5684/udp 0.000330 # DTLS-secured CoAP
|
||||||
qmvideo 5689/udp 0.000330 # QM video network management protocol
|
qmvideo 5689/udp 0.000330 # QM video network management protocol
|
||||||
unknown 5693/udp 0.000330
|
unknown 5693/udp 0.000330
|
||||||
unknown 5699/udp 0.000330
|
unknown 5699/udp 0.000330
|
||||||
|
|||||||
2714
nselib/coap.lua
Normal file
2714
nselib/coap.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,7 @@ local libs = {
|
|||||||
"brute",
|
"brute",
|
||||||
"cassandra",
|
"cassandra",
|
||||||
"citrixxml",
|
"citrixxml",
|
||||||
|
"coap",
|
||||||
"comm",
|
"comm",
|
||||||
"creds",
|
"creds",
|
||||||
"cvs",
|
"cvs",
|
||||||
@@ -66,6 +67,7 @@ local libs = {
|
|||||||
"imap",
|
"imap",
|
||||||
"informix",
|
"informix",
|
||||||
"ipOps",
|
"ipOps",
|
||||||
|
"ipmi",
|
||||||
"ipp",
|
"ipp",
|
||||||
"iscsi",
|
"iscsi",
|
||||||
"isns",
|
"isns",
|
||||||
|
|||||||
316
scripts/coap-resources.nse
Normal file
316
scripts/coap-resources.nse
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
local coap = require "coap"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local table = require "table"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Dumps list of available resources from CoAP endpoints.
|
||||||
|
|
||||||
|
This script establishes a connection to a CoAP endpoint and performs a
|
||||||
|
GET request on a resource. The default resource for our request is
|
||||||
|
<code>/.well-known/core</core>, which should contain a list of
|
||||||
|
resources provided by the endpoint.
|
||||||
|
|
||||||
|
For additional information:
|
||||||
|
* https://en.wikipedia.org/wiki/Constrained_Application_Protocol
|
||||||
|
* https://tools.ietf.org/html/rfc7252
|
||||||
|
* https://tools.ietf.org/html/rfc6690
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage nmap -p U:5683 -sU --script coap-resources <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 5683/udp open coap udp-response ttl 36
|
||||||
|
-- | coap-resources:
|
||||||
|
-- | /large:
|
||||||
|
-- | rt: block
|
||||||
|
-- | sz: 1280
|
||||||
|
-- | title: Large resource
|
||||||
|
-- | /large-update:
|
||||||
|
-- | ct: 0
|
||||||
|
-- | rt: block
|
||||||
|
-- | sz: 55
|
||||||
|
-- | title: Large resource that can be updated using PUT method
|
||||||
|
-- | /link1:
|
||||||
|
-- | if: If1
|
||||||
|
-- | rt: Type1 Type2
|
||||||
|
-- |_ title: Link test resource
|
||||||
|
--
|
||||||
|
-- @args coap-resources.uri URI to request via the GET method,
|
||||||
|
-- <code>/.well-known/core</code> by default.
|
||||||
|
--
|
||||||
|
-- @xmloutput
|
||||||
|
-- <table key="/">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="title">General Info</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="/ft">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="title">Faults Reporting</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="/mn">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="title">Monitor Reporting</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="/st">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="title">Status Reporting</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="/time">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="obs,</devices/block>;title">Devices Block</elem>
|
||||||
|
-- <elem key="title">Internal Clock</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="/wn">
|
||||||
|
-- <elem key="ct">0</elem>
|
||||||
|
-- <elem key="title">Warnings Reporting</elem>
|
||||||
|
-- </table>
|
||||||
|
|
||||||
|
author = "Mak Kolybabi <mak@kolybabi.com>"
|
||||||
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"safe", "discovery"}
|
||||||
|
|
||||||
|
-- TODO: Add 5684 "coaps" if DTLS support is added
|
||||||
|
portrule = shortport.port_or_service(5683, "coap", "udp")
|
||||||
|
|
||||||
|
format_payload = function(payload)
|
||||||
|
-- Leave strings alone.
|
||||||
|
if type(payload) == "string" then
|
||||||
|
return payload
|
||||||
|
end
|
||||||
|
|
||||||
|
local tbl = stdnse.output_table()
|
||||||
|
|
||||||
|
-- We want to go through all of the links in alphabetical order.
|
||||||
|
table.sort(payload, function(a,b) return a.name < b.name end)
|
||||||
|
|
||||||
|
for _, link in ipairs(payload) do
|
||||||
|
-- We want to go through all of the parameters in alphabetical
|
||||||
|
-- order.
|
||||||
|
table.sort(link.parameters, function(a,b) return a.name < b.name end)
|
||||||
|
|
||||||
|
local row = stdnse.output_table()
|
||||||
|
for _, param in ipairs(link.parameters) do
|
||||||
|
row[param.name] = param.value
|
||||||
|
end
|
||||||
|
|
||||||
|
tbl[link.name] = row
|
||||||
|
end
|
||||||
|
|
||||||
|
return tbl
|
||||||
|
end
|
||||||
|
|
||||||
|
get_blocks = function(helper, options, b2opt, payload, max_reqs)
|
||||||
|
-- Initialize the block table to store all of our received blocks.
|
||||||
|
local blocks = {}
|
||||||
|
blocks[b2opt.number] = payload
|
||||||
|
|
||||||
|
-- If we don't know the number of the last block, we'll need to use
|
||||||
|
-- the largest number we've seen as the maxumum.
|
||||||
|
local max = b2opt.number
|
||||||
|
|
||||||
|
-- If the first block we received happens to be the last one, either
|
||||||
|
-- by being a one-block sequence or by the endpoint sending us the
|
||||||
|
-- last block in the sequence first, we want to record the final
|
||||||
|
-- number in the sequence.
|
||||||
|
local last = nil
|
||||||
|
if b2opt.more == false then
|
||||||
|
last = b2opt.number
|
||||||
|
max = b2opt.number
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We'll continue to request blocks that are the same size as the
|
||||||
|
-- original block, since the endpoint likely prefers that.
|
||||||
|
local length = b2opt.length
|
||||||
|
|
||||||
|
-- We want to track the number of requests we make so that a
|
||||||
|
-- malicious endpoint can't keep us on the hook forever.
|
||||||
|
for req = 1, max_reqs do
|
||||||
|
-- Determine if there are any blocks in the sequence that we have
|
||||||
|
-- not yet received.
|
||||||
|
local top = max + 1
|
||||||
|
if last then
|
||||||
|
top = last
|
||||||
|
end
|
||||||
|
|
||||||
|
local num = top
|
||||||
|
for i = 0, top do
|
||||||
|
if not blocks[i] then
|
||||||
|
num = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If the block we think we're missing is at the end of the
|
||||||
|
-- sequence, we've got them all.
|
||||||
|
if last and num >= last then
|
||||||
|
stdnse.debug3("All %d blocks have been retrieved.", last)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create the request.
|
||||||
|
local opts = {
|
||||||
|
["code"] = "get",
|
||||||
|
["type"] = "confirmable",
|
||||||
|
["options"] = {
|
||||||
|
{["name"] = "block2", ["value"] = {
|
||||||
|
["number"] = num,
|
||||||
|
["more"] = false,
|
||||||
|
["length"] = length
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local components = stdnse.strsplit("/", options.uri)
|
||||||
|
for _, component in ipairs(components) do
|
||||||
|
if component ~= "" then
|
||||||
|
table.insert(opts.options, {["name"] = "uri_path", ["value"] = component})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Send the request and receive the response.
|
||||||
|
stdnse.debug3("Requesting block %d of size %d.", num, length)
|
||||||
|
local status, response = helper:request(opts)
|
||||||
|
if not status then
|
||||||
|
return false, response
|
||||||
|
end
|
||||||
|
|
||||||
|
if not response.payload then
|
||||||
|
return false, "Response did not contain a payload."
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for the presence of the block2 option, and if it's
|
||||||
|
-- missing then we're going to stop.
|
||||||
|
b2opt = coap.COAP.header.find_option(response, "block2")
|
||||||
|
if not b2opt then
|
||||||
|
stdnse.debug1("Stopped requesting more blocks, response found without block2 option.")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
stdnse.debug3("Received block %d of size %d.", b2opt.number, b2opt.length)
|
||||||
|
blocks[b2opt.number] = response.payload
|
||||||
|
|
||||||
|
if b2opt.more == false then
|
||||||
|
stdnse.debug3("Block %d indicates it is the end of the sequence.", b2opt.number, b2opt.length)
|
||||||
|
last = b2opt.number
|
||||||
|
max = b2opt.number
|
||||||
|
elseif b2opt.number > max then
|
||||||
|
max = b2opt.number
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reassemble payload, handling potentially missing blocks.
|
||||||
|
local result = ""
|
||||||
|
for i = 1, max do
|
||||||
|
if not blocks[i] then
|
||||||
|
stdnse.debug3("Block %d is missing, replacing with dummy data.", i)
|
||||||
|
result = result .. ("<! missing block %d!>"):format(i)
|
||||||
|
else
|
||||||
|
result = result .. blocks[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_args ()
|
||||||
|
local args = {}
|
||||||
|
|
||||||
|
local uri = stdnse.get_script_args(SCRIPT_NAME .. '.uri')
|
||||||
|
if not uri then
|
||||||
|
uri = "/.well-known/core"
|
||||||
|
end
|
||||||
|
args.uri = uri
|
||||||
|
|
||||||
|
return true, args
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local output = stdnse.output_table()
|
||||||
|
|
||||||
|
-- Parse and sanity check the command line arguments.
|
||||||
|
local status, options = parse_args()
|
||||||
|
if not status then
|
||||||
|
output.ERROR = options
|
||||||
|
return output, output.ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create an instance of the CoAP library's client object.
|
||||||
|
local helper = coap.Helper:new(host, port)
|
||||||
|
|
||||||
|
-- Connect to the CoAP endpoint.
|
||||||
|
local status, response = helper:connect({["uri"] = options.uri})
|
||||||
|
if not status then
|
||||||
|
-- Erros at this stage indicate we're probably not talking to a CoAP server,
|
||||||
|
-- so we exit silently.
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check that the response is a 2.05, otherwise we don't know how to
|
||||||
|
-- continue.
|
||||||
|
if response.code ~= "content" then
|
||||||
|
-- If the port runs an echo service, we'll see an unexpected 'get' code.
|
||||||
|
if response.code == "get" then
|
||||||
|
-- Exit silently, this has all been a mistake.
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If the requested resource wasn't found, that's okay.
|
||||||
|
if response.code == "not_found" then
|
||||||
|
stdnse.debug1("The target reports that the resource '%s' was not found.", options.uri)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Otherwise, we assume that we're getting a legitimate CoAP response.
|
||||||
|
output.ERROR = ("Server responded with '%s' code where 'content' was expected."):format(response.code)
|
||||||
|
return output, output.ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = response.payload
|
||||||
|
if not result then
|
||||||
|
output.ERROR = "Payload for initial response was not part of the packet."
|
||||||
|
return output, output.ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for the presence of the block2 option, which indicates that
|
||||||
|
-- we'll need to perform more requests.
|
||||||
|
local b2opt = coap.COAP.header.find_option(response, "block2")
|
||||||
|
if b2opt then
|
||||||
|
-- Since the block2 option was used, the payload should be an unparsed string.
|
||||||
|
assert(type(result) == "string")
|
||||||
|
|
||||||
|
local status, payload = get_blocks(helper, options, b2opt, result, 64)
|
||||||
|
if not status then
|
||||||
|
output.ERROR = result
|
||||||
|
return output, output.ERROR
|
||||||
|
end
|
||||||
|
result = result .. payload
|
||||||
|
|
||||||
|
-- Parse the payload.
|
||||||
|
local status, parsed = coap.COAP.payload.parse(response, result)
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1("Failed to parse payload: %s", parsed)
|
||||||
|
stdnse.debug1("Falling back to returning raw payload as last resort.")
|
||||||
|
output["Raw CoAP response"] = result
|
||||||
|
return output, stdnse.format_output(true, output)
|
||||||
|
end
|
||||||
|
|
||||||
|
result = parsed
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Regardless of whether the block2 option was used, we should now have a
|
||||||
|
-- parsed payload in some format or another. For now, they should all be
|
||||||
|
-- strings or tables.
|
||||||
|
assert(type(result) == "string" or type(result) == "table")
|
||||||
|
|
||||||
|
-- If the payload has been parsed, and we requested the default
|
||||||
|
-- resource, then we know how to format it nicely.
|
||||||
|
local formatted = result
|
||||||
|
if true then
|
||||||
|
formatted = format_payload(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
end
|
||||||
@@ -65,6 +65,7 @@ Entry { filename = "citrix-enum-servers-xml.nse", categories = { "discovery", "s
|
|||||||
Entry { filename = "citrix-enum-servers.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "citrix-enum-servers.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "clamav-exec.nse", categories = { "exploit", "vuln", } }
|
Entry { filename = "clamav-exec.nse", categories = { "exploit", "vuln", } }
|
||||||
Entry { filename = "clock-skew.nse", categories = { "default", "safe", } }
|
Entry { filename = "clock-skew.nse", categories = { "default", "safe", } }
|
||||||
|
Entry { filename = "coap-resources.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "couchdb-databases.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "couchdb-databases.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "couchdb-stats.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "couchdb-stats.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "creds-summary.nse", categories = { "auth", "default", "safe", } }
|
Entry { filename = "creds-summary.nse", categories = { "auth", "default", "safe", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user