diff --git a/CHANGELOG b/CHANGELOG index 72c5e86d4..a2bc3196e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,17 @@ [NOT YET RELEASED] +o [NSE] Added a new library for ASN.1 parsing and adapted the SNMP library to + make use of it. Added 5 scripts that use the new libraries: + - snmp-netstat shows listening and connected sockets + - snmp-processes shows process information including name, pid, path and + parameters + - snmp-win32-services shows the names of running Windows services + - snmp-win32-shares shows the names and path of Windows shares + - snmp-win32-software shows a list of installed Windows software + - snmp-win32-users shows a list of local Windows users + [Patrik] + o Qualified an assertion to allow zero-byte sends in Nsock. Without this, an NSE script could cause this assertion failure by doing socket:send(""): @@ -9,7 +20,7 @@ o Qualified an assertion to allow zero-byte sends in Nsock. Without [David] o Added a service probe for Logitech SqueezeCenter command line interface - [Patrik] + [Patrik] o Improved PostgreSQL match lines by matching the line of the error to a specific version [Patrik]. diff --git a/nselib/asn1.lua b/nselib/asn1.lua new file mode 100644 index 000000000..9d747f370 --- /dev/null +++ b/nselib/asn1.lua @@ -0,0 +1,459 @@ +--- ASN1 functions. +-- +-- Large chunks of this code have been ripped right out from snmp.lua +-- +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- +-- @author Patrik Karlsson +-- + +-- Version 0.3 +-- Created 01/12/2010 - v0.1 - Created by Patrik Karlsson +-- Revised 01/28/2010 - v0.2 - Adapted to create a framework for SNMP, LDAP and future protocols +-- Revised 02/02/2010 - v0.3 - Changes: o Re-designed so that ASN1Encoder and ASN1Decoder are separate classes +-- o Each script or library should now create it's own Encoder and Decoder instance +-- + +module(... or "asn1",package.seeall) + +require("bit") + +BERCLASS = { + Universal = 0, + Application = 64, + ContextSpecific = 128, + Private = 192 +} + +--- The decoder class +-- +ASN1Decoder = { + + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, + + --- Registers the base simple type decoders + -- + registerBaseDecoders = function(self) + self.decoder = {} + + -- Boolean + self.decoder["01"] = function( self, encStr, elen, pos ) + local val = bin.unpack("H", encStr, pos) + if val ~= "FF" then + return pos, true + else + return pos, false + end + end + + -- Integer + self.decoder["02"] = function( self, encStr, elen, pos ) + return self.decodeInt(encStr, elen, pos) + end + + -- Octet String + self.decoder["04"] = function( self, encStr, elen, pos ) + return bin.unpack("A" .. elen, encStr, pos) + end + + -- Null + self.decoder["05"] = function( self, encStr, elen, pos ) + return pos, false + end + + -- Object Identifier + self.decoder["06"] = function( self, encStr, elen, pos ) + return self:decodeOID( encStr, elen, pos ) + end + + -- Context specific tags + -- + self.decoder["30"] = function( self, encStr, elen, pos ) + return self:decodeSeq(encStr, elen, pos) + end + end, + + --- Allows for registration of additional tag decoders + -- + -- @param table containing decoding functions @see tagDecoders + registerTagDecoders = function(self, tagDecoders) + self:registerBaseDecoders() + for k, v in pairs(tagDecoders) do + self.decoder[k] = v + end + end, + + --- Decodes the ASN.1's built-in simple types + -- + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding + -- @return The decoded value(s). + decode = function(self, encStr, pos) + + local etype, elen + local newpos = pos + + newpos, etype = bin.unpack("H1", encStr, newpos) + newpos, elen = self.decodeLength(encStr, newpos) + + if self.decoder[etype] then + return self.decoder[etype]( self, encStr, elen, newpos ) + else + stdnse.print_debug("no decoder for etype: " .. etype) + return newpos, nil + end + end, + + --- + -- Decodes length part of encoded value according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The length of the following value. + decodeLength = function(encStr, pos) + local elen + pos, elen = bin.unpack('C', encStr, pos) + if (elen > 128) then + elen = elen - 128 + local elenCalc = 0 + local elenNext + for i = 1, elen do + elenCalc = elenCalc * 256 + pos, elenNext = bin.unpack("C", encStr, pos) + elenCalc = elenCalc + elenNext + end + elen = elenCalc + end + return pos, elen + end, + + --- + -- Decodes a sequence according to ASN.1 basic encoding rules. + -- @param encStr Encoded string. + -- @param len Length of sequence in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The decoded sequence as a table. + decodeSeq = function(self, encStr, len, pos) + local seq = {} + local sPos = 1 + local sStr + pos, sStr = bin.unpack("A" .. len, encStr, pos) + while (sPos < len) do + local newSeq + sPos, newSeq = self:decode(sStr, sPos) + table.insert(seq, newSeq) + end + return pos, seq + end, + + -- Decode one component of an OID from a byte string. 7 bits of the component + -- are stored in each octet, most significant first, with the eigth bit set in + -- all octets but the last. These encoding rules come from + -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT + -- IDENTIFIER. + decode_oid_component = function(encStr, pos) + local octet + local n = 0 + + repeat + pos, octet = bin.unpack("C", encStr, pos) + n = n * 128 + bit.band(0x7F, octet) + until octet < 128 + + return pos, n + end, + + --- Decodes an OID from a sequence of bytes. + -- + -- @param encStr Encoded string. + -- @param len Length of sequence in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The OID as an array. + decodeOID = function(self, encStr, len, pos) + local last + local oid = {} + local octet + + last = pos + len - 1 + if pos <= last then + oid._snmp = '06' + pos, octet = bin.unpack("C", encStr, pos) + oid[2] = math.mod(octet, 40) + octet = octet - oid[2] + oid[1] = octet/40 + end + + while pos <= last do + local c + pos, c = self.decode_oid_component(encStr, pos) + oid[#oid + 1] = c + end + + return pos, oid + end, + + --- + -- Decodes length part of encoded value according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The length of the following value. + decodeLength = function(encStr, pos) + local elen + pos, elen = bin.unpack('C', encStr, pos) + if (elen > 128) then + elen = elen - 128 + local elenCalc = 0 + local elenNext + for i = 1, elen do + elenCalc = elenCalc * 256 + pos, elenNext = bin.unpack("C", encStr, pos) + elenCalc = elenCalc + elenNext + end + elen = elenCalc + end + return pos, elen + end, + + --- + -- Decodes an Integer according to ASN.1 basic encoding rules. + -- @param encStr Encoded string. + -- @param len Length of integer in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The decoded integer. + decodeInt = function(encStr, len, pos) + local hexStr + pos, hexStr = bin.unpack("H" .. len, encStr, pos) + local value = tonumber(hexStr, 16) + if (value >= math.pow(256, len)/2) then + value = value - math.pow(256, len) + end + return pos, value + end, + + --- + -- Decodes an SNMP packet or a part of it according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The decoded value(s). + dec = function(self, encStr, pos) + local result + local _ + _, result = self:decode(encStr, pos) + return result + end, + +} + +--- The encoder class +-- +ASN1Encoder = { + + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, + + --- + -- Encodes an ASN1 sequence + encodeSeq = function(self, seqData) + return bin.pack('HAA' , '30', self.encodeLength(string.len(seqData)), seqData) + end, + + --- + -- Encodes a given value according to ASN.1 basic encoding rules for SNMP + -- packet creation. + -- @param val Value to be encoded. + -- @return Encoded value. + encode = function(self, val) + local vtype = type(val) + + if self.encoder[vtype] then + return self.encoder[vtype](self,val) + else + return nil + end + + return '' + end, + + --- Allows for registration of additional tag encoders + -- + -- @param table containing encoding functions @see tagEncoders + registerTagEncoders = function(self, tagEncoders) + self:registerBaseEncoders() + for k, v in pairs(tagEncoders) do + self.encoder[k] = v + end + end, + + -- ASN.1 Simple types encoders + registerBaseEncoders = function(self) + self.encoder = {} + + -- Bolean encoder + self.encoder['boolean'] = function( self, val ) + if val then + return bin.pack('H','01 01 FF') + else + return bin.pack('H', '01 01 00') + end + end + + -- Integer encoder + self.encoder['number'] = function( self, val ) + local ival = self.encodeInt(val) + local len = self.encodeLength(string.len(ival)) + return bin.pack('HAA', '02', len, ival) + end + + -- Octet String encoder + self.encoder['string'] = function( self, val ) + local len = self.encodeLength(string.len(val)) + return bin.pack('HAA', '04', len, val) + end + + -- Null encoder + self.encoder['nil'] = function( self, val ) + return bin.pack('H', '05 00') + end + + end, + + -- Encode one component of an OID as a byte string. 7 bits of the component are + -- stored in each octet, most significant first, with the eigth bit set in all + -- octets but the last. These encoding rules come from + -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT + -- IDENTIFIER. + encode_oid_component = function(n) + local parts = {} + parts[1] = string.char(bit.mod(n, 128)) + while n >= 128 do + n = bit.rshift(n, 7) + parts[#parts + 1] = string.char(bit.mod(n, 128) + 0x80) + end + return string.reverse(table.concat(parts)) + end, + + --- + -- Encodes an Integer according to ASN.1 basic encoding rules. + -- @param val Value to be encoded. + -- @return Encoded integer. + encodeInt = function(val) + local lsb = 0 + if val > 0 then + local valStr = "" + while (val > 0) do + lsb = math.mod(val, 256) + valStr = valStr .. bin.pack("C", lsb) + val = math.floor(val/256) + end + if lsb > 127 then -- two's complement collision + valStr = valStr .. bin.pack("H", "00") + end + + return string.reverse(valStr) + elseif val < 0 then + local i = 1 + local tcval = val + 256 -- two's complement + while tcval <= 127 do + tcval = tcval + (math.pow(256, i) * 255) + i = i+1 + end + local valStr = "" + while (tcval > 0) do + lsb = math.mod(tcval, 256) + valStr = valStr .. bin.pack("C", lsb) + tcval = math.floor(tcval/256) + end + return string.reverse(valStr) + else -- val == 0 + return bin.pack("x") + end + end, + + --- + -- Encodes the length part of a ASN.1 encoding triplet using the "primitive, + -- definite-length" method. + -- @param val Value to be encoded. + -- @return Encoded length value. + encodeLength = function(len) + if len < 128 then + return string.char(len) + else + local parts = {} + + while len > 0 do + parts[#parts + 1] = string.char(bit.mod(len, 256)) + len = bit.rshift(len, 8) + end + + assert(#parts < 128) + return string.char(#parts + 0x80) .. string.reverse(table.concat(parts)) + end + end +} + + +--- +-- Converts a BER encoded type to a numeric value +-- This allows it to be used in the encoding function +-- +-- @param class number - see BERCLASS +-- @param constructed boolean (true if constructed, false if primitive) +-- @param number numeric +-- @return number to be used with encode +function BERtoInt(class, constructed, number) + + local asn1_type = class + number + + if constructed == true then + asn1_type = asn1_type + 32 + end + + return asn1_type +end + +--- +-- Converts an integer to a BER encoded type table +-- +-- @param i number containing the value to decode +-- @return table with the following entries class, constructed, +-- primitive and number +function intToBER( i ) + local ber = {} + + if bit.band( i, BERCLASS.Application ) == BERCLASS.Application then + ber.class = BERCLASS.Application + elseif bit.band( i, BERCLASS.ContextSpecific ) == BERCLASS.ContextSpecific then + ber.class = BERCLASS.ContextSpecific + elseif bit.band( i, BERCLASS.Private ) == BERCLASS.Private then + ber.class = BERCLASS.Private + else + ber.class = BERCLASS.Universal + end + if bit.band( i, 32 ) == 32 then + ber.constructed = true + ber.number = i - ber.class - 32 + else + ber.primitive = true + ber.number = i - ber.class + end + return ber +end + + diff --git a/nselib/snmp.lua b/nselib/snmp.lua index 5ee667314..31107b9cb 100644 --- a/nselib/snmp.lua +++ b/nselib/snmp.lua @@ -3,254 +3,158 @@ -- @args snmpcommunity The community string to use. If not given, it is -- "public", or whatever is passed to buildPacket. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html - +-- +-- +-- module(... or "snmp",package.seeall) require("bit") +require("asn1") ---- --- Encodes an Integer according to ASN.1 basic encoding rules. --- @param val Value to be encoded. --- @return Encoded integer. -local function encodeInt(val) - local lsb = 0 - if val > 0 then - local valStr = "" - while (val > 0) do - lsb = math.mod(val, 256) - valStr = valStr .. bin.pack("C", lsb) - val = math.floor(val/256) - end - if lsb > 127 then -- two's complement collision - valStr = valStr .. bin.pack("H", "00") - end +-- SNMP ASN.1 Encoders +local tagEncoder = {} - return string.reverse(valStr) - elseif val < 0 then - local i = 1 - local tcval = val + 256 -- two's complement - while tcval <= 127 do - tcval = tcval + (math.pow(256, i) * 255) - i = i+1 - end - local valStr = "" - while (tcval > 0) do - lsb = math.mod(tcval, 256) - valStr = valStr .. bin.pack("C", lsb) - tcval = math.floor(tcval/256) - end - return string.reverse(valStr) - else -- val == 0 - return bin.pack("x") - end +-- Override the boolean encoder +tagEncoder['boolean'] = function(self, val) + return bin.pack('H', '05 00') end +-- Complex tag encoders +tagEncoder['table'] = function(self, val) + if val._snmp == '06' then -- OID + local oidStr = string.char(val[1]*40 + val[2]) + for i = 3, #val do + oidStr = oidStr .. self.encode_oid_component(val[i]) + end + return bin.pack("HAA", '06', self.encodeLength(#oidStr), oidStr) ---- --- Encodes the length part of a ASN.1 encoding triplet using the "primitive, --- definite-length" method. --- @param val Value to be encoded. --- @return Encoded length value. -local function encodeLength(len) - if len < 128 then - return string.char(len) - else - local parts = {} + elseif (val._snmp == '40') then -- ipAddress + return bin.pack("HC4", '40 04', unpack(val)) + + -- counter or gauge or timeticks or opaque + elseif (val._snmp == '41' or val._snmp == '42' or val._snmp == '43' or val._snmp == '44') then + local val = self:encodeInt(val[1]) + return bin.pack("HAA", val._snmp, self.encodeLength(string.len(val)), val) + end + + local encVal = "" + for _, v in ipairs(val) do + encVal = encVal .. self:encode(v) -- todo: buffer? + end - while len > 0 do - parts[#parts + 1] = string.char(bit.mod(len, 256)) - len = bit.rshift(len, 8) - end - - assert(#parts < 128) - return string.char(#parts + 0x80) .. string.reverse(table.concat(parts)) - end + local tableType = bin.pack("H", "30") + if (val["_snmp"]) then + tableType = bin.pack("H", val["_snmp"]) + end + return bin.pack('AAA', tableType, self.encodeLength(string.len(encVal)), encVal) end - --- Encode one component of an OID as a byte string. 7 bits of the component are --- stored in each octet, most significant first, with the eigth bit set in all --- octets but the last. These encoding rules come from --- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT --- IDENTIFIER. -local function encode_oid_component(n) - local parts = {} - parts[1] = string.char(bit.mod(n, 128)) - while n >= 128 do - n = bit.rshift(n, 7) - parts[#parts + 1] = string.char(bit.mod(n, 128) + 0x80) - end - return string.reverse(table.concat(parts)) -end - - --- -- Encodes a given value according to ASN.1 basic encoding rules for SNMP -- packet creation. -- @param val Value to be encoded. -- @return Encoded value. function encode(val) - local vtype = type(val) - if (vtype == 'number') then - local ival = encodeInt(val) - local len = encodeLength(string.len(ival)) - return bin.pack('HAA', '02', len, ival) - end - if (vtype == 'string') then - local len = encodeLength(string.len(val)) - return bin.pack('HAA', '04', len, val) - end - if (vtype == 'nil' or vtype == 'boolean') then - return bin.pack('H', '05 00') - end - if (vtype == 'table') then -- complex data types - if val._snmp == '06' then -- OID - local oidStr = string.char(val[1]*40 + val[2]) - for i = 3, #val do - oidStr = oidStr .. encode_oid_component(val[i]) - end - return bin.pack("HAA", '06', encodeLength(#oidStr), oidStr) - elseif (val._snmp == '40') then -- ipAddress - return bin.pack("HC4", '40 04', unpack(val)) - elseif (val._snmp == '41') then -- counter - local cnt = encodeInt(val[1]) - return bin.pack("HAA", val._snmp, encodeLength(string.len(cnt)), cnt) - elseif (val._snmp == '42') then -- gauge - local gauge = encodeInt(val[1]) - return bin.pack("HAA", val._snmp, encodeLength(string.len(gauge)), gauge) - elseif (val._snmp == '43') then -- timeticks - local ticks = encodeInt(val[1]) - return bin.pack("HAA", val._snmp, encodeLength(string.len(ticks)), ticks) - elseif (val._snmp == '44') then -- opaque - return bin.pack("HAA", val._snmp, encodeLength(string.len(val[1])), val[1]) - end - local encVal = "" - for _, v in ipairs(val) do - encVal = encVal .. encode(v) -- todo: buffer? - end - local tableType = bin.pack("H", "30") - if (val["_snmp"]) then - tableType = bin.pack("H", val["_snmp"]) - end - return bin.pack('AAA', tableType, encodeLength(string.len(encVal)), encVal) - end - return '' + local vtype = type(val) + local encoder = asn1.ASN1Encoder:new() + encoder:registerTagEncoders( tagEncoder ) + + + local encVal = encoder:encode(val) + + if encVal then + return encVal + end + + return '' end +-- SNMP ASN.1 Decoders +local tagDecoder = {} ---- --- Decodes length part of encoded value according to ASN.1 basic encoding --- rules. --- @param encStr Encoded string. --- @param pos Current position in the string. --- @return The position after decoding. --- @return The length of the following value. -local function decodeLength(encStr, pos) - local elen - pos, elen = bin.unpack('C', encStr, pos) - if (elen > 128) then - elen = elen - 128 - local elenCalc = 0 - local elenNext - for i = 1, elen do - elenCalc = elenCalc * 256 - pos, elenNext = bin.unpack("C", encStr, pos) - elenCalc = elenCalc + elenNext - end - elen = elenCalc - end - return pos, elen -end - - ---- --- Decodes an Integer according to ASN.1 basic encoding rules. --- @param encStr Encoded string. --- @param len Length of integer in bytes. --- @param pos Current position in the string. --- @return The position after decoding. --- @return The decoded integer. -local function decodeInt(encStr, len, pos) - local hexStr - pos, hexStr = bin.unpack("H" .. len, encStr, pos) - local value = tonumber(hexStr, 16) - if (value >= math.pow(256, len)/2) then - value = value - math.pow(256, len) - end - return pos, value -end - --- Decode one component of an OID from a byte string. 7 bits of the component --- are stored in each octet, most significant first, with the eigth bit set in --- all octets but the last. These encoding rules come from --- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT --- IDENTIFIER. -local function decode_oid_component(encStr, pos) - local octet - local n = 0 - - repeat - pos, octet = bin.unpack("C", encStr, pos) - n = n * 128 + bit.band(0x7F, octet) - until octet < 128 - - return pos, n -end - ---- Decodes an OID from a sequence of bytes. +-- Application specific tags -- --- @param encStr Encoded string. --- @param len Length of sequence in bytes. --- @param pos Current position in the string. --- @return The position after decoding. --- @return The OID as an array. -local function decodeOID(encStr, len, pos) - local last - local oid = {} - local octet +-- IP Address - last = pos + len - 1 - if pos <= last then - oid._snmp = '06' - pos, octet = bin.unpack("C", encStr, pos) - oid[2] = math.mod(octet, 40) - octet = octet - oid[2] - oid[1] = octet/40 - end - - while pos <= last do - local c - pos, c = decode_oid_component(encStr, pos) - oid[#oid + 1] = c - end - - return pos, oid +tagDecoder["40"] = function( self, encStr, elen, pos ) + local ip = {} + pos, ip[1], ip[2], ip[3], ip[4] = bin.unpack("C4", encStr, pos) + ip._snmp = '40' + return pos, ip end ---- --- Decodes a sequence according to ASN.1 basic encoding rules. --- @param encStr Encoded string. --- @param len Length of sequence in bytes. --- @param pos Current position in the string. --- @return The position after decoding. --- @return The decoded sequence as a table. -local function decodeSeq(encStr, len, pos) - local seq = {} - local sPos = 1 - local i = 1 - local sStr - pos, sStr = bin.unpack("A" .. len, encStr, pos) - while (sPos < len) do - local newSeq - sPos, newSeq = decode(sStr, sPos) - table.insert(seq, newSeq) - i = i + 1 - end - return pos, seq +-- Counter +tagDecoder["41"] = function( self, encStr, elen, pos ) + local tbl = {} + pos, tbl[1] = self.decodeInt(encStr, elen, pos) + tbl._snmp = '41' + return pos, tbl end +-- Gauge +tagDecoder["42"] = function( self, encStr, elen, pos ) + local tbl = {} + pos, tbl[1] = self.decodeInt(encStr, elen, pos) + tbl._snmp = '41' + return pos, tbl +end + +-- TimeTicks +tagDecoder["43"] = function( self, encStr, elen, pos ) + local tbl = {} + pos, tbl[1] = self.decodeInt(encStr, elen, pos) + tbl._snmp = '41' + return pos, tbl +end + +-- Opaque +tagDecoder["44"] = function( self, encStr, elen, pos ) + local tbl = {} + pos, tbl[1] = self.decodeInt(encStr, elen, pos) + tbl._snmp = '41' + return pos, tbl +end + +-- Context specific tags +-- +tagDecoder["A0"] = function( self, encStr, elen, pos ) + local seq + pos, seq = self:decodeSeq(encStr, elen, pos) + seq._snmp = "A0" + return pos, seq +end + +tagDecoder["A1"] = function( self, encStr, elen, pos ) + local seq + pos, seq = self:decodeSeq(encStr, elen, pos) + seq._snmp = "A1" + return pos, seq +end + +tagDecoder["A2"] = function( self, encStr, elen, pos ) + local seq + pos, seq = self:decodeSeq(encStr, elen, pos) + seq._snmp = "A2" + return pos, seq +end + +tagDecoder["A3"] = function( self, encStr, elen, pos ) + local seq + pos, seq = self:decodeSeq(encStr, elen, pos) + seq._snmp = "A3" + return pos, seq +end + +tagDecoder["A4"] = function( self, encStr, elen, pos ) + local seq + pos, seq = self:decodeSeq(encStr, elen, pos) + seq._snmp = "A4" + return pos, seq +end + + --- -- Decodes an SNMP packet or a part of it according to ASN.1 basic encoding -- rules. @@ -259,81 +163,10 @@ end -- @return The position after decoding -- @return The decoded value(s). function decode(encStr, pos) - local etype, elen - pos, etype = bin.unpack("H1", encStr, pos) - pos, elen = decodeLength(encStr, pos) - if (etype == "02") then -- INTEGER - return decodeInt(encStr, elen, pos) - - elseif (etype == "04") then -- STRING - return bin.unpack("A" .. elen, encStr, pos) - - elseif (etype == "05") then -- NULL - return pos, false + local decoder = asn1.ASN1Decoder:new() + decoder:registerTagDecoders( tagDecoder ) - elseif (etype == "06") then -- OID - return decodeOID( encStr, elen, pos ) - - elseif (etype == "30") then -- sequence - local seq - pos, seq = decodeSeq(encStr, elen, pos) - return pos, seq - - elseif (etype == "A0") then -- getReq - local seq - pos, seq = decodeSeq(encStr, elen, pos) - seq._snmp = etype - return pos, seq - - elseif (etype == "A1") then -- getNextReq - local seq - pos, seq = decodeSeq(encStr, elen, pos) - seq._snmp = etype - return pos, seq - - elseif (etype == "A2") then -- getResponse - local seq - pos, seq = decodeSeq(encStr, elen, pos) - seq._snmp = etype - return pos, seq - - elseif (etype == "A3") then -- setReq - local seq - pos, seq = decodeSeq(encStr, elen, pos) - seq._snmp = etype - return pos, seq - elseif (etype == "A4") then -- Trap - local seq - pos, seq = decodeSeq(encStr, elen, pos) - seq._snmp = etype - return pos, seq - elseif (etype == '40') then -- App: IP-Address - local ip = {} - pos, ip[1], ip[2], ip[3], ip[4] = bin.unpack("C4", encStr, pos) - ip._snmp = '40' - return pos, ip - elseif (etype == '41') then -- App: counter - local cnt = {} - pos, cnt[1] = decodeInt(encStr, elen, pos) - cnt._snmp = '41' - return pos, cnt - elseif (etype == '42') then -- App: gauge - local gauge = {} - pos, gauge[1] = decodeInt(encStr, elen, pos) - gauge._snmp = '42' - return pos, gauge - elseif (etype == '43') then -- App: TimeTicks - local ticks = {} - pos, ticks[1] = decodeInt(encStr, elen, pos) - ticks._snmp = '43' - return pos, ticks - elseif (etype == '44') then -- App: opaque - local opaque = {} - pos, opaque[1] = bin.unpack("A" .. elen, encStr, pos) - opaque._snmp = '44' - return pos, opaque - end - return pos, nil + return decoder:decode( encStr, pos ) end --- @@ -416,7 +249,7 @@ end function buildGetNextRequest(options, ...) if not options then options = {} end - if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end + if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end if not options.err then options.err = 0 end if not options.errIdx then options.errIdx = 0 end @@ -431,7 +264,7 @@ function buildGetNextRequest(options, ...) payload[i] = {} payload[i][1] = select(i, ...) if type(payload[i][1]) == "string" then - payload[i][1] = str2oid(payload[i][1]) + payload[i][1] = str2oid(payload[i][1]) end payload[i][2] = false end @@ -648,8 +481,11 @@ end -- @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 + 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 diff --git a/scripts/snmp-netstat.nse b/scripts/snmp-netstat.nse new file mode 100644 index 000000000..d0bf644ad --- /dev/null +++ b/scripts/snmp-netstat.nse @@ -0,0 +1,145 @@ +description = [[ +Attempts to query SNMP for a netstat like output +]] + +--- +-- @output +-- | snmp-netstat: +-- | TCP 0.0.0.0:21 0.0.0.0:2256 +-- | TCP 0.0.0.0:80 0.0.0.0:8218 +-- | TCP 0.0.0.0:135 0.0.0.0:53285 +-- | TCP 0.0.0.0:389 0.0.0.0:38990 +-- | TCP 0.0.0.0:445 0.0.0.0:49158 +-- | TCP 127.0.0.1:389 127.0.0.1:1045 +-- | TCP 127.0.0.1:389 127.0.0.1:1048 +-- | UDP 192.168.56.3:137 *:* +-- | UDP 192.168.56.3:138 *:* +-- | UDP 192.168.56.3:389 *:* +-- |_ UDP 192.168.56.3:464 *:* + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.1 +-- Created 01/19/2010 - v0.1 - created by Patrik Karlsson + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + local lip = oid:match( "^" .. base_oid .. "%.(%d+%.%d+%.%d+%.%d+)") or "" + local lport = oid:match( "^" .. base_oid .. "%.%d+%.%d+%.%d+%.%d+%.(%d+)") + local fip = oid:match( "^" .. base_oid .. "%.%d+%.%d+%.%d+%.%d+%.%d+%.(%d+%.%d+%.%d+%.%d+)") or "*:*" + local fport = oid:match( "^" .. base_oid .. "%.%d+%.%d+%.%d+%.%d+%.%d+%.%d+%.%d+%.%d+%.%d+%.(%d+)") + + if lport and lport ~= "0" then + lip = lip .. ":" .. lport + end + + if fport and fport ~= "0" then + fip = fip .. ":" .. fport + end + + + value = string.format("%-20s %s", lip, fip ) + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl, prefix ) + + local new_tab = {} + + for _, v in ipairs( tbl ) do + table.insert( new_tab, string.format( "%-4s %s", prefix, v.value ) ) + end + + return new_tab + +end + +function table_merge( t1, t2 ) + for _, v in ipairs(t2) do + table.insert(t1, v) + end + + return t1 +end + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local tcp_oid = "1.3.6.1.2.1.6.13.1.1" + local udp_oid = "1.3.6.1.2.1.7.5.1.1" + local netstat = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + local tcp = snmp_walk( socket, tcp_oid ) + local udp = snmp_walk( socket, udp_oid ) + + if ( tcp == nil ) or ( #tcp == 0 ) or ( udp==nil ) or ( #udp == 0 ) then + return + end + + tcp = process_answer(tcp, "TCP") + udp = process_answer(udp, "UDP") + netstat = table_merge( tcp, udp ) + + nmap.set_port_state(host, port, "open") + socket:close() + + return stdnse.format_output( true, netstat ) +end + diff --git a/scripts/snmp-processes.nse b/scripts/snmp-processes.nse new file mode 100644 index 000000000..c630b618c --- /dev/null +++ b/scripts/snmp-processes.nse @@ -0,0 +1,176 @@ +description = [[ +Attempts to enumerate running processes through SNMP +]] + +--- +-- @output +-- | snmp-processes: +-- | System Idle Process +-- | PID: 1 +-- | System +-- | PID: 4 +-- | smss.exe +-- | Path: \SystemRoot\System32\ +-- | PID: 256 +-- | csrss.exe +-- | Path: C:\WINDOWS\system32\ +-- | Params: ObjectDirectory=\Windows SharedSection=1024,3072,512 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserS +-- | PID: 308 +-- | winlogon.exe +-- | PID: 332 +-- | services.exe +-- | Path: C:\WINDOWS\system32\ +-- | PID: 380 +-- | lsass.exe +-- | Path: C:\WINDOWS\system32\ +-- |_ PID: 392 + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.3 +-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson +-- Revised 01/19/2010 - v0.2 - fixed loop that would occure if a mib did not exist +-- Revised 01/19/2010 - v0.2 - removed debugging output and renamed file + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + socket:close() + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Gets a value for the specified oid +-- +-- @param tbl table containing oid and value +-- @param oid string containing the object id for which the value should be extracted +-- @return value of relevant type or nil if oid was not found +function get_value_from_table( tbl, oid ) + + for _, v in ipairs( tbl ) do + if v.oid == oid then + return v.value + end + end + + return nil +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl ) + + local swrun_name = "1.3.6.1.2.1.25.4.2.1.2" + local swrun_pid = "1.3.6.1.2.1.25.4.2.1.1" + local swrun_path = "1.3.6.1.2.1.25.4.2.1.4" + local swrun_params = "1.3.6.1.2.1.25.4.2.1.5" + local new_tbl = {} + + for _, v in ipairs( tbl ) do + + if ( v.oid:match("^" .. swrun_name) ) then + local item = {} + local objid = v.oid:gsub( "^" .. swrun_name, swrun_path) + local value = get_value_from_table( tbl, objid ) + + if value and value:len() > 0 then + table.insert( item, ("Path: %s"):format( value ) ) + end + + objid = v.oid:gsub( "^" .. swrun_name, swrun_params) + value = get_value_from_table( tbl, objid ) + + if value and value:len() > 0 then + table.insert( item, ("Params: %s"):format( value ) ) + end + + objid = v.oid:gsub( "^" .. swrun_name, swrun_pid) + value = get_value_from_table( tbl, objid ) + + if value then + table.insert( item, ("PID: %s"):format( value ) ) + end + + item.name = v.value + table.insert( item, value ) + table.insert( new_tbl, item ) + end + + end + + return new_tbl + +end + + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local data, snmpoid = nil, "1.3.6.1.2.1.25.4.2" + local shares = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + shares = snmp_walk( socket, snmpoid ) + + if ( shares == nil ) or ( #shares == 0 ) then + return + end + + shares = process_answer( shares ) + + nmap.set_port_state(host, port, "open") + + return stdnse.format_output( true, shares ) +end + diff --git a/scripts/snmp-win32-services.nse b/scripts/snmp-win32-services.nse new file mode 100644 index 000000000..86646ed3e --- /dev/null +++ b/scripts/snmp-win32-services.nse @@ -0,0 +1,121 @@ +description = [[ +Attempts to enumerate Windows Services through SNMP +]] + +--- +-- @output +-- | snmp-win32-services: +-- | Apache Tomcat +-- | Application Experience Lookup Service +-- | Application Layer Gateway Service +-- | Automatic Updates +-- | COM+ Event System +-- | COM+ System Application +-- | Computer Browser +-- | Cryptographic Services +-- | DB2 - DB2COPY1 - DB2 +-- | DB2 Management Service (DB2COPY1) +-- | DB2 Remote Command Server (DB2COPY1) +-- | DB2DAS - DB2DAS00 +-- |_ DCOM Server Process Launcher + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.2 +-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson +-- Revised 01/19/2010 - v0.2 - fixed loop that would occure if a mib did not exist + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + socket:close() + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl ) + + local new_tab = {} + + for _, v in ipairs( tbl ) do + table.insert( new_tab, v.value ) + end + + table.sort( new_tab ) + + return new_tab + +end + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local snmpoid = "1.3.6.1.4.1.77.1.2.3.1.1" + local services = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + services = snmp_walk( socket, snmpoid ) + + if ( services == nil ) or ( #services == 0 ) then + return + end + + services = process_answer(services) + nmap.set_port_state(host, port, "open") + + return stdnse.format_output( true, services ) +end + diff --git a/scripts/snmp-win32-shares.nse b/scripts/snmp-win32-shares.nse new file mode 100644 index 000000000..0cd9d03d6 --- /dev/null +++ b/scripts/snmp-win32-shares.nse @@ -0,0 +1,142 @@ +description = [[ +Attempts to enumerate Windows Shares through SNMP +]] + +--- +-- @output +-- | snmp-win32-shares: +-- | SYSVOL +-- | C:\WINDOWS\sysvol\sysvol +-- | NETLOGON +-- | C:\WINDOWS\sysvol\sysvol\inspectit-labb.local\SCRIPTS +-- | Webapps +-- |_ C:\Program Files\Apache Software Foundation\Tomcat 5.5\webapps\ROOT + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.2 +-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson +-- Revised 01/19/2010 - v0.2 - fixed loop that would occure if a mib did not exist + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + socket:close() + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Gets a value for the specified oid +-- +-- @param tbl table containing oid and value +-- @param oid string containing the object id for which the value should be extracted +-- @return value of relevant type or nil if oid was not found +function get_value_from_table( tbl, oid ) + + for _, v in ipairs( tbl ) do + if v.oid == oid then + return v.value + end + end + + return nil +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl ) + + local share_name = "1.3.6.1.4.1.77.1.2.27.1.1" + local share_path = "1.3.6.1.4.1.77.1.2.27.1.2" + local new_tbl = {} + + for _, v in ipairs( tbl ) do + + if ( v.oid:match("^" .. share_name) ) then + local item = {} + local objid = v.oid:gsub( "^" .. share_name, share_path) + local path = get_value_from_table( tbl, objid ) + + item.name = v.value + table.insert( item, path ) + table.insert( new_tbl, item ) + end + + end + + return new_tbl + +end + + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local data, snmpoid = nil, "1.3.6.1.4.1.77.1.2.27" + local shares = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + shares = snmp_walk( socket, snmpoid ) + + if ( shares == nil ) or ( #shares == 0 ) then + return + end + + shares = process_answer( shares ) + + nmap.set_port_state(host, port, "open") + + return stdnse.format_output( true, shares ) +end + diff --git a/scripts/snmp-win32-software.nse b/scripts/snmp-win32-software.nse new file mode 100644 index 000000000..216ff87e1 --- /dev/null +++ b/scripts/snmp-win32-software.nse @@ -0,0 +1,147 @@ +description = [[ +Attempts to enumerate installed software through SNMP +]] + +--- +-- @output +-- | snmp-win32-software: +-- | Apache Tomcat 5.5 (remove only); 2007-09-15 15:13:18 +-- | Microsoft Internationalized Domain Names Mitigation APIs; 2007-09-15 15:13:18 +-- | Security Update for Windows Media Player (KB911564); 2007-09-15 15:13:18 +-- | Security Update for Windows Server 2003 (KB924667-v2); 2007-09-15 15:13:18 +-- | Security Update for Windows Media Player 6.4 (KB925398); 2007-09-15 15:13:18 +-- | Security Update for Windows Server 2003 (KB925902); 2007-09-15 15:13:18 +-- |_ Windows Internet Explorer 7; 2007-09-15 15:13:18 + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.2 +-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson +-- Revised 01/19/2010 - v0.2 - fixed loop that would occure if a mib did not exist + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + socket:close() + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Gets a value for the specified oid +-- +-- @param tbl table containing oid and value +-- @param oid string containing the object id for which the value should be extracted +-- @return value of relevant type or nil if oid was not found +function get_value_from_table( tbl, oid ) + + for _, v in ipairs( tbl ) do + if v.oid == oid then + return v.value + end + end + + return nil +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl ) + + local sw_name = "1.3.6.1.2.1.25.6.3.1.2" + local sw_date = "1.3.6.1.2.1.25.6.3.1.5" + local new_tbl = {} + + for _, v in ipairs( tbl ) do + + if ( v.oid:match("^" .. sw_name) ) then + local objid = v.oid:gsub( "^" .. sw_name, sw_date) + local install_date = get_value_from_table( tbl, objid ) + local sw_item + + local _, year, month, day, hour, min, sec = bin.unpack( ">SCCCCC", install_date ) + install_date = ("%02d-%02d-%02d %02d:%02d:%02d"):format( year, month, day, hour, min, sec ) + + sw_item = ("%s; %s"):format(v.value ,install_date) + table.insert( new_tbl, sw_item ) + end + + end + + table.sort( new_tbl ) + return new_tbl + +end + + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local data, snmpoid = nil, "1.3.6.1.2.1.25.6.3.1" + local sw = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + sw = snmp_walk( socket, snmpoid ) + + if ( sw == nil ) or ( #sw == 0 ) then + return + end + + sw = process_answer( sw ) + + nmap.set_port_state(host, port, "open") + + return stdnse.format_output( true, sw ) +end + diff --git a/scripts/snmp-win32-users.nse b/scripts/snmp-win32-users.nse new file mode 100644 index 000000000..607e83026 --- /dev/null +++ b/scripts/snmp-win32-users.nse @@ -0,0 +1,118 @@ +description = [[ +Attempts to enumerate User Accounts through SNMP +]] + +--- +-- @output +-- | snmp-win32-users: +-- | Administrator +-- | Guest +-- | IUSR_EDUSRV011 +-- | IWAM_EDUSRV011 +-- | SUPPORT_388945a0 +-- | Tomcat +-- | db2admin +-- | ldaptest +-- |_ patrik + + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + +-- Version 0.2 +-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson +-- Revised 01/19/2010 - v0.2 - fixed loop that would occure if a mib did not exist + +require "shortport" +require "snmp" + +portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) + +--- Walks the MIB Tree +-- +-- @param socket socket already connected to the server +-- @base_oid string containing the base object ID to walk +-- @return table containing oid and value +function snmp_walk( socket, base_oid ) + + local catch = function() socket:close() end + local try = nmap.new_try(catch) + + local snmp_table = {} + local oid = base_oid + + while ( true ) do + + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + options.reqId = 28428 -- unnecessary? + payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) + + try(socket:send(payload)) + response = try( socket:receive_bytes(1) ) + + snmpdata = snmp.fetchResponseValues( response ) + + value = snmpdata[1][1] + oid = snmpdata[1][2] + + if not oid:match( base_oid ) or base_oid == oid then + break + end + + item.oid = oid + item.value = value + + table.insert( snmp_table, item ) + + end + + socket:close() + snmp_table.baseoid = base_oid + + return snmp_table + +end + +--- Processes the table and creates the script output +-- +-- @param tbl table containing oid and value +-- @return table suitable for stdnse.format_output +function process_answer( tbl ) + + local new_tab = {} + + for _, v in ipairs( tbl ) do + table.insert( new_tab, v.value ) + end + + table.sort( new_tab ) + + return new_tab + +end + +action = function(host, port) + + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local snmpoid = "1.3.6.1.4.1.77.1.2.25" + local users = {} + + socket:set_timeout(5000) + try(socket:connect(host.ip, port.number, "udp")) + + users = snmp_walk( socket, snmpoid ) + users = process_answer( users ) + + if ( users == nil ) or ( #users == 0 ) then + return + end + + nmap.set_port_state(host, port, "open") + + return stdnse.format_output( true, users ) +end +