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
-- | 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 bin = require("bin")
local nmap = require("nmap")
local table = require("table")
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)
local 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()
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
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()
local 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
@KyuuKazami