diff --git a/nselib/snmp.lua b/nselib/snmp.lua
index a2cfff1da..b9ae154e4 100644
--- a/nselib/snmp.lua
+++ b/nselib/snmp.lua
@@ -1,12 +1,15 @@
---
--- SNMP functions.
+-- SNMP library.
--
--- @args snmpcommunity The community string to use. If not given, it is
--- "public", or whatever is passed to buildPacket.
+-- @author Patrik Karlsson
+-- @author Gioacchino Mazzurco
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+-- 2015-06-11 Gioacchino Mazzurco - Use creds library to handle SNMP community
+
local asn1 = require "asn1"
local bin = require "bin"
+local creds = require "creds"
local math = require "math"
local nmap = require "nmap"
local stdnse = require "stdnse"
@@ -136,37 +139,16 @@ function decode(encStr, pos)
return decoder:decode( encStr, pos )
end
----
--- Get the best community string for a host
--- Tries the following in order:
--- * host.registry.snmpcommunity
--- * snmpcommunity script-arg
--- * The fallback parameter
--- * "public"
--- @param host The host table to check.
--- @param fallback An alternate default to use instead of "public"
--- @return The best-guess SNMP community string to use
-function best_community(host, fallback)
- if host and host.registry and host.registry.snmpcommunity then
- return host.registry.snmpcommunity
- else
- return nmap.registry.args.snmpcommunity or fallback or "public"
- end
-end
-
---
-- Create an SNMP packet.
-- @param PDU SNMP Protocol Data Unit to be encapsulated in the packet.
-- @param version SNMP version, default 0 (SNMP V1).
--- @param commStr community string, if not already supplied in registry or as
--- the snmpcommunity script argument.
+-- @param commStr community string.
function buildPacket(PDU, version, commStr)
- local comm = commStr or best_community()
-
if (not version) then version = 0 end
local packet = {}
packet[1] = version
- packet[2] = comm
+ packet[2] = commStr
packet[3] = PDU
return packet
end
@@ -218,11 +200,10 @@ end
-- @param ... Object identifiers to be queried.
-- @return Table representing PDU.
function buildGetNextRequest(options, ...)
- if not options then options = {} end
-
- if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end
- if not options.err then options.err = 0 end
- if not options.errIdx then options.errIdx = 0 end
+ options = options or {}
+ options.reqId = options.reqId or math.fmod(nmap.clock_ms(), 65000)
+ options.err = options.err or 0
+ options.errIdx = options.errIdx or 0
local req = {}
req._snmp = 'A1'
@@ -414,7 +395,7 @@ function fetchResponseValues(resp)
local varBind
if (resp._snmp and resp._snmp == 'A2') then
varBind = resp[4]
- elseif (resp[3]._snmp and resp[3]._snmp == 'A2') then
+ elseif (resp[3] and resp[3]._snmp and resp[3]._snmp == 'A2') then
varBind = resp[3][4]
end
@@ -443,21 +424,6 @@ function fetchResponseValues(resp)
end
----
--- Fetches the first value from a SNMP response.
--- @param response SNMP Response (will be decoded if necessary).
--- @return First decoded value of the response.
-function fetchFirst(response)
- local result = fetchResponseValues(response)
-
- if type(result) == "table" and result[1] and result[1][1] then
- return result[1][1]
- else
- return nil
- end
-end
-
-
--- SNMP Helper class
--
-- Handles socket communication, parsing, and setting of community strings
@@ -467,20 +433,35 @@ Helper = {
--
-- @param host string containing the host name or ip
-- @param port number containing the port to connect to
+ -- @param community string containing SNMP community
-- @param options A table with appropriate options:
-- * timeout - the timeout in milliseconds (Default: 5000)
-- * version - the SNMP version code (Default: 0 (SNMP V1))
-- @return o a new instance of Helper
- new = function( self, host, port, options )
+ new = function( self, host, port, community, options )
local o = {}
setmetatable(o, self)
self.__index = self
o.host = host
o.port = port
+
+ o.community = community or "public"
+ if community == nil then
+ local creds_store = creds.Credentials:new(creds.ALL_DATA, host, port)
+ for _,cs in ipairs({creds.State.PARAM, creds.State.VALID}) do
+ local account = creds_store:getCredentials(cs)()
+ if (account and account.pass) then
+ o.community = account.pass == "" and "" or account.pass
+ break
+ end
+ end
+ end
+
o.options = options or {
timeout = 5000,
version = 0
}
+
return o
end,
@@ -490,7 +471,7 @@ Helper = {
-- @return status true on success, false on failure
connect = function( self )
self.socket = nmap.new_socket()
- self.socket:set_timeout(self.timeout)
+ self.socket:set_timeout(self.options.timeout)
local status, err = self.socket:connect(self.host, self.port)
if ( not(status) ) then return false, err end
@@ -503,20 +484,19 @@ Helper = {
-- @return status False if there was an error, true otherwise.
-- @return response The raw response read from the socket.
request = function (self, message)
- local payload = encode( buildPacket(
- message,
- self.version,
- self.community or self.host.registry.snmpcommunity
- ) )
+ local payload = encode( buildPacket(
+ message,
+ self.version,
+ self.community
+ ) )
- local status, err = self.socket:send(payload)
- if not status then
- stdnse.debug2("snmp.Helper.request: Send to %s failed: %s", self.host.ip, err)
- return false, err
- end
+ local status, err = self.socket:send(payload)
+ if not status then
+ stdnse.debug2("snmp.Helper.request: Send to %s failed: %s", self.host.ip, err)
+ return false, err
+ end
- local response
- status, response = self.socket:receive_bytes(1)
+ return self.socket:receive_bytes(1)
end,
--- Sends an SNMP Get Next request
@@ -570,30 +550,21 @@ Helper = {
-- @return table containing oid and value
walk = function (self, base_oid)
- local snmp_table = {}
+ local snmp_table = { baseoid = base_oid }
local oid = base_oid
local options = {}
- while ( true ) do
- local status, response = self:getnext(options, oid)
-
- local snmpdata = fetchResponseValues( response )
-
- local value = snmpdata[1][1]
+ local status, snmpdata = self:getnext(options, oid)
+ while ( snmpdata and snmpdata[1] and snmpdata[1][1] and snmpdata[1][2] ) do
oid = snmpdata[1][2]
-
- if not oid:match( base_oid ) or base_oid == oid then
- break
- end
-
- table.insert(snmp_table, { oid = oid, value = value })
-
+ if not oid:match(base_oid) or base_oid == oid then break end
+
+ table.insert(snmp_table, { oid = oid, value = snmpdata[1][1] })
+ local _ -- NSE don't want you to use global even if it is _
+ _, snmpdata = self:getnext(options, oid)
end
- snmp_table.baseoid = base_oid
-
- return true, snmp_table
-
+ return status, snmp_table
end
}
diff --git a/scripts/snmp-brute.nse b/scripts/snmp-brute.nse
index af4b3bf8f..8a358314e 100644
--- a/scripts/snmp-brute.nse
+++ b/scripts/snmp-brute.nse
@@ -174,6 +174,7 @@ local sniff_snmp_responses = function(host, port, lport, result)
pcap:set_timeout(host.times.timeout * 1000 * 3)
pcap:pcap_open(host.interface, 300, false, "src host ".. host.ip .." and udp and src port 161 and dst port "..lport)
+ local communities = creds.Credentials:new(SCRIPT_NAME, host, port)
-- last_run indicated whether there will be only one more receive
local last_run = false
@@ -197,7 +198,7 @@ local sniff_snmp_responses = function(host, port, lport, result)
_, res = snmp.decode(response)
if type(res) == "table" then
- result.communities[ #(result.communities) + 1 ] = res[2]
+ communities:add(nil, res[2], creds.State.VALID)
else
result.status = false
result.msg = "Wrong type of SNMP response received"
@@ -233,7 +234,6 @@ action = function(host, port)
local condvar = nmap.condvar(result)
result.sent = false --whether the probes are sent
- result.communities = {} -- list of valid community strings
result.msg = "" -- Error/Status msg
result.status = true -- Status (is everything ok)
@@ -263,27 +263,7 @@ action = function(host, port)
socket:close()
if result.status then
- -- add the community strings to the creds database
- local c = creds.Credentials:new(SCRIPT_NAME, host, port)
- for _, community_string in ipairs(result.communities) do
- c:add("",community_string, creds.State.VALID)
- end
-
- -- insert the first community string as a snmpcommunity registry field
- local creds_iter = c:getCredentials()
- if creds_iter then
- local account = creds_iter()
- if account then
- if account.pass == "" then
- host.registry.snmpcommunity = ""
- else
- host.registry.snmpcommunity = account.pass
- end
- end
- end
-
- -- return output
- return c:getTable()
+ return creds.Credentials:new(SCRIPT_NAME, host, port):getTable()
else
stdnse.debug1("An error occurred: "..result.msg)
end
diff --git a/scripts/snmp-ios-config.nse b/scripts/snmp-ios-config.nse
index 36dbb243f..346fe6344 100644
--- a/scripts/snmp-ios-config.nse
+++ b/scripts/snmp-ios-config.nse
@@ -141,8 +141,7 @@ action = function(host, port)
return "\n ERROR: Failed to receive cisco configuration file"
end
- local result
- result = snmp.fetchFirst(response)
+ local result = response and response[1] and response[1][1]
if not result then
return
end
diff --git a/scripts/snmp-sysdescr.nse b/scripts/snmp-sysdescr.nse
index 8565dc62d..e30b59dbd 100644
--- a/scripts/snmp-sysdescr.nse
+++ b/scripts/snmp-sysdescr.nse
@@ -46,7 +46,7 @@ action = function(host, port)
-- since we got something back, the port is definitely open
nmap.set_port_state(host, port, "open")
- local result = snmp.fetchFirst(response)
+ local result = response and response[1] and response[1][1]
-- build a SNMP v1 packet
-- copied from packet capture of snmpget exchange
@@ -57,7 +57,7 @@ action = function(host, port)
return result
end
- local uptime = snmp.fetchFirst(response)
+ local uptime = response and response[1] and response[1][1]
if not uptime then
return
end