mirror of
https://github.com/nmap/nmap.git
synced 2025-12-16 04:39:03 +00:00
o [NSE] Added minimal Service Location Protocol (SLP) library and the script
broadcast-novell-locate that detects servers running eDirectory. [Patrik]
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
364
nselib/srvloc.lua
Normal file
364
nselib/srvloc.lua
Normal file
@@ -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:
|
||||
--
|
||||
-- <code>
|
||||
-- local helper = srvloc.Helper:new()
|
||||
-- local status, tree = helper:ServiceRequest("ndap.novell", "DEFAULT")
|
||||
-- if ( status ) then tree = tree:match("%/%/%/(.*)%.$") end
|
||||
-- </code>
|
||||
|
||||
--@author Patrik Karlsson <patrik@cqure.net>
|
||||
--@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 <patrik@cqure.net>
|
||||
|
||||
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,
|
||||
|
||||
}
|
||||
69
scripts/broadcast-novell-locate.nse
Normal file
69
scripts/broadcast-novell-locate.nse
Normal file
@@ -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( "<I", bin.pack("H", addr) )
|
||||
local ip = ipOps.fromdword(dw_addr)
|
||||
|
||||
if ( not(ips[ip]) ) then
|
||||
table.insert(addrs, ip)
|
||||
ips[ip] = ip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local output = {}
|
||||
local status, treename = helper:ServiceRequest("ndap.novell", "DEFAULT")
|
||||
if ( status ) then
|
||||
treename = treename:match("%/%/%/(.*)%.$")
|
||||
table.insert(output, ("Tree name: %s"):format(treename))
|
||||
end
|
||||
table.insert(output, ("Server name: %s"):format(srvname))
|
||||
table.insert(output, addrs)
|
||||
|
||||
return stdnse.format_output(true, output)
|
||||
end
|
||||
@@ -13,6 +13,7 @@ Entry { filename = "broadcast-avahi-dos.nse", categories = { "broadcast", "dos",
|
||||
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-ms-sql-discover.nse", categories = { "broadcast", "discovery", "safe", } }
|
||||
Entry { filename = "broadcast-novell-locate.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-upnp-info.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user