summaryrefslogtreecommitdiffstats
path: root/scripts/snmp-info.nse
blob: bad3f4f2431eab023c01fead2378c49886963959 (plain)
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