mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 13:11:28 +00:00
Add TLS support to rdp-enum-encryption Closes #1614
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
#Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE][GH#1614] Add TLS support to rdp-enum-encryption. Enables determining
|
||||
protocol version against servers that require TLS and lays ground work for
|
||||
some NLA/CredSSP information collection. [Tom Sellers]
|
||||
|
||||
o [NSE][GH#1611] Address two protocol parsing issues in rdp-enum-encryption and
|
||||
the RDP nse library which broke scanning of Windows XP. Clarify protocol
|
||||
types [Tom Sellers]
|
||||
|
||||
117
nselib/rdp.lua
117
nselib/rdp.lua
@@ -10,6 +10,7 @@
|
||||
local nmap = require("nmap")
|
||||
local stdnse = require("stdnse")
|
||||
local string = require "string"
|
||||
local asn1 = require "asn1"
|
||||
_ENV = stdnse.module("rdp", stdnse.seeall)
|
||||
|
||||
-- Server Core Data 2.2.1.4.2
|
||||
@@ -26,6 +27,26 @@ PROTO_VERSION = {
|
||||
[0x0008000C] = " RDP 10.7 server",
|
||||
}
|
||||
|
||||
-- T.125 Result enumerated type
|
||||
CONNECT_RESPONSE_RESULT = {
|
||||
[ 0] = "rt-successful",
|
||||
[ 1] = "rt-domain-merging",
|
||||
[ 2] = "rt-domain-not-hierarchical",
|
||||
[ 3] = "rt-no-such-channel",
|
||||
[ 4] = "rt-no-such-domain",
|
||||
[ 5] = "rt-no-such-user",
|
||||
[ 6] = "rt-not-admitted",
|
||||
[ 7] = "rt-other-user-id",
|
||||
[ 8] = "rt-parameters-unacceptable",
|
||||
[ 9] = "rt-token-not-available",
|
||||
[10] = "rt-token-not-possessed",
|
||||
[11] = "rt-too-many-channels",
|
||||
[12] = "rt-too-many-tokens",
|
||||
[13] = "rt-too-many-users",
|
||||
[14] = "rt-unspecified-failure",
|
||||
[15] = "rt-user-rejected",
|
||||
}
|
||||
|
||||
Packet = {
|
||||
|
||||
TPKT = {
|
||||
@@ -105,7 +126,7 @@ Packet = {
|
||||
ConfCreateResponse = {
|
||||
|
||||
|
||||
new = function(self, data)
|
||||
new = function(self)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
@@ -113,23 +134,55 @@ Packet = {
|
||||
end,
|
||||
|
||||
parse = function(data)
|
||||
local ccr = Packet.ConfCreateResponse:new()
|
||||
local pos = 67
|
||||
|
||||
while data:len() > pos do
|
||||
block_type, block_len = string.unpack("<I2I2", data, pos)
|
||||
local tag_decoder = {}
|
||||
|
||||
tag_decoder["\x0A"] = function( self, encStr, elen, pos )
|
||||
return self.decodeInt(encStr, elen, pos)
|
||||
end
|
||||
|
||||
local ccr = Packet.ConfCreateResponse:new()
|
||||
|
||||
local decoder = asn1.ASN1Decoder:new()
|
||||
decoder:registerTagDecoders( tag_decoder )
|
||||
|
||||
local _, pos = decoder.decodeLength(data, 3)
|
||||
local response_result, userdata
|
||||
response_result, pos = decoder:decode(data, pos)
|
||||
ccr.result = CONNECT_RESPONSE_RESULT[response_result]
|
||||
|
||||
ccr.calledConnectId, pos = decoder:decode(data, pos)
|
||||
|
||||
-- T.125 DomainParameters SEQUENCE
|
||||
-- Not interested in its values now, just need to correctly parse
|
||||
-- the block so we can arrive at userData
|
||||
_, pos = decoder:decode(data, pos)
|
||||
|
||||
-- T.125 userData OCTO string
|
||||
userdata, _ = decoder:decode(data, pos)
|
||||
|
||||
if userdata == nil then
|
||||
return ccr
|
||||
end
|
||||
|
||||
-- Hackery to avoid writing ASN.1 PER decoding. Skip over fixed length
|
||||
-- T.124 ConnectData header. Decode the length since it can be multiple
|
||||
-- bytes. Drops us where we need to be.
|
||||
_, pos = asn1.ASN1Decoder.decodeLength(userdata, 22 )
|
||||
local block_type, block_len
|
||||
while userdata:len() > pos do
|
||||
block_type, block_len = string.unpack("<I2I2", userdata, pos)
|
||||
if block_type == 0x0c01 then
|
||||
-- 2.2.1.42 Server Core Data - TS_UD_SC_CORE
|
||||
proto_ver = string.unpack("<I4", data, pos + 4)
|
||||
local proto_ver = string.unpack("<I4",userdata, pos + 4)
|
||||
ccr.proto_version = ("RDP Protocol Version: %s"):format(PROTO_VERSION[proto_ver] or "Unknown")
|
||||
elseif block_type == 0x0c02 then
|
||||
-- 2.2.1.4.3 Server Security Data - TS_UD_SC_SEC1
|
||||
ccr.enc_level = string.unpack("B", data, pos + 8)
|
||||
ccr.enc_cipher= string.unpack("B", data, pos + 4)
|
||||
ccr.enc_level = string.unpack("B", userdata, pos + 8)
|
||||
ccr.enc_cipher= string.unpack("B", userdata, pos + 4)
|
||||
end
|
||||
pos = pos + block_len
|
||||
end
|
||||
|
||||
return ccr
|
||||
end,
|
||||
},
|
||||
@@ -149,8 +202,6 @@ Request = {
|
||||
|
||||
__tostring = function(self)
|
||||
local cookie = "mstshash=nmap"
|
||||
local itpkt_len = 21 + #cookie
|
||||
local itut_len = 16 + #cookie
|
||||
|
||||
local data = string.pack(">I2I2B",
|
||||
0x0000, -- dst reference
|
||||
@@ -172,8 +223,8 @@ Request = {
|
||||
|
||||
MCSConnectInitial = {
|
||||
|
||||
new = function(self, cipher)
|
||||
local o = { cipher = cipher }
|
||||
new = function(self, cipher, server_proto)
|
||||
local o = { cipher = cipher, server_proto = server_proto }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
@@ -183,7 +234,7 @@ Request = {
|
||||
|
||||
local data = stdnse.fromhex(
|
||||
"7f 65" .. -- BER: Application-Defined Type = APPLICATION 101,
|
||||
"82 01 90" .. -- BER: Type Length = 404 bytes
|
||||
"82 01 94" .. -- BER: Type Length = 404 bytes
|
||||
"04 01 01" .. -- Connect-Initial::callingDomainSelector
|
||||
"04 01 01" .. -- Connect-Initial::calledDomainSelector
|
||||
"01 01 ff" .. -- Connect-Initial::upwardFlag = TRUE
|
||||
@@ -214,20 +265,21 @@ Request = {
|
||||
"02 01 01" .. -- DomainParameters::maxHeight = 1
|
||||
"02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535
|
||||
"02 01 02" .. -- DomainParameters::protocolVersion = 2
|
||||
"04 82 01 2f" .. -- Connect-Initial::userData (307 bytes)
|
||||
"04 82 01 33" .. -- Connect-Initial::userData (307 bytes)
|
||||
"00 05" .. -- object length = 5 bytes
|
||||
"00 14 7c 00 01" .. -- object
|
||||
"81 26" .. -- ConnectData::connectPDU length = 298 bytes
|
||||
"00 08 00 10 00 01 c0 00 44 75 63 61 81 18" .. -- PER encoded (ALIGNED variant of BASIC-PER) GCC Conference Create Request PDU
|
||||
"01 c0 d4 00" .. -- TS_UD_HEADER::type = CS_CORE (0xc001), length = 216 bytes
|
||||
"81 2a" .. -- ConnectData::connectPDU length = 42 bytes
|
||||
"00 08 00 10 00 01 c0 00 44 75 63 61 81 1c" .. -- PER encoded (ALIGNED variant of BASIC-PER) GCC Conference Create Request PDU
|
||||
"01 c0 d8 00" .. -- TS_UD_HEADER::type = CS_CORE (0xc001), length = 216 bytes
|
||||
"04 00 08 00" .. -- TS_UD_CS_CORE::version = 0x0008004
|
||||
"00 05" .. -- TS_UD_CS_CORE::desktopWidth = 1280
|
||||
"20 03" .. -- TS_UD_CS_CORE::desktopHeight = 1024
|
||||
"01 ca" .. -- TS_UD_CS_CORE::colorDepth = RNS_UD_COLOR_8BPP (0xca01)
|
||||
"03 aa" .. -- TS_UD_CS_CORE::SASSequence
|
||||
"09 08 00 00" .. -- TS_UD_CS_CORE::keyboardLayout = 0x409 = 1033 = English (US)
|
||||
"28 0a 00 00" .. -- TS_UD_CS_CORE::clientBuild = 3790
|
||||
"45 00 4d 00 50 00 2d 00 4c 00 41 00 50 00 2d 00 30 00 30 00 31 00 34 00 00 00 00 00 00 00 00 00" .. -- TS_UD_CS_CORE::clientName = ELTONS-TEST2
|
||||
"09 04 00 00" .. -- TS_UD_CS_CORE::keyboardLayout = 0x409 = 1033 = English (US)
|
||||
"28 0a 00 00" .. -- TS_UD_CS_CORE::clientBuild = 2600
|
||||
"45 00 4d 00 50 00 2d 00 4c 00 41 00 50 00 2d 00 " ..
|
||||
"30 00 30 00 31 00 34 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientName = EMP-LAP-0014
|
||||
"04 00 00 00" .. -- TS_UD_CS_CORE::keyboardType
|
||||
"00 00 00 00" .. -- TS_UD_CS_CORE::keyboardSubtype
|
||||
"0c 00 00 00" .. -- TS_UD_CS_CORE::keyboardFunctionKey
|
||||
@@ -238,16 +290,17 @@ Request = {
|
||||
"01 ca" .. -- TS_UD_CS_CORE::postBeta2ColorDepth = RNS_UD_COLOR_8BPP (0xca01)
|
||||
"01 00" .. -- TS_UD_CS_CORE::clientProductId
|
||||
"00 00 00 00" .. -- TS_UD_CS_CORE::serialNumber
|
||||
"10 00" .. -- TS_UD_CS_CORE::highColorDepth = 24 bpp
|
||||
"07 00" .. -- TS_UD_CS_CORE::supportedColorDepths
|
||||
"18 00" .. -- TS_UD_CS_CORE::highColorDepth = 24 bpp
|
||||
"07 00" .. -- TS_UD_CS_CORE::supportedColorDepths = 24 bpp
|
||||
"01 00" .. -- TS_UD_CS_CORE::earlyCapabilityFlags
|
||||
"36 00 39 00 37 00 31 00 32 00 2d 00 37 00 38 00 " ..
|
||||
"33 00 2d 00 30 00 33 00 35 00 37 00 39 00 37 00 " ..
|
||||
"34 00 2d 00 34 00 32 00 37 00 31 00 34 00 00 00 " ..
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientDigProductId = "69712-783-0357974-42714"
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " ..
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientDigProductId
|
||||
"00" .. -- TS_UD_CS_CORE::connectionType = 0 (not used as RNS_UD_CS_VALID_CONNECTION_TYPE not set)
|
||||
"00" .. -- TS_UD_CS_CORE::pad1octet
|
||||
"00 00 00 00" .. -- TS_UD_CS_CORE::serverSelectedProtocol
|
||||
"00") -- TS_UD_CS_CORE::pad1octet
|
||||
-- TS_UD_CS_CORE::serverSelectedProtocol
|
||||
.. string.pack("<I4", self.server_proto or 0) .. stdnse.fromhex(
|
||||
"04 c0 0c 00" .. -- TS_UD_HEADER::type = CS_CLUSTER (0xc004), length = 12 bytes
|
||||
"09 00 00 00" .. -- TS_UD_CS_CLUSTER::Flags = 0x0d
|
||||
"00 00 00 00" .. -- TS_UD_CS_CLUSTER::RedirectedSessionID
|
||||
@@ -267,7 +320,7 @@ Request = {
|
||||
return tostring(Packet.TPKT:new(Packet.ITUT:new(0xF0, data)))
|
||||
end
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -284,7 +337,6 @@ Response = {
|
||||
|
||||
parse = function(data)
|
||||
local cc = Response.ConnectionConfirm:new()
|
||||
local pos, _
|
||||
|
||||
cc.tpkt = Packet.TPKT.parse(data)
|
||||
cc.itut = Packet.ITUT.parse(cc.tpkt.data)
|
||||
@@ -373,8 +425,7 @@ Comm = {
|
||||
return false, err
|
||||
end
|
||||
|
||||
local data
|
||||
status, data = self:recv()
|
||||
local _, data = self:recv()
|
||||
if ( #data< 5 ) then
|
||||
return false, "Packet too short"
|
||||
end
|
||||
|
||||
@@ -64,41 +64,64 @@ local function enum_protocols(host, port)
|
||||
}
|
||||
|
||||
local res_proto = { name = "Security layer" }
|
||||
local proto_version
|
||||
|
||||
for k, v in pairs(PROTOCOLS) do
|
||||
-- Prevent reconnecting too quickly, improves reliability
|
||||
stdnse.sleep(0.2)
|
||||
|
||||
local comm = rdp.Comm:new(host, port)
|
||||
if ( not(comm:connect()) ) then
|
||||
return false, fail("Failed to connect to server")
|
||||
end
|
||||
local cr = rdp.Request.ConnectionRequest:new(v)
|
||||
local status, response = comm:exch(cr)
|
||||
comm:close()
|
||||
if ( not(status) ) then
|
||||
|
||||
if status then
|
||||
if response.itut.data ~= "" then
|
||||
local success = string.unpack("B", response.itut.data)
|
||||
|
||||
if ( success == 2 ) then
|
||||
table.insert(res_proto, ("%s: SUCCESS"):format(k))
|
||||
elseif ( nmap.debugging() > 0 ) then
|
||||
local err = string.unpack("B", response.itut.data, 5)
|
||||
if ( err > 0 ) then
|
||||
table.insert(res_proto, ("%s: FAILED (%s)"):format(k, ERRORS[err] or "Unknown"))
|
||||
else
|
||||
table.insert(res_proto, ("%s: FAILED"):format(k))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- rdpNegData, which contains the negotiation response or failure,
|
||||
-- is optional. WinXP SP3 does not return this section which means
|
||||
-- we can't tell if the protocol is accepted or not.
|
||||
table.insert(res_proto, ("%s: Unknown"):format(k))
|
||||
end
|
||||
else
|
||||
comm:close()
|
||||
return false, response
|
||||
end
|
||||
|
||||
if response.itut.data ~= "" then
|
||||
local success = string.unpack("B", response.itut.data)
|
||||
|
||||
if ( success == 2 ) then
|
||||
table.insert(res_proto, ("%s: SUCCESS"):format(k))
|
||||
elseif ( nmap.debugging() > 0 ) then
|
||||
local err = string.unpack("B", response.itut.data, 5)
|
||||
if ( err > 0 ) then
|
||||
table.insert(res_proto, ("%s: FAILED (%s)"):format(k, ERRORS[err] or "Unknown"))
|
||||
else
|
||||
table.insert(res_proto, ("%s: FAILED"):format(k))
|
||||
-- For servers that require TLS or NLA the only way to get the RDP protocol
|
||||
-- version to negotiate TLS or NLA. This section does that for TLS. There
|
||||
-- is no NLA currently.
|
||||
if status and (v == 1) then
|
||||
local res, _ = comm.socket:reconnect_ssl()
|
||||
if res then
|
||||
local msc = rdp.Request.MCSConnectInitial:new(0, 1)
|
||||
status, response = comm:exch(msc)
|
||||
if status then
|
||||
if response.ccr.proto_version then
|
||||
proto_version = response.ccr.proto_version
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- rdpNegData, which contains the negotiaion response or failure,
|
||||
-- is optional. WinXP SP3 does not return this section which means
|
||||
-- we can't tell if the protocol is accepted or not.
|
||||
table.insert(res_proto, ("%s: Unknown"):format(k))
|
||||
end
|
||||
|
||||
comm:close()
|
||||
end
|
||||
table.sort(res_proto)
|
||||
return true, res_proto
|
||||
return true, res_proto, proto_version
|
||||
end
|
||||
|
||||
local function enum_ciphers(host, port)
|
||||
@@ -133,19 +156,22 @@ local function enum_ciphers(host, port)
|
||||
end
|
||||
|
||||
for k, v in get_ordered_ciphers() do
|
||||
-- Prevent reconnecting too quickly, improves reliability
|
||||
stdnse.sleep(0.2)
|
||||
|
||||
local comm = rdp.Comm:new(host, port)
|
||||
if ( not(comm:connect()) ) then
|
||||
return false, fail("Failed to connect to server")
|
||||
end
|
||||
|
||||
local cr = rdp.Request.ConnectionRequest:new()
|
||||
local status, response = comm:exch(cr)
|
||||
local status, _ = comm:exch(cr)
|
||||
if ( not(status) ) then
|
||||
break
|
||||
end
|
||||
|
||||
local msc = rdp.Request.MCSConnectInitial:new(v)
|
||||
local status, response = comm:exch(msc)
|
||||
status, response = comm:exch(msc)
|
||||
comm:close()
|
||||
if ( status ) then
|
||||
if ( response.ccr and response.ccr.enc_cipher == v ) then
|
||||
@@ -166,20 +192,22 @@ end
|
||||
action = function(host, port)
|
||||
local result = {}
|
||||
|
||||
local status, res_proto = enum_protocols(host, port)
|
||||
local status, res_proto, proto_ver = enum_protocols(host, port)
|
||||
if ( not(status) ) then
|
||||
return res_proto
|
||||
end
|
||||
|
||||
local status, res_ciphers, proto_version = enum_ciphers(host, port)
|
||||
local status, res_ciphers, cipher_ver = enum_ciphers(host, port)
|
||||
if ( not(status) ) then
|
||||
return res_ciphers
|
||||
end
|
||||
|
||||
table.insert(result, res_proto)
|
||||
table.insert(result, res_ciphers)
|
||||
if proto_version then
|
||||
table.insert(result, proto_version)
|
||||
if proto_ver then
|
||||
table.insert(result, proto_ver)
|
||||
elseif cipher_ver then
|
||||
table.insert(result, cipher_ver)
|
||||
end
|
||||
return stdnse.format_output(true, result)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user