1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-17 21:19:01 +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-*- # 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 o Audited the nmap-service-probes database to remove all unused
captures, fixing dozens of bugs with captures either being ignored captures, fixing dozens of bugs with captures either being ignored
or two fields erroneously using the same capture. This was done by 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-wake-on-lan.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-wpad-discover.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-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-brute-xml.nse", categories = { "auth", "intrusive", } }
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } } Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
Entry { filename = "citrix-enum-apps.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 = "whois.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "x11-access.nse", categories = { "auth", "default", "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-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "xmpp-info.nse", categories = { "default", "discovery", "safe", "version", } } 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