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-*-
|
# 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
|
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.
|
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-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-dropbox-listener.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-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-upnp-info.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user