diff --git a/CHANGELOG b/CHANGELOG index 87dd57095..c1c560bc4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # 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 from Apache Flume, which is a log collection service. diff --git a/nselib/rdp.lua b/nselib/rdp.lua new file mode 100644 index 000000000..7d89c1616 --- /dev/null +++ b/nselib/rdp.lua @@ -0,0 +1,337 @@ +--- +-- A minimal RDP library. Currently has functionality to determine encryption +-- and cipher support. +-- +-- +-- @author "Patrik Karlsson " +-- @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(" +-- +-- @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 \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index c46f61832..d30f81e83 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -301,6 +301,7 @@ Entry { filename = "pptp-version.nse", categories = { "version", } } Entry { filename = "qscan.nse", categories = { "discovery", "safe", } } Entry { filename = "quake3-info.nse", categories = { "default", "discovery", "safe", "version", } } 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 = "realvnc-auth-bypass.nse", categories = { "auth", "default", "safe", } } Entry { filename = "redis-brute.nse", categories = { "brute", "intrusive", } }