1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-15 20:29: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:
patrik
2012-01-26 19:35:19 +00:00
parent c2e868e17a
commit 0ad978d3b8
5 changed files with 534 additions and 0 deletions

View File

@@ -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
View 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,
}

View 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

View File

@@ -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", } }

View 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