1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-24 16:39:03 +00:00

o [NSE] Added rdp library and the script rdp-enum-encryption that enumerates

both the Security Layer and Encryption level of the RDP service. [Patrik
  Karlsson]
This commit is contained in:
patrik
2012-07-21 21:24:14 +00:00
parent 096e40d470
commit 977996e5fa
4 changed files with 502 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added rdp library and the script rdp-enum-encryption that enumerates
both the Security Layer and Encryption level of the RDP service. [Patrik
Karlsson]
o [NSE] Added flume-master-info by John Bond. This script gets info o [NSE] Added flume-master-info by John Bond. This script gets info
from Apache Flume, which is a log collection service. from Apache Flume, which is a log collection service.

337
nselib/rdp.lua Normal file
View File

@@ -0,0 +1,337 @@
---
-- A minimal RDP library. Currently has functionality to determine encryption
-- and cipher support.
--
--
-- @author "Patrik Karlsson <patrik@cqure.net>"
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
--
local bin = require("bin")
local stdnse = require("stdnse")
_ENV = stdnse.module("rdp", stdnse.seeall)
Packet = {
TPKT = {
new = function(self, data)
local o = { data = tostring(data), version = 3 }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return bin.pack(">CCSA",
self.version,
self.reserved or 0,
(self.data and #self.data + 4 or 4),
self.data
)
end,
parse = function(data)
local tpkt = Packet.TPKT:new()
local pos
pos, tpkt.version, tpkt.reserved, tpkt.length = bin.unpack(">CCS", data)
pos, tpkt.data = bin.unpack("A" .. (#data - pos), data, pos)
return tpkt
end
},
ITUT = {
new = function(self, code, data)
local o = { data = tostring(data), code = code }
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local itut = Packet.ITUT:new()
local pos
pos, itut.length, itut.code = bin.unpack("CC", data)
if ( itut.code == 0xF0 ) then
pos, itut.eot = bin.unpack("C", data, pos)
elseif ( itut.code == 0xD0 ) then
pos, itut.dstref, itut.srcref, itut.class = bin.unpack(">SSC", data, pos)
end
pos, itut.data = bin.unpack("A" .. (#data - pos), data, pos)
return itut
end,
__tostring = function(self)
local len = (self.code ~= 0xF0 and #self.data + 1 or 2)
local data = bin.pack("CC",
len,
self.code or 0
)
if ( self.code == 0xF0 ) then
data = data .. bin.pack("C", 0x80) -- EOT
end
return data .. self.data
end,
},
}
Request = {
ConnectionRequest = {
new = function(self, proto)
local o = { proto = proto }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
local cookie = "mstshash=nmap"
local itpkt_len = 21 + #cookie
local itut_len = 16 + #cookie
local data = bin.pack(">SSCA",
0x0000, -- dst reference
0x0000, -- src reference
0x00, -- class and options
("Cookie: %s\r\n"):format(cookie))
if ( self.proto ) then
data = data .. bin.pack("<II",
0x00080001, -- Unknown
self.proto -- protocol
)
end
return tostring(Packet.TPKT:new(Packet.ITUT:new(0xE0, data)))
end
},
MCSConnectInitial = {
new = function(self, cipher)
local o = { cipher = cipher }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
local data = bin.pack("<HIH",
"7f 65" .. -- BER: Application-Defined Type = APPLICATION 101,
"82 01 90" .. -- BER: Type Length = 404 bytes
"04 01 01" .. -- Connect-Initial::callingDomainSelector
"04 01 01" .. -- Connect-Initial::calledDomainSelector
"01 01 ff" .. -- Connect-Initial::upwardFlag = TRUE
"30 19" .. -- Connect-Initial::targetParameters (25 bytes)
"02 01 22" .. -- DomainParameters::maxChannelIds = 34
"02 01 02" .. -- DomainParameters::maxUserIds = 2
"02 01 00" .. -- DomainParameters::maxTokenIds = 0
"02 01 01" .. -- DomainParameters::numPriorities = 1
"02 01 00" .. -- DomainParameters::minThroughput = 0
"02 01 01" .. -- DomainParameters::maxHeight = 1
"02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535
"02 01 02" .. -- DomainParameters::protocolVersion = 2
"30 19" .. -- Connect-Initial::minimumParameters (25 bytes)
"02 01 01" .. -- DomainParameters::maxChannelIds = 1
"02 01 01" .. -- DomainParameters::maxUserIds = 1
"02 01 01" .. -- DomainParameters::maxTokenIds = 1
"02 01 01" .. -- DomainParameters::numPriorities = 1
"02 01 00" .. -- DomainParameters::minThroughput = 0
"02 01 01" .. -- DomainParameters::maxHeight = 1
"02 02 04 20" .. -- DomainParameters::maxMCSPDUsize = 1056
"02 01 02" .. -- DomainParameters::protocolVersion = 2
"30 1c" .. -- Connect-Initial::maximumParameters (28 bytes)
"02 02 ff ff" .. -- DomainParameters::maxChannelIds = 65535
"02 02 fc 17" .. -- DomainParameters::maxUserIds = 64535
"02 02 ff ff" .. -- DomainParameters::maxTokenIds = 65535
"02 01 01" .. -- DomainParameters::numPriorities = 1
"02 01 00" .. -- DomainParameters::minThroughput = 0
"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)
"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
"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
"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
"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::imeFileName = ""
"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
"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" .. -- 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
"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
"02 c0 0c 00", -- TS_UD_HEADER::type = CS_SECURITY (0xc002), length = 12 bytes
-- "1b 00 00 00" .. -- TS_UD_CS_SEC::encryptionMethods
self.cipher or 0,
"00 00 00 00" .. -- TS_UD_CS_SEC::extEncryptionMethods
"03 c0 2c 00" .. -- TS_UD_HEADER::type = CS_NET (0xc003), length = 44 bytes
"03 00 00 00" .. -- TS_UD_CS_NET::channelCount = 3
"72 64 70 64 72 00 00 00" .. -- CHANNEL_DEF::name = "rdpdr"
"00 00 80 80" .. -- CHANNEL_DEF::options = 0x80800000
"63 6c 69 70 72 64 72 00" .. -- CHANNEL_DEF::name = "cliprdr"
"00 00 a0 c0" .. -- CHANNEL_DEF::options = 0xc0a00000
"72 64 70 73 6e 64 00 00" .. -- CHANNEL_DEF::name = "rdpsnd"
"00 00 00 c0" -- CHANNEL_DEF::options = 0xc0000000
)
return tostring(Packet.TPKT:new(Packet.ITUT:new(0xF0, data)))
end
}
}
Response = {
ConnectionConfirm = {
new = function(self)
local o = { }
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local cc = Response.ConnectionConfirm:new()
local pos, _
cc.tpkt = Packet.TPKT.parse(data)
cc.itut = Packet.ITUT.parse(cc.tpkt.data)
return cc
end,
},
MCSConnectResponse = {
new = function(self)
local o = { }
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local cr = Response.MCSConnectResponse:new()
cr.tpkt = Packet.TPKT.parse(data)
cr.itut = Packet.ITUT.parse(cr.tpkt.data)
return cr
end
}
}
Comm = {
-- Creates a new Comm instance
-- @param host table
-- @param port table
-- @return o instance of Comm
new = function(self, host, port)
local o = { host = host, port = port }
setmetatable(o, self)
self.__index = self
return o
end,
-- Connect to the server
-- @return status true on success, false on failure
-- @return err string containing error message, if status is false
connect = function(self)
self.socket = nmap.new_socket()
self.socket:set_timeout(5000)
if ( not(self.socket:connect(self.host, self.port)) ) then
return false, "Failed connecting to server"
end
return true
end,
-- Close the connection to the server
-- @return status true on success, false on failure
close = function(self)
return self.socket:close()
end,
-- Sends a message to the server
-- @param pkt an instance of Request.*
-- @return status true on success, false on failure
-- @return err string containing error message, if status is false
send = function(self, pkt)
return self.socket:send(tostring(pkt))
end,
-- Receives a message from the server
-- @return status true on success, false on failure
-- @return err string containing error message, if status is false
recv = function(self)
return self.socket:receive()
end,
-- Sends a message to the server and receives the response
-- @param pkt an instance of Request.*
-- @return status true on success, false on failure
-- @return err string containing error message, if status is false
-- pkt instance of Response.* on success
exch = function(self, pkt)
local status, err = self:send(pkt)
if ( not(status) ) then
return false, err
end
local data
status, data = self:recv()
if ( #data< 5 ) then
return false, "Packet too short"
end
local pos, itut_code = bin.unpack("C", data, 6)
if ( itut_code == 0xD0 ) then
stdnse.print_debug(2, "RDP: Received ConnectionConfirm response")
return true, Response.ConnectionConfirm.parse(data)
elseif ( itut_code == 0xF0 ) then
return true, Response.MCSConnectResponse.parse(data)
end
return false, "Received unhandled packet"
end,
}
return _ENV;

View File

@@ -0,0 +1,160 @@
description = [[
Determines what Security layer and Encryption level that is supported by the
RDP service. It does so by cycling through all existing protocols and ciphers.
When run in debug mode, the script also returns the protocols and ciphers that
fail and any errors that were reported.
The script was inspired by MWR's RDP Cipher Checker
http://labs.mwrinfosecurity.com/tools/2009/01/12/rdp-cipher-checker/
]]
---
-- @usage
-- nmap -p 3389 --script rdp-enum-encryption <ip>
--
-- @output
-- PORT STATE SERVICE
-- 3389/tcp open ms-wbt-server
-- | rdp-enum-encryption:
-- | Security layer
-- | CredSSP: SUCCESS
-- | Native RDP: SUCCESS
-- | SSL: SUCCESS
-- | RDP Encryption level: High
-- | 128-bit RC4: SUCCESS
-- |_ FIPS 140-1: SUCCESS
--
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
local shortport = require("shortport")
local rdp = require("rdp")
local stdnse = require("stdnse")
categories = {"safe", "discovery"}
portrule = shortport.port_or_service(3389, "ms-wbt-server")
local function enum_protocols(host, port)
local PROTOCOLS = {
["Native RDP"] = 0,
["SSL"] = 1,
["CredSSP"] = 3
}
local ERRORS = {
[1] = "SSL_REQUIRED_BY_SERVER",
[2] = "SSL_NOT_ALLOWED_BY_SERVER",
[3] = "SSL_CERT_NOT_ON_SERVER",
[4] = "INCONSISTENT_FLAGS",
[5] = "HYBRID_REQUIRED_BY_SERVER"
}
local res_proto = { name = "Security layer" }
for k, v in pairs(PROTOCOLS) do
local comm = rdp.Comm:new(host, port)
if ( not(comm:connect()) ) then
return false, "ERROR: Failed to connect to server"
end
local cr = rdp.Request.ConnectionRequest:new(v)
status, response = comm:exch(cr)
comm:close()
if ( not(status) ) then
return false, response
end
local pos, success = bin.unpack("C", response.itut.data)
if ( success == 2 ) then
table.insert(res_proto, ("%s: SUCCESS"):format(k))
elseif ( nmap.debugging() > 0 ) then
local pos, err = bin.unpack("C", 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
end
table.sort(res_proto)
return true, res_proto
end
local function enum_ciphers(host, port)
local CIPHERS = {
{ ["40-bit RC4"] = 1 },
{ ["56-bit RC4"] = 8 },
{ ["128-bit RC4"] = 2 },
{ ["FIPS 140-1"] = 16 }
}
local ENC_LEVELS = {
[0] = "None",
[1] = "Low",
[2] = "Client Compatible",
[3] = "High",
[4] = "FIPS Compliant",
}
local res_ciphers = {}
local function get_ordered_ciphers()
i = 0
return function()
i = i + 1
if ( not(CIPHERS[i]) ) then return end
for k,v in pairs(CIPHERS[i]) do
return k, v
end
end
end
for k, v in get_ordered_ciphers() do
local comm = rdp.Comm:new(host, port)
if ( not(comm:connect()) ) then
return false, "ERROR: Failed to connect to server"
end
local cr = rdp.Request.ConnectionRequest:new()
status, response = comm:exch(cr)
if ( not(status) ) then
break
end
local msc = rdp.Request.MCSConnectInitial:new(v)
local status, response = comm:exch(msc)
comm:close()
if ( status ) then
local pos, enc_level = bin.unpack("C", response.itut.data, 95 + 8)
local pos, enc_cipher= bin.unpack("C", response.itut.data, 95 + 4)
if ( enc_cipher == v ) then
table.insert(res_ciphers, ("%s: SUCCESS"):format(k))
end
res_ciphers.name = ("RDP Encryption level: %s"):format(ENC_LEVELS[enc_level] or "Unknown")
elseif ( nmap.debugging() > 0 ) then
table.insert(res_ciphers, ("%s: FAILURE"):format(k))
end
end
return true, res_ciphers
end
action = function(host, port)
local result = {}
local status, res_proto = enum_protocols(host, port)
if ( not(status) ) then
return res_proto
end
local status, res_ciphers = enum_ciphers(host, port)
if ( not(status) ) then
return res_ciphers
end
table.insert(result, res_proto)
table.insert(result, res_ciphers)
return stdnse.format_output(true, result)
end

View File

@@ -301,6 +301,7 @@ Entry { filename = "pptp-version.nse", categories = { "version", } }
Entry { filename = "qscan.nse", categories = { "discovery", "safe", } } Entry { filename = "qscan.nse", categories = { "discovery", "safe", } }
Entry { filename = "quake3-info.nse", categories = { "default", "discovery", "safe", "version", } } Entry { filename = "quake3-info.nse", categories = { "default", "discovery", "safe", "version", } }
Entry { filename = "quake3-master-getservers.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "quake3-master-getservers.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "rdp-enum-encryption.nse", categories = { "discovery", "safe", } }
Entry { filename = "rdp-vuln-ms12-020.nse", categories = { "intrusive", "vuln", } } Entry { filename = "rdp-vuln-ms12-020.nse", categories = { "intrusive", "vuln", } }
Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default", "safe", } } Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default", "safe", } }
Entry { filename = "redis-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "redis-brute.nse", categories = { "brute", "intrusive", } }