From cf873707cdacca976e77d469feb9ae732101e8af Mon Sep 17 00:00:00 2001 From: patrik Date: Wed, 15 Jun 2011 06:23:30 +0000 Subject: [PATCH] o [NSE] Added minimal Service Location Protocol (SLP) library and the script broadcast-novell-locate that detects servers running eDirectory. [Patrik] --- CHANGELOG | 3 + nselib/srvloc.lua | 364 ++++++++++++++++++++++++++++ scripts/broadcast-novell-locate.nse | 69 ++++++ scripts/script.db | 1 + 4 files changed, 437 insertions(+) create mode 100644 nselib/srvloc.lua create mode 100644 scripts/broadcast-novell-locate.nse diff --git a/CHANGELOG b/CHANGELOG index f9c9216e4..1e47fc10c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added minimal Service Location Protocol (SLP) library and the script + broadcast-novell-locate that detects servers running eDirectory. [Patrik] + o [ncat] ncat now listens on localhost and ::1 when you do ncat -l. If you specify an address or use -4,-6 it works as before. diff --git a/nselib/srvloc.lua b/nselib/srvloc.lua new file mode 100644 index 000000000..384b33c22 --- /dev/null +++ b/nselib/srvloc.lua @@ -0,0 +1,364 @@ +--- A relatively small implementation of the Service Location Protocol. +-- It was initially designed to support requests for discovering Novell NCP +-- servers, but should work for any other service as well. +-- +-- The implementation is based on the following classes: +-- * Request.Service +-- - Contains necessary code to produce a service request +-- +-- * Request.Attributes +-- - Contains necessary code to produce a attribute request +-- +-- * Reply.Service +-- - Contains necessary code to process and parse the response to the +-- service request +-- +-- * Reply.Attributes +-- - Contains necessary code to process and parse the response to the +-- attribute request +-- +-- The following code illustrates intended use of the library: +-- +-- +-- local helper = srvloc.Helper:new() +-- local status, tree = helper:ServiceRequest("ndap.novell", "DEFAULT") +-- if ( status ) then tree = tree:match("%/%/%/(.*)%.$") end +-- + +--@author Patrik Karlsson +--@copyright Same as Nmap--See http://nmap.org/book/man-legal.html + +-- Version 0.1 +-- Created 24/04/2011 - v0.1 - created by Patrik Karlsson + +module(... or "srvloc", package.seeall) + +require 'bit' + +PacketFunction = { + SERVICE_REQUEST = 1, + SERVICE_REPLY = 2, + ATTRIB_REQUEST = 6, +} + +Reply = { + + Service = { + + --- Creates a new instance of the Reply.Service class + -- @param data string containing the raw reply as read from the socket + -- @return o instance of Reply.Service + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + o:parse(data) + return o + end, + + --- Parses the service reply raw packet data + -- @param data string containing the raw reply as read from the socket + parse = function(self, data) + local pos + local len_hi, len_lo + + pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) + self.len = bit.lshift(len_hi, 16) + len_lo + pos, self.flags = bin.unpack(">S", data, pos) + + local neo_hi, neo_lo + pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) + self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo + + local lang_tag_len + pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) + pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) + + local no_urls, reserved, url_len + pos, self.error_code, no_urls, reserved, self.url_lifetime, + url_len = bin.unpack(">SSCSS", data, pos) + + local num_auths + pos, self.url, num_auths = bin.unpack("A" .. url_len .. "C", data, pos) + end, + + --- Attempts to create an instance by reading data off the socket + -- @param socket socket conected to the SRVLOC service + -- @return new instance of the Reply.Service class + fromSocket = function(socket) + local status, data = socket:receive() + if ( not(status) ) then return end + return Reply.Service:new(data) + end, + + --- Gets the url value from the reply + -- @return uri string containing the reply url + getUrl = function(self) return self.url end, + }, + + Attribute = { + + --- Creates a new instance of Reply.Attribute + -- @param data string containing the raw reply as read from the socket + -- @return o instance of Reply.Attribute + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + o:parse(data) + return o + end, + + --- Parses the service reply raw packet data + -- @param data string containing the raw reply as read from the socket + parse = function(self, data) + local pos + local len_hi, len_lo + + pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) + self.len = bit.lshift(len_hi, 16) + len_lo + pos, self.flags = bin.unpack(">S", data, pos) + + local neo_hi, neo_lo + pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) + self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo + + local lang_tag_len + pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) + pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) + + local attrib_list_len + pos, self.error_code, attrib_list_len = bin.unpack(">SS", data, pos) + + pos, self.attrib_list = bin.unpack("A"..attrib_list_len, data, pos) + + local num_auths + pos, num_auths = bin.unpack("C", data, pos) + end, + + --- Attempts to create an instance by reading data off the socket + -- @param socket socket conected to the SRVLOC service + -- @return new instance of the Reply.Attribute class + fromSocket = function(socket) + local status, data = socket:receive() + if ( not(status) ) then return end + return Reply.Attribute:new(data) + end, + + --- Gets the attribute list + -- @return attrib_list + getAttribList = function(self) return self.attrib_list end, + } +} + + +Request = { + + -- The attribute request + Attribute = { + + --- Creates a new instance of the Attribue request + -- @return o instance of Attribute + new = function(self) + local o = { + lang_tag = "en", version = 2, service_type = "", + scope = "", next_extension_offset = 0, + prev_resp_list_len = 0, slp_spi_len = 0 } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Sets the request scope + -- @param scope string containing the request scope + setScope = function(self, scope) self.scope = scope end, + + --- Sets the language tag + -- @param lang string containing the language + setLangTag = function(self, lang) self.lang_tag = lang end, + + --- Sets the request flags + -- @param flags number containing the numeric flag representation + setFlags = function(self, flags) self.flags = flags end, + + --- Sets the request XID + -- @param xid number containing the request XID + setXID = function(self, xid) self.xid = xid end, + + --- Sets the request function + -- @param func number containing the request function number + setFunction = function(self, func) self.func = func end, + + --- Sets the request taglist + -- @param tl string containing the taglist + setTagList = function(self, tl) self.tag_list = tl end, + + --- Sets the request url + -- @param u string containing the url + setUrl = function(self, u) self.url = u end, + + --- "Serializes" the request to a string + -- @return data string containing a string representation of the request + __tostring = function(self) + assert(self.func, "Packet function was not specified") + assert(self.scope, "Packet scope was not specified") + + local BASE_LEN = 24 + local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + + self.slp_spi_len + #self.service_type + #self.url + + #self.tag_list + #self.scope + local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) + local len_lo = bit.band(len, 0xFFFF) + local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), + 0x00FF) + local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) + + local data = bin.pack(">CCCSSCSSSASSASASAS", self.version, self.func, + len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, + self.prev_resp_list_len, #self.url, self.url, #self.scope, self.scope, + #self.tag_list, self.tag_list, self.slp_spi_len) + + return data + end + }, + + -- The Service request + Service = { + + --- Creates a new instance of the Service request + -- @return o instance of Service + new = function(self) + local o = { + lang_tag = "en", version = 2, service_type = "", + scope = "", next_extension_offset = 0, + prev_resp_list_len = 0, predicate_len = 0, slp_spi_len = 0 } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Sets the service type of the request + -- @param t string containing the type of the request + setServiceType = function(self, t) self.service_type = t end, + + --- Sets the request scope + -- @param scope string containing the request scope + setScope = function(self, scope) self.scope = scope end, + + --- Sets the language tag + -- @param lang string containing the language + setLangTag = function(self, lang) self.lang_tag = lang end, + + --- Sets the request flags + -- @param flags number containing the numeric flag representation + setFlags = function(self, flags) self.flags = flags end, + + --- Sets the request XID + -- @param xid number containing the request XID + setXID = function(self, xid) self.xid = xid end, + + --- Sets the request function + -- @param func number containing the request function number + setFunction = function(self, func) self.func = func end, + + --- "Serializes" the request to a string + -- @return data string containing a string representation of the request + __tostring = function(self) + assert(self.func, "Packet function was not specified") + assert(self.scope, "Packet scope was not specified") + + local BASE_LEN = 24 + local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + + self.predicate_len + self.slp_spi_len + #self.service_type + + #self.scope + local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) + local len_lo = bit.band(len, 0xFFFF) + local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), + 0x00FF) + local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) + + local data = bin.pack(">CCCSSCSSSASSASASS", self.version, self.func, + len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, + self.prev_resp_list_len, #self.service_type, self.service_type, #self.scope, + self.scope, self.predicate_len, self.slp_spi_len) + + return data + end + } + +} + + +-- The Helper class serves as primary interface for scripts using the libraryy +Helper = { + + new = function(self, host, port) + local o = { xid = 1, socket = nmap.new_socket("udp") } + setmetatable(o, self) + self.__index = self + local family = nmap.address_family() + o.host = host or (family=="inet6" and "FF02::116" or "239.255.255.253") + o.port = port or { number=427, proto="udp" } + return o + end, + + --- Sends a service request and waits for the response + -- @param srvtype string containing the service type to query + -- @param scope string containing the scope of the request + -- @return true on success, false on failure + -- @return url string (on success) containing the url of the ServiceReply + -- @return err string (on failure) containing the error message + ServiceRequest = function(self, srvtype, scope) + local srvtype = srvtype or "" + local scope = scope or "" + local sr = Request.Service:new() + sr:setXID(self.xid) + sr:setServiceType(srvtype) + sr:setScope(scope) + sr:setFunction(PacketFunction.SERVICE_REQUEST) + sr:setFlags(0x2000) + + self.socket:set_timeout(5000) + self.socket:sendto( self.host, self.port, tostring(sr) ) + + local r = Reply.Service.fromSocket(self.socket) + self.socket:close() + + self.xid = self.xid + 1 + + if ( not(r) ) then + return false, "ERROR: Helper.Locate no response received" + end + return true, r:getUrl() + end, + + --- Requests an attribute from the server + -- @param url as retrieved by the Service request + -- @param scope string containing the request scope + -- @param taglist string containing the request tag list + AttributeRequest = function(self, url, scope, taglist) + local url = url or "" + local scope = scope or "" + local taglist = taglist or "" + local ar = Request.Attribute:new() + ar:setXID(self.xid) + ar:setScope(scope) + ar:setUrl(url) + ar:setTagList(taglist) + ar:setFunction(PacketFunction.ATTRIB_REQUEST) + ar:setFlags(0x2000) + + self.socket:set_timeout(5000) + self.socket:sendto( self.host, self.port, tostring(ar) ) + + local r = Reply.Attribute.fromSocket(self.socket) + self.socket:close() + + self.xid = self.xid + 1 + if ( not(r) ) then + return false, "ERROR: Helper.Locate no response received" + end + return true, r:getAttribList() + end, + +} \ No newline at end of file diff --git a/scripts/broadcast-novell-locate.nse b/scripts/broadcast-novell-locate.nse new file mode 100644 index 000000000..205e466dc --- /dev/null +++ b/scripts/broadcast-novell-locate.nse @@ -0,0 +1,69 @@ +description = [[ +Attempts to use the Service Location Protocol to discover NCP Servers +]] + +--- +-- +--@output +-- Pre-scan script results: +-- | broadcast-novell-locate: +-- | Tree name: CQURE-LABTREE +-- | Server name: linux-l84t +-- | Addresses +-- |_ 192.168.56.33 +-- +-- + +-- Version 0.1 +-- Created 04/26/2011 - v0.1 - created by Patrik Karlsson + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"broadcast", "safe"} + +require 'srvloc' +require 'ipOps' + +prerule = function() return true end + +function action() + + local helper = srvloc.Helper:new() + + local status, bindery = helper:ServiceRequest("bindery.novell", "DEFAULT") + if ( not(status) ) then return end + local srvname = bindery:match("%/%/%/(.*)$") + + local status, attrib = helper:AttributeRequest(bindery, "DEFAULT", "svcaddr-ws") + attrib = attrib:match("^%(svcaddr%-ws=(.*)%)$") + if ( not(attrib) ) then return end + + local attribs = stdnse.strsplit(",", attrib) + if ( not(attribs) ) then return end + + local addrs = { name = "Addresses"} + local ips = {} + for _, attr in ipairs(attribs) do + local addr = attr:match("^%d*%-%d*%-%d*%-(........)") + if ( addr ) then + local pos, dw_addr = bin.unpack( "