1
0
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:
tomsellers
2019-06-04 19:38:28 +00:00
parent 43b9461e5c
commit a4f3c85eb9
3 changed files with 141 additions and 58 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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