summaryrefslogtreecommitdiffstats
path: root/scripts/rdp-enum-encryption.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/rdp-enum-encryption.nse')
-rw-r--r--scripts/rdp-enum-encryption.nse213
1 files changed, 213 insertions, 0 deletions
diff --git a/scripts/rdp-enum-encryption.nse b/scripts/rdp-enum-encryption.nse
new file mode 100644
index 0000000..6451bee
--- /dev/null
+++ b/scripts/rdp-enum-encryption.nse
@@ -0,0 +1,213 @@
+description = [[
+Determines which Security layer and Encryption level 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
+-- | Security layer
+-- | CredSSP (NLA): SUCCESS
+-- | CredSSP with Early User Auth: SUCCESS
+-- | Native RDP: SUCCESS
+-- | RDSTLS: SUCCESS
+-- | SSL: SUCCESS
+-- | RDP Encryption level: High
+-- | 40-bit RC4: SUCCESS
+-- | 56-bit RC4: SUCCESS
+-- | 128-bit RC4: SUCCESS
+-- | FIPS 140-1: SUCCESS
+-- |_ RDP Protocol Version: RDP 5.x, 6.x, 7.x, or 8.x server
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+
+
+local nmap = require("nmap")
+local table = require("table")
+local shortport = require("shortport")
+local rdp = require("rdp")
+local stdnse = require("stdnse")
+local string = require "string"
+
+categories = {"safe", "discovery"}
+
+portrule = shortport.port_or_service(3389, "ms-wbt-server")
+
+local function fail (err) return stdnse.format_output(false, err) end
+
+local function enum_protocols(host, port)
+ local PROTOCOLS = {
+ ["Native RDP"] = 0,
+ ["SSL"] = 1,
+ ["CredSSP (NLA)"] = 3,
+ ["RDSTLS"] = 4,
+ ["CredSSP with Early User Auth"] = 8
+ }
+
+ 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" }
+ 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)
+
+ 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
+
+ -- 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
+ end
+
+ comm:close()
+ end
+ table.sort(res_proto)
+ return true, res_proto, proto_version
+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 proto_version
+
+ local function get_ordered_ciphers()
+ local 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
+ -- 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, _ = 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
+ if ( response.ccr and response.ccr.enc_cipher == v ) then
+ table.insert(res_ciphers, ("%s: SUCCESS"):format(k))
+ end
+ res_ciphers.name = ("RDP Encryption level: %s"):format(ENC_LEVELS[response.ccr.enc_level] or "Unknown")
+
+ if response.ccr.proto_version then
+ proto_version = response.ccr.proto_version
+ end
+ elseif ( nmap.debugging() > 0 ) then
+ table.insert(res_ciphers, ("%s: FAILURE"):format(k))
+ end
+ end
+ return true, res_ciphers, proto_version
+end
+
+action = function(host, port)
+ local result = {}
+
+ local status, res_proto, proto_ver = enum_protocols(host, port)
+ if ( not(status) ) then
+ return res_proto
+ end
+
+ 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_ver then
+ table.insert(result, proto_ver)
+ elseif cipher_ver then
+ table.insert(result, cipher_ver)
+ end
+ return stdnse.format_output(true, result)
+end