1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
local datetime = require "datetime"
local datafiles = require "datafiles"
local ipOps = require "ipOps"
local nmap = require "nmap"
local shortport = require "shortport"
local snmp = require "snmp"
local stdnse = require "stdnse"
local string = require "string"
local U = require "lpeg-utility"
local comm = require "comm"
description = [[
Extracts basic information from an SNMPv3 GET request. The same probe is used
here as in the service version detection scan.
]]
---
--@output
--161/udp open snmp udp-response ttl 244 ciscoSystems SNMPv3 server (public)
--| snmp-info:
--| enterprise: ciscoSystems
--| engineIDFormat: mac
--| engineIDData: 00:d4:8c:00:11:22
--| snmpEngineBoots: 6
--|_ snmpEngineTime: 358d01h13m46s
--
--@xmloutput
-- <elem key="enterprise">ciscoSystems</elem>
-- <elem key="engineIDFormat">mac</elem>
-- <elem key="engineIDData">00:d4:8c:b5:32:bc</elem>
-- <elem key="snmpEngineBoots">6</elem>
-- <elem key="snmpEngineTime">358d01h26m34s</elem>
author = "Daniel Miller"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "version", "safe"}
portrule = shortport.version_port_or_service(161, "snmp", "udp")
-- Lifted from nmap-service-probes:
local SNMPv3GetRequest = "\x30\x3a\x02\x01\x03\x30\x0f\x02\x02\x4a\x69\x02\x03\0\xff\xe3\x04\x01\x04\x02\x01\x03\x04\x10\x30\x0e\x04\0\x02\x01\0\x02\x01\0\x04\0\x04\0\x04\0\x30\x12\x04\0\x04\0\xa0\x0c\x02\x02\x37\xf0\x02\x01\0\x02\x01\0\x30\0"
-- TODO: This should probably check for version 1 and version 2, since those
-- can operate on the same port. Right now it's really just "snmp3-info"
action = function (host, port)
local ENTERPRISE_NUMS = nmap.registry.enterprise_numbers
if not ENTERPRISE_NUMS then
local status
status, ENTERPRISE_NUMS = datafiles.parse_file("nselib/data/enterprise_numbers.txt",
{[function(l) return tonumber(l:match("^%d+")) end] = "\t(.*)$"})
if not status then
stdnse.debug1("Couldn't parse enterprise numbers datafile: %s", ENTERPRISE_NUMS)
ENTERPRISE_NUMS = {}
setmetatable(ENTERPRISE_NUMS, {__index = function(i) return "unknown" end})
end
nmap.registry.enterprise_numbers = ENTERPRISE_NUMS
end
local response
-- Did the service engine already do the hard work?
if port.version and port.version.service_fp then
-- Probes sent, replies received, but no match.
response = U.get_response(port.version.service_fp, "SNMPv3GetRequest")
end
if not response then
-- Have to send the probe ourselves
local status
status, response = comm.exchange(host, port, SNMPv3GetRequest)
if not status then
stdnse.debug1("Couldn't get a response: %s", response)
return nil
end
end
local decoded = snmp.decode(response)
-- Check for SNMP version 3 and msgid 0x4a69 (from the probe)
if ((not decoded) or
(decoded[1] or false) ~= 3 or
(not decoded[2]) or
(decoded[2][1] or false) ~= 0x4a69) then
stdnse.debug1("Service is not SNMPv3, or packet structure not recognized")
return nil
end
-- This really only works for User-based Security Model (USM)
if decoded[2][4] ~= 3 then
-- TODO: at least report the security model in use
stdnse.debug1("SNMP service not using User-based Security Model")
return nil
end
-- Decode the msgSecurityParameters octet-string
decoded = snmp.decode(decoded[3])
local output = stdnse.output_table()
-- Decode the msgAuthoritativeEngineID octet-string
local engineID = decoded[1]
local enterprise, pos = string.unpack(">I4", engineID)
if enterprise > 0x80000000 then
enterprise = enterprise - 0x80000000
output.enterprise = ENTERPRISE_NUMS[enterprise]
local format, data
format, pos = string.unpack("B", engineID, pos)
if format == 1 then
output.engineIDFormat = "ipv4"
output.engineIDData = ipOps.str_to_ip(engineID:sub(pos,pos+3))
elseif format == 2 then
output.engineIDFormat = "ipv6"
output.engineIDData = ipOps.str_to_ip(engineID:sub(pos,pos+15))
elseif format == 3 then
output.engineIDFormat = "mac"
output.engineIDData = stdnse.tohex(engineID:sub(pos,pos+5), {separator=':'})
elseif format == 4 then
output.engineIDFormat = "text"
output.engineIDData = engineID:sub(pos)
elseif format == 5 then
output.engineIDFormat = "octets"
output.engineIDData = stdnse.tohex(engineID:sub(pos))
else
output.engineIDFormat = "unknown"
output.engineIDData = stdnse.tohex(engineID:sub(pos))
end
else
output.enterprise = ENTERPRISE_NUMS[enterprise] or enterprise
output.engineIDFormat = "unknown"
output.engineIDData = stdnse.tohex(engineID:sub(5))
end
output.snmpEngineBoots = decoded[2]
output.snmpEngineTime = datetime.format_time(decoded[3])
port.version = port.version or {}
port.version.service = "snmp"
if port.version.product and port.version.product ~= "SNMPv3 server" then
port.version.product = ("%s; %s SNMPv3 server"):format(port.version.product, output.enterprise)
else
port.version.product = ("%s SNMPv3 server"):format(output.enterprise)
end
nmap.set_port_version(host, port, "hardmatched")
return output
end
|