mirror of
https://github.com/nmap/nmap.git
synced 2025-12-16 04:39:03 +00:00
o [NSE] Added the scripts xdmcp-discover, broadcast-xdmcp-discover and the
X Display Manager Control Protocol (xdmcp) library. The scripts discover hosts either using unicast or broadcast and try to detect supported authentication and authorization mechanisms. [Patrik]
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added the scripts xdmcp-discover, broadcast-xdmcp-discover and the
|
||||
X Display Manager Control Protocol (xdmcp) library. The scripts discover
|
||||
hosts either using unicast or broadcast and try to detect supported
|
||||
authentication and authorization mechanisms. [Patrik]
|
||||
|
||||
o Audited the nmap-service-probes database to remove all unused
|
||||
captures, fixing dozens of bugs with captures either being ignored
|
||||
or two fields erroneously using the same capture. This was done by
|
||||
|
||||
394
nselib/xdmcp.lua
Normal file
394
nselib/xdmcp.lua
Normal file
@@ -0,0 +1,394 @@
|
||||
---
|
||||
-- Implementation of the XDMCP X-Windows protocol based on:
|
||||
-- x http://www.xfree86.org/current/xdmcp.pdf
|
||||
--
|
||||
-- @author "Patrik Karlsson <patrik@cqure.net>"
|
||||
|
||||
module(... or "xdmcp", package.seeall)
|
||||
|
||||
-- Supported operations
|
||||
OpCode = {
|
||||
BCAST_QUERY = 1,
|
||||
QUERY = 2,
|
||||
WILLING = 5,
|
||||
REQUEST = 7,
|
||||
ACCEPT = 8,
|
||||
MANAGE = 10,
|
||||
}
|
||||
|
||||
-- Packet class
|
||||
Packet = {
|
||||
|
||||
-- The cdmcp header
|
||||
Header = {
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param version number containing the protocol version
|
||||
-- @param opcode number containing the opcode type
|
||||
-- @param length number containing the length of the data
|
||||
-- @return o instance of class
|
||||
new = function(self, version, opcode, length)
|
||||
local o = { version = version, opcode = opcode, length = length }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Parses data based on which a new object is instantiated
|
||||
-- @param data opaque string containing data received over the wire
|
||||
-- @return hdr instance of class
|
||||
parse = function(data)
|
||||
local pos, hdr = nil, Packet.Header:new()
|
||||
pos, hdr.version, hdr.opcode, hdr.length = bin.unpack(">SSS", data)
|
||||
return hdr
|
||||
end,
|
||||
|
||||
-- Converts the instance to an opaque string
|
||||
-- @return str string containing the instance
|
||||
__tostring = function(self)
|
||||
assert(self.length, "No header length was supplied")
|
||||
return bin.pack(">SSS", self.version, self.opcode, self.length)
|
||||
end,
|
||||
},
|
||||
|
||||
[OpCode.QUERY] = {
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param authnames table of strings containing authentication
|
||||
-- mechanism names.
|
||||
-- @return o instance of class
|
||||
new = function(self, authnames)
|
||||
local o = {
|
||||
header = Packet.Header:new(1, OpCode.QUERY),
|
||||
authnames = authnames or {},
|
||||
}
|
||||
o.header.length = #o.authnames + 1
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Converts the instance to an opaque string
|
||||
-- @return str string containing the instance
|
||||
__tostring = function(self)
|
||||
local data = tostring(self.header)
|
||||
data = data .. bin.pack("C", #self.authnames)
|
||||
for _, name in ipairs(self.authnames) do
|
||||
data = data .. bin.pack("P", name)
|
||||
end
|
||||
return data
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
[OpCode.BCAST_QUERY] = {
|
||||
new = function(...)
|
||||
local packet = Packet[OpCode.QUERY]:new(...)
|
||||
packet.header.opcode = OpCode.BCAST_QUERY
|
||||
return packet
|
||||
end,
|
||||
|
||||
__tostring = function(...)
|
||||
return Packet[OpCode.QUERY]:__tostring(...)
|
||||
end
|
||||
|
||||
},
|
||||
|
||||
[OpCode.WILLING] = {
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @return o instance of class
|
||||
new = function(self)
|
||||
local o = {
|
||||
header = Packet.Header:new(1, OpCode.WILLING)
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Parses data based on which a new object is instantiated
|
||||
-- @param data opaque string containing data received over the wire
|
||||
-- @return hdr instance of class
|
||||
parse = function(data)
|
||||
local willing = Packet[OpCode.WILLING]:new()
|
||||
willing.header = Packet.Header.parse(data)
|
||||
|
||||
local pos = 7
|
||||
pos, willing.authname, willing.hostname,
|
||||
willing.status = bin.unpack("ppp", data, pos)
|
||||
return willing
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
[OpCode.REQUEST] = {
|
||||
|
||||
-- The connection class
|
||||
Connection = {
|
||||
|
||||
IpType = {
|
||||
IPv4 = 0,
|
||||
IPv6 = 6,
|
||||
},
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param iptype number
|
||||
-- @param ip opaque string containing the ip
|
||||
-- @return o instance of class
|
||||
new = function(self, iptype, ip)
|
||||
local o = {
|
||||
iptype = iptype,
|
||||
ip = ip,
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param disp_no number containing the display name
|
||||
-- @param auth_name string containing the authentication name
|
||||
-- @param auth_data string containing additional authentication data
|
||||
-- @param authr_names string containing authorization mechanisms
|
||||
-- @param manf_id string containing the manufacturer id
|
||||
-- @return o instance of class
|
||||
new = function(self, disp_no, conns, auth_name, auth_data, authr_names, manf_id )
|
||||
local o = {
|
||||
header = Packet.Header:new(1, OpCode.REQUEST),
|
||||
disp_no = disp_no or 1,
|
||||
conns = conns or {},
|
||||
auth_name = auth_name or "",
|
||||
auth_data = auth_data or "",
|
||||
authr_names = authr_names or {},
|
||||
manf_id = manf_id or "",
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Adds a new connection entry
|
||||
-- @param conn instance of Connections
|
||||
addConnection = function(self, conn)
|
||||
table.insert(self.conns, conn)
|
||||
end,
|
||||
|
||||
-- Adds a new authorization entry
|
||||
-- @param str string containing the name of the authorization mechanism
|
||||
addAuthrName = function(self, str)
|
||||
table.insert(self.authr_names, str)
|
||||
end,
|
||||
|
||||
-- Converts the instance to an opaque string
|
||||
-- @return str string containing the instance
|
||||
__tostring = function(self)
|
||||
local data = bin.pack(">SC", self.disp_no, #self.conns)
|
||||
for _, conn in ipairs(self.conns) do
|
||||
data = data .. bin.pack(">S", conn.iptype)
|
||||
end
|
||||
data = data .. bin.pack("C", #self.conns)
|
||||
for _, conn in ipairs(self.conns) do
|
||||
data = data .. bin.pack(">P", ipOps.ip_to_str(conn.ip))
|
||||
end
|
||||
data = data .. bin.pack(">PP", self.auth_name, self.auth_data)
|
||||
data = data .. bin.pack("C", #self.authr_names)
|
||||
for _, authr in ipairs(self.authr_names) do
|
||||
data = data .. bin.pack(">P", authr)
|
||||
end
|
||||
data = data .. bin.pack(">P", self.manf_id)
|
||||
self.header.length = #data
|
||||
|
||||
return tostring(self.header) .. data
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
[OpCode.ACCEPT] = {
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param session_id number containing the session id
|
||||
-- @param auth_name string containing the authentication name
|
||||
-- @param auth_data string containing additional authentication data
|
||||
-- @param authr_name string containing the authorization mechanism name
|
||||
-- @param authr_names string containing authorization mechanisms
|
||||
-- @return o instance of class
|
||||
new = function(self, session_id, auth_name, auth_data, authr_name, authr_data)
|
||||
local o = {
|
||||
header = Packet.Header:new(1, OpCode.ACCEPT),
|
||||
session_id = session_id,
|
||||
auth_name = auth_name,
|
||||
auth_data = auth_data,
|
||||
authr_name = authr_name,
|
||||
authr_data = authr_data,
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Parses data based on which a new object is instantiated
|
||||
-- @param data opaque string containing data received over the wire
|
||||
-- @return hdr instance of class
|
||||
parse = function(data)
|
||||
local accept = Packet[OpCode.ACCEPT]:new()
|
||||
accept.header = Packet.Header.parse(data)
|
||||
local pos = 7
|
||||
pos, accept.session_id, accept.auth_name, accept.auth_data,
|
||||
accept.authr_name, accept.authr_data = bin.unpack(">IPPPP", data, pos)
|
||||
return accept
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
[OpCode.MANAGE] = {
|
||||
|
||||
-- Creates a new instance of class
|
||||
-- @param session_id number containing the session id
|
||||
-- @param disp_no number containing the display number
|
||||
-- @param disp_class string containing the display class
|
||||
-- @return o instance of class
|
||||
new = function(self, sess_id, disp_no, disp_class)
|
||||
local o = {
|
||||
header = Packet.Header:new(1, OpCode.MANAGE),
|
||||
session_id = sess_id,
|
||||
disp_no = disp_no,
|
||||
disp_class = disp_class or ""
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Converts the instance to an opaque string
|
||||
-- @return str string containing the instance
|
||||
__tostring = function(self)
|
||||
local data = bin.pack(">ISP", self.session_id, self.disp_no, self.disp_class)
|
||||
self.header.length = #data
|
||||
return tostring(self.header) .. data
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-- The Helper class serves as the main script interface
|
||||
Helper = {
|
||||
|
||||
-- Creates a new instance of Helper
|
||||
-- @param host table as received by the action method
|
||||
-- @param port table as received by the action method
|
||||
-- @param options table
|
||||
-- @retun o new instance of Helper
|
||||
new = function(self, host, port, options)
|
||||
local o = {
|
||||
host = host,
|
||||
port = port,
|
||||
options = options or {},
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- "Connects" to the server (ie. creates the socket)
|
||||
-- @return status, true on success, false on failure
|
||||
connect = function(self)
|
||||
self.socket = nmap.new_socket("udp")
|
||||
self.socket:set_timeout(self.options.timeout or 10000)
|
||||
return true
|
||||
end,
|
||||
|
||||
-- Creates a xdmcp session
|
||||
-- @param auth_name string containing the authentication name
|
||||
-- @param authr_name string containing the authorization mechanism name
|
||||
-- @param disp_class string containing the display class
|
||||
-- @return status true on success, false on failure
|
||||
-- @return response table or err string containing an error message
|
||||
createSession = function(self, auth_names, authr_names, disp_no)
|
||||
local info = nmap.get_interface_info(self.host.interface)
|
||||
if ( not(info) ) then
|
||||
return false, ("Failed to get information for interface %s"):format(host.interface)
|
||||
end
|
||||
|
||||
local req = xdmcp.Packet[xdmcp.OpCode.QUERY]:new(auth_names)
|
||||
local status, response = self:exch(req)
|
||||
if ( not(status) ) then
|
||||
return false, response
|
||||
elseif ( response.header.opcode ~= xdmcp.OpCode.WILLING ) then
|
||||
return false, "Received unexpected response"
|
||||
end
|
||||
|
||||
local REQ = xdmcp.Packet[xdmcp.OpCode.REQUEST]
|
||||
local iptype = REQ.Connection.IpType.IPv4
|
||||
if ( nmap.address_family() == 'inet6' ) then
|
||||
iptype = REQ.Connection.IpType.IPv6
|
||||
end
|
||||
|
||||
local conns = { REQ.Connection:new(iptype, info.address) }
|
||||
local req = REQ:new(disp_no, conns, nil, nil, authr_names)
|
||||
local status, response = self:exch(req)
|
||||
if ( not(status) ) then
|
||||
return false, response
|
||||
elseif ( response.header.opcode ~= xdmcp.OpCode.ACCEPT ) then
|
||||
return false, "Received unexpected response"
|
||||
end
|
||||
|
||||
-- Sending this last manage packet doesn't make any sense as we can't
|
||||
-- set up a listening TCP server anyway. When we can, we could enable
|
||||
-- this and wait for the incoming request and retrieve X protocol info.
|
||||
|
||||
-- local manage = xdmcp.Packet[xdmcp.OpCode.MANAGE]:new(response.session_id,
|
||||
-- disp_no, "MIT-unspecified")
|
||||
-- local status, response = self:exch(manage)
|
||||
-- if ( not(status) ) then
|
||||
-- return false, response
|
||||
-- end
|
||||
|
||||
return true, {
|
||||
session_id = response.session_id,
|
||||
auth_name = response.auth_name,
|
||||
auth_data = response.auth_data,
|
||||
authr_name = response.authr_name,
|
||||
authr_data = response.authr_data,
|
||||
}
|
||||
end,
|
||||
|
||||
send = function(self, req)
|
||||
return self.socket:sendto(self.host, self.port, tostring(req))
|
||||
end,
|
||||
|
||||
recv = function(self)
|
||||
local status, data = self.socket:receive()
|
||||
if ( not(status) ) then
|
||||
return false, data
|
||||
end
|
||||
local header = Packet.Header.parse(data)
|
||||
if ( not(header) ) then
|
||||
return false, "Failed to parse xdmcp header"
|
||||
end
|
||||
if ( not(Packet[header.opcode]) ) then
|
||||
return false, ("No parser for opcode: %d"):format(header.opcode)
|
||||
end
|
||||
local resp = Packet[header.opcode].parse(data)
|
||||
if ( not(resp) ) then
|
||||
return false, "Failed to parse response"
|
||||
end
|
||||
return true, resp
|
||||
end,
|
||||
|
||||
-- Sends a request to the server, receives and parses a response
|
||||
-- @param req instance of Packet
|
||||
-- @return status true on success, false on failure
|
||||
-- @return response instance of response packet
|
||||
exch = function(self, req)
|
||||
local status, err = self:send(req)
|
||||
if ( not(status) ) then
|
||||
return false, "Failed to send xdmcp request"
|
||||
end
|
||||
return self:recv()
|
||||
end,
|
||||
|
||||
}
|
||||
69
scripts/broadcast-xdmcp-discover.nse
Normal file
69
scripts/broadcast-xdmcp-discover.nse
Normal file
@@ -0,0 +1,69 @@
|
||||
description = [[
|
||||
Discovers servers running the X Display Manager Control Protocol (XDMCP) by
|
||||
sending a XDMCP broadcast request to the LAN. Display managers allowing access
|
||||
are marked using the keyword Willing in the result.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script broadcast-xdmcp-discover
|
||||
--
|
||||
-- @output
|
||||
-- Pre-scan script results:
|
||||
-- | broadcast-xdmcp-discover:
|
||||
-- |_ 192.168.2.162 - Willing
|
||||
--
|
||||
-- @arg broadcast-xdmcp-discover.timeout socket timeout in seconds (default: 5)
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"broadcast", "safe"}
|
||||
|
||||
require 'xdmcp'
|
||||
|
||||
prerule = function() return true end
|
||||
|
||||
local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout")
|
||||
|
||||
action = function()
|
||||
|
||||
local host, port = { ip = "255.255.255.255" }, { number = 177, protocol = "udp" }
|
||||
local options = { timeout = 1 }
|
||||
local helper = xdmcp.Helper:new(host, port, options)
|
||||
local status = helper:connect()
|
||||
|
||||
local req = xdmcp.Packet[xdmcp.OpCode.BCAST_QUERY]:new(nil)
|
||||
local status, err = helper:send(req)
|
||||
if ( not(status) ) then
|
||||
return false, response
|
||||
end
|
||||
|
||||
local timeout = arg_timeout or 5
|
||||
local start = os.time()
|
||||
local result = {}
|
||||
repeat
|
||||
|
||||
local status, response = helper:recv()
|
||||
if ( not(status) and response ~= "TIMEOUT" ) then
|
||||
break
|
||||
elseif ( status ) then
|
||||
local status, _, _, rhost = helper.socket:get_info()
|
||||
if ( response.header.opcode == xdmcp.OpCode.WILLING ) then
|
||||
result[rhost] = true
|
||||
else
|
||||
result[rhost] = false
|
||||
end
|
||||
end
|
||||
|
||||
until( os.time() - start > timeout )
|
||||
|
||||
local output = {}
|
||||
for ip, res in pairs(result) do
|
||||
if ( res ) then
|
||||
table.insert(output, ("%s - Willing"):format(ip))
|
||||
else
|
||||
table.insert(output, ("%s - Unwilling"):format(ip))
|
||||
end
|
||||
end
|
||||
return stdnse.format_output(true, output)
|
||||
end
|
||||
@@ -36,6 +36,7 @@ Entry { filename = "broadcast-upnp-info.nse", categories = { "broadcast", "safe"
|
||||
Entry { filename = "broadcast-wake-on-lan.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-wpad-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-xdmcp-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "citrix-enum-apps.nse", categories = { "discovery", "safe", } }
|
||||
@@ -313,5 +314,6 @@ Entry { filename = "wdb-version.nse", categories = { "default", "discovery", "ve
|
||||
Entry { filename = "whois.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "x11-access.nse", categories = { "auth", "default", "safe", } }
|
||||
Entry { filename = "xdmcp-discover.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "xmpp-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "xmpp-info.nse", categories = { "default", "discovery", "safe", "version", } }
|
||||
|
||||
64
scripts/xdmcp-discover.nse
Normal file
64
scripts/xdmcp-discover.nse
Normal file
@@ -0,0 +1,64 @@
|
||||
description = [[
|
||||
Requests a XDMCP session and lists supported authentication and authorization mechanisms
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -sU -p 177 --script xdmcp-discover <ip>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 177/udp open|filtered xdmcp
|
||||
-- | xdmcp-discover:
|
||||
-- | Session id: 0x0000703E
|
||||
-- | Authorization name: MIT-MAGIC-COOKIE-1
|
||||
-- |_ Authorization data: c282137c9bf8e2af88879e6eaa922326
|
||||
--
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "discovery"}
|
||||
|
||||
require 'ipOps'
|
||||
require 'shortport'
|
||||
require 'xdmcp'
|
||||
|
||||
portrule = shortport.port_or_service(177, "xdmcp", "udp")
|
||||
|
||||
local mutex = nmap.mutex("xdmcp-discover")
|
||||
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
||||
|
||||
|
||||
action = function(host, port)
|
||||
|
||||
local DISPLAY_ID = 1
|
||||
local result = {}
|
||||
|
||||
local helper = xdmcp.Helper:new(host, port)
|
||||
local status = helper:connect()
|
||||
if ( not(status) ) then
|
||||
return fail("Failed to connect to server")
|
||||
end
|
||||
|
||||
local status, response = helper:createSession(nil,
|
||||
{"MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1"}, DISPLAY_ID)
|
||||
|
||||
if ( not(status) ) then
|
||||
return fail("Failed to create xdmcp session")
|
||||
end
|
||||
|
||||
table.insert(result, ("Session id: 0x%.8X"):format(response.session_id))
|
||||
if ( response.auth_name and 0 < #response.auth_name ) then
|
||||
table.insert(result, ("Authentication name: %s"):format(response.auth_name))
|
||||
end
|
||||
if ( response.auth_data and 0 < #response.auth_data ) then
|
||||
table.insert(result, ("Authentication data: %s"):format(stdnse.tohex(response.auth_data)))
|
||||
end
|
||||
if ( response.authr_name and 0 < #response.authr_name ) then
|
||||
table.insert(result, ("Authorization name: %s"):format(response.authr_name))
|
||||
end
|
||||
if ( response.authr_data and 0 < #response.authr_data ) then
|
||||
table.insert(result, ("Authorization data: %s"):format(stdnse.tohex(response.authr_data)))
|
||||
end
|
||||
return stdnse.format_output(true, result)
|
||||
end
|
||||
Reference in New Issue
Block a user