diff --git a/CHANGELOG b/CHANGELOG index 850287cb8..7a002f12a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] More VNC updates: Support for VeNCrypt auth type, output of + authentication sub-types in vnc-info, and all zero-authentication types are + recognized and reported. [Daniel Miller] + o [NSE] Update to enable smb-os-discovery to augment version detection for certain SMB related services using data that the script discovers. [Tom Sellers] diff --git a/nselib/sslcert.lua b/nselib/sslcert.lua index beef2cd47..0f70c1f51 100644 --- a/nselib/sslcert.lua +++ b/nselib/sslcert.lua @@ -568,51 +568,36 @@ StartTLS = { local sock = v.socket if v:supportsSecType(vnc.VNC.sectypes.VENCRYPT) then - status = sock:send( bin.pack("C", vnc.VNC.sectypes.VENCRYPT) ) + status, data = v:handshake_vencrypt() if not status then - return false, "Failed to select VeNCrypt authentication type" + return false, string.format("Failed VeNCrypt handshake: %s", data) end - - local status, buf = sock:receive_buf(match.numbytes(2), true) - local pos, maj, min = bin.unpack("CC", buf) - if maj ~= 0 or min ~= 2 then - return false, string.format("Unknown VeNCrypt version: %d.%d", maj, min) - end - sock:send(bin.pack("CC", maj, min)) - status, buf = sock:receive_buf(match.numbytes(1), true) - pos, status = bin.unpack("C", buf) - if status ~= 0 then - return false, string.format("Server refused VeNCrypt version %d.%d", maj, min) - end - - status, buf = sock:receive_buf(match.numbytes(1), true) - local pos, nauth = bin.unpack("C", buf) - if nauth == 0 then - return false, "No VeNCrypt auth subtypes received" - end - - -- vencrypt auth types are u32 - status, buf = sock:receive_buf(match.numbytes(nauth * 4), true) + local auth_order = { + -- X509 types are not anonymous, have real certs + vnc.VENCRYPT_SUBTYPES.X509VNC, + vnc.VENCRYPT_SUBTYPES.X509SASL, + vnc.VENCRYPT_SUBTYPES.X509NONE, + vnc.VENCRYPT_SUBTYPES.X509PLAIN, + -- TLS types use anonymous DH handshakes + vnc.VENCRYPT_SUBTYPES.TLSVNC, + vnc.VENCRYPT_SUBTYPES.TLSSASL, + vnc.VENCRYPT_SUBTYPES.TLSNONE, + vnc.VENCRYPT_SUBTYPES.TLSPLAIN, + -- PLAIN type doesn't use TLS + } local best - pos = 1 - for i=1, nauth do - local auth - pos, auth = bin.unpack(">I", buf, pos) - if auth >= 260 and auth <= 263 then - -- X509 auth subtype - best = auth + for i=1, #auth_order do + if stdnse.contains(v.vencrypt.types, auth_order[i]) then + best = auth_order[i] break - elseif auth >= 257 then - -- other TLS auth subtype (Plain is 256) - -- These are anon types, so no cert available - best = auth end end + if not best then return false, "No TLS VeNCrypt auth subtype received" end sock:send(bin.pack(">I", best)) - status, buf = sock:receive_buf(match.numbytes(1), true) + local status, buf = sock:receive_buf(match.numbytes(1), true) if not status or string.byte(buf, 1) ~= 1 then return false, "VeNCrypt auth subtype refused" end diff --git a/nselib/vnc.lua b/nselib/vnc.lua index c9d933ac6..24b510738 100644 --- a/nselib/vnc.lua +++ b/nselib/vnc.lua @@ -34,6 +34,29 @@ _ENV = stdnse.module("vnc", stdnse.seeall) local HAVE_SSL, openssl = pcall(require,'openssl') +VENCRYPT_SUBTYPES = { + PLAIN = 256, + TLSNONE = 257, + TLSVNC = 258, + TLSPLAIN = 259, + X509NONE = 260, + X509VNC = 261, + X509PLAIN = 262, + X509SASL = 263, + TLSSASL = 264, +} +VENCRYPT_SUBTYPES_STR = { + [256] = "Plain", + [257] = "None, Anonymous TLS", + [258] = "VNC auth, Anonymous TLS", + [259] = "Plain, Anonymous TLS", + [260] = "None, Server-authenticated TLS", + [261] = "VNC auth, Server-authenticated TLS", + [262] = "Plain, Server-authenticated TLS", + [263] = "SASL, Server-authenticated TLS", + [264] = "SASL, Anonymous TLS", +} + local function process_error(socket) local status, tmp = socket:receive_buf(match.numbytes(4), true) if( not(status) ) then @@ -47,15 +70,39 @@ local function process_error(socket) return false, err end +local function first_of (list, lookup) + for i=1, #list do + if stdnse.contains(lookup, list[i]) then + return list[i] + end + end +end + +-- generalized output formatter for security types and subtypes +local function get_types_as_table (types, lookup) + local tmp = {} + local typemt = { + __tostring = function(me) + return ("%s (%s)"):format(me.name, me.type) + end + } + for i=1, types.count do + local t = {name = lookup[types.types[i]] or "Unknown security type", type=types.types[i]} + setmetatable(t, typemt) + table.insert( tmp, t ) + end + return tmp +end + VNC = { versions = { - ["RFB 003.003\n"] = "3.3", - ["RFB 003.007\n"] = "3.7", - ["RFB 003.008\n"] = "3.8", + ["RFB 003.003"] = "3.3", + ["RFB 003.007"] = "3.7", + ["RFB 003.008"] = "3.8", -- Mac Screen Sharing, could probably be used to fingerprint OS - ["RFB 003.889\n"] = "3.889", + ["RFB 003.889"] = "3.889", }, sectypes = { @@ -127,11 +174,12 @@ VNC = { -- @return status, true on success, false on failure -- @return error string containing error message if status is false handshake = function(self) - local status, data = self.socket:receive_buf(match.numbytes(12), true) - if not string.match(data, "^RFB %d%d%d%.%d%d%d[\r\n]") then + local status, data = self.socket:receive_buf("[\r\n]+", true) + if not status or not string.match(data, "^RFB %d%d%d%.%d%d%d[\r\n]") then stdnse.debug1("ERROR: Not a VNC port. Banner: %s", data) return false, "Not a VNC port." end + data = data:sub(1,11) local vncsec = { count = 1, types = {} @@ -144,14 +192,14 @@ VNC = { self.protover = VNC.versions[data] local cli_version = data if ( not(self.protover) ) then - stdnse.debug1("ERROR: VNC:handshake unsupported version (%s)", data:sub(1,11)) + stdnse.debug1("ERROR: VNC:handshake unsupported version (%s)", data) self.protover = string.match(data, "^RFB (%d+%.%d+)") --return false, ("Unsupported version (%s)"):format(data:sub(1,11)) local versions = { - "RFB 003.003\n", - "RFB 003.007\n", - "RFB 003.008\n", - "RFB 003.889\n", + "RFB 003.003", + "RFB 003.007", + "RFB 003.008", + "RFB 003.889", } for i=1, #versions do if versions[i] >= data then @@ -161,13 +209,14 @@ VNC = { end end - status = self.socket:send( cli_version or "RFB 003.889\n" ) + self.client_version = VNC.versions[cli_version or "RFB 003.889"] + status = self.socket:send( (cli_version or "RFB 003.889") .. "\n" ) if ( not(status) ) then stdnse.debug1("ERROR: VNC:handshake failed to send client version") return false, "ERROR: VNC:handshake failed" end - if ( cli_version == "RFB 003.003\n" ) then + if ( self.client_version == "3.3" ) then local status, tmp = self.socket:receive_buf(match.numbytes(4), true) if( not(status) ) then return false, "VNC:handshake failed to receive security data" @@ -231,18 +280,43 @@ VNC = { -- @return status true on success, false on failure -- @return err string containing error message when status is false login = function( self, username, password, authtype ) - if not authtype then - if self:supportsSecType( VNC.sectypes.VNCAUTH ) then - return self:login_vncauth(username, password) - elseif self:supportsSecType( VNC.sectypes.TLS ) then - return self:login_tls(username, password) - else - return false, "vnc.lua does not support that security type" - end - elseif ( not( self:supportsSecType( authtype ) ) ) then - return false, "The server does not support the \"VNC Authentication\" security type." + if ( not(password) ) then + return false, "No password was supplied" end + if not authtype then + if self:supportsSecType( VNC.sectypes.NONE ) then + return self:login_none() + + elseif self:supportsSecType( VNC.sectypes.VNCAUTH ) then + return self:login_vncauth(username, password) + + elseif self:supportsSecType( VNC.sectypes.TLS ) then + return self:login_tls(username, password) + + elseif self:supportsSecType( VNC.sectypes.VENCRYPT ) then + return self:login_vencrypt(username, password) + + else + return false, "The server does not support any matching security type" + end + elseif ( not( self:supportsSecType( authtype ) ) ) then + return false, string.format( + 'The server does not support the "%s" security type.', VNC.sectypes_str[authtype]) + end + + end, + + login_none = function (self) + local status = self.socket:send( bin.pack("C", VNC.sectypes.NONE) ) + if not status then + return false, "Failed to select None authentication type" + end + if self.client_version == "3.8" then + return self:check_auth_result() + end + -- nothing to do here! + return true end, --- Attempts to login to the VNC service using VNC Authentication @@ -252,14 +326,9 @@ VNC = { -- @return status true on success, false on failure -- @return err string containing error message when status is false login_vncauth = function( self, username, password ) - if ( not(password) ) then - return false, "No password was supplied" - end - - -- Announce that we support VNC Authentication local status = self.socket:send( bin.pack("C", VNC.sectypes.VNCAUTH) ) - if ( not(status) ) then - return false, "Failed to select authentication type" + if not status then + return false, "Failed to send authentication type" end local status, chall = self.socket:receive_buf(match.numbytes(16), true) @@ -274,23 +343,22 @@ VNC = { if ( not(status) ) then return false, "Failed to send authentication response to server" end + return self:check_auth_result() + end, + check_auth_result = function(self) local status, result = self.socket:receive_buf(match.numbytes(4), true) if ( not(status) ) then return false, "Failed to retrieve authentication status from server" end if ( select(2, bin.unpack("I", result) ) ~= 0 ) then - return false, ("Authentication failed with password %s"):format(password) + return false, "Authentication failed" end - return true, "" + return true end, - login_tls = function( self, username, password ) - if ( not(password) ) then - return false, "No password was supplied" - end - + handshake_tls = function(self) local status = self.socket:send( bin.pack("C", VNC.sectypes.TLS) ) if not status then return false, "Failed to select TLS authentication type" @@ -325,26 +393,129 @@ VNC = { table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) ) end self.vncsec = vncsec + return true + end, + login_tls = function( self, username, password ) + local status, err = self:handshake_tls() + if not status then + return status, err + end return self:login(username, password) end, + handshake_vencrypt = function(self) + local status = self.socket:send( bin.pack("C", VNC.sectypes.VENCRYPT) ) + if not status then + return false, "Failed to select VeNCrypt authentication type" + end + + local status, buf = self.socket:receive_buf(match.numbytes(2), true) + local pos, maj, min = bin.unpack("CC", buf) + if maj ~= 0 or min ~= 2 then + return false, string.format("Unknown VeNCrypt version: %d.%d", maj, min) + end + self.socket:send(bin.pack("CC", maj, min)) + status, buf = self.socket:receive_buf(match.numbytes(1), true) + pos, status = bin.unpack("C", buf) + if status ~= 0 then + return false, string.format("Server refused VeNCrypt version %d.%d", maj, min) + end + + status, buf = self.socket:receive_buf(match.numbytes(1), true) + local pos, nauth = bin.unpack("C", buf) + if nauth == 0 then + return false, "No VeNCrypt auth subtypes received" + end + + -- vencrypt auth types are u32 + status, buf = self.socket:receive_buf(match.numbytes(nauth * 4), true) + pos = 1 + local vencrypt = { + count = nauth, + types = {} + } + for i=1, nauth do + local auth + pos, auth = bin.unpack(">I", buf, pos) + table.insert(vencrypt.types, auth) + end + self.vencrypt = vencrypt + return true + end, + + login_vencrypt = function(self, username, password) + local status, err = self:handshake_vencrypt() + if not status then + return status, err + end + + local subauth = first_of({ + VENCRYPT_SUBTYPES.TLSNONE, + VENCRYPT_SUBTYPES.X509NONE, + VENCRYPT_SUBTYPES.PLAIN, + VENCRYPT_SUBTYPES.TLSPLAIN, + VENCRYPT_SUBTYPES.X509PLAIN, + VENCRYPT_SUBTYPES.TLSVNC, + VENCRYPT_SUBTYPES.X509VNC, + -- These not supported yet + --VENCRYPT_SUBTYPES.TLSSASL, + --VENCRYPT_SUBTYPES.X509SASL, + }, self.vencrypt.types) + + if not subauth then + return false, "The server does not support any supported security type" + end + + self.socket:send(bin.pack(">I", subauth)) + local status, buf = self.socket:receive_buf(match.numbytes(1), true) + if not status or string.byte(buf, 1) ~= 1 then + return false, "VeNCrypt auth subtype refused" + end + + if subauth == VENCRYPT_SUBTYPES.PLAIN then + return self:login_plain(username, password) + end + + status, err = self.socket:reconnect_ssl() + if not status then + return false, "Failed to reconnect SSL to VNC server" + end + + if subauth == VENCRYPT_SUBTYPES.TLSNONE or subauth == VENCRYPT_SUBTYPES.X509NONE then + return self:check_auth_result() + elseif subauth == VENCRYPT_SUBTYPES.TLSVNC or subauth == VENCRYPT_SUBTYPES.X509VNC then + return self:login_vncauth(username, password) + elseif subauth == VENCRYPT_SUBTYPES.TLSPLAIN or subauth == VENCRYPT_SUBTYPES.X509PLAIN then + return self:login_plain(username, password) + elseif subauth == VENCRYPT_SUBTYPES.TLSSASL or subauth == VENCRYPT_SUBTYPES.X509SASL then + return self:login_sasl(username, password) + end + + end, + + login_plain = function(self, username, password) + local status = self.socket:send(bin.pack(">IIAA", #username, #password, username, password)) + if not status then + return false, "Failed to send plain auth" + end + + return self:check_auth_result() + end, + + login_sasl = function(self, username, password) + -- TODO: support this! + return false, "Unsupported" + end, + --- Returns all supported security types as a table -- -- @return table containing a entry for each security type getSecTypesAsTable = function( self ) - local tmp = {} - local typemt = { - __tostring = function(me) - return ("%s (%s)"):format(me.name, me.type) - end - } - for i=1, self.vncsec.count do - local t = {name=VNC.sectypes_str[self.vncsec.types[i]] or "Unknown security type", type=self.vncsec.types[i]} - setmetatable(t, typemt) - table.insert( tmp, t ) - end - return true, tmp + return get_types_as_table(self.vncsec, VNC.sectypes_str) + end, + getVencryptTypesAsTable = function (self) + return get_types_as_table(self.vencrypt, VENCRYPT_SUBTYPES_STR) end, --- Checks if the supplied security type is supported or not @@ -367,6 +538,39 @@ VNC = { return self.protover end, + --- Send a ClientInit message. + --@param shared boolean determining whether the screen should be shared, or whether other logged-on users should be booted. + --@return status true if message was successful, false otherwise + --@return table containing contents of ServerInit message, or error message. + client_init = function (self, shared) + self.socket:send(shared and "\x01" or "\x00") + local status, buf = self.socket:receive_buf(match.numbytes(24), true) + if not status then + return false, "Did not receive ServerInit message" + end + local pos, width, height, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, gshift, bshift, pad1, pad2, namelen = bin.unpack(">SSCCCCSSSCCCSCI", buf) + local status, buf = self.socket:receive_buf(match.numbytes(namelen), true) + if not status then + return false, "Did not receive ServerInit desktop name" + end + local pos, name = bin.unpack("A" .. namelen, buf) + return true, { + width = width, + height = height, + bpp = bpp, + depth = depth, + bigendian = bigendian, + truecolor = truecolor, + rmax = rmax, + gmax = gmax, + bmax = bmax, + rshift = rshift, + gshift = gshift, + bshift = bshift, + name = name + } + + end } return _ENV; diff --git a/scripts/vnc-brute.nse b/scripts/vnc-brute.nse index d3bb06040..859cbf0ec 100644 --- a/scripts/vnc-brute.nse +++ b/scripts/vnc-brute.nse @@ -122,8 +122,9 @@ Driver = end status, data = vnc:login( nil, "is_sec_mec_supported?" ) - -- Check secondary auth type after potential TLS handshake that happened during login - if ( vnc:supportsSecType(vnc.sectypes.NONE) ) then + -- Check whether auth succeeded. This is most likely because one of the + -- NONE auth types was supported, since vnc.lua will just return true in that case. + if status then return false, "No authentication required" end diff --git a/scripts/vnc-info.nse b/scripts/vnc-info.nse index 8c7ae6e72..41ef5b035 100644 --- a/scripts/vnc-info.nse +++ b/scripts/vnc-info.nse @@ -45,26 +45,67 @@ local function fail(err) return stdnse.format_output(false, err) end action = function(host, port) - local vnc = vnc.VNC:new( host, port ) + local v = vnc.VNC:new( host, port ) local status, data local result = stdnse.output_table() - status, data = vnc:connect() + status, data = v:connect() if ( not(status) ) then return fail(data) end - status, data = vnc:handshake() + status, data = v:handshake() if ( not(status) ) then return fail(data) end - status, data = vnc:getSecTypesAsTable() - if ( not(status) ) then return fail(data) end + data = v:getSecTypesAsTable() - result["Protocol version"] = vnc:getProtocolVersion() + result["Protocol version"] = v:getProtocolVersion() if ( data and #data ~= 0 ) then result["Security types"] = data end - if ( vnc:supportsSecType(vnc.sectypes.NONE) ) then + local none_auth = false + if ( v:supportsSecType(v.sectypes.NONE) ) then + none_auth = true + end + + if v:supportsSecType(v.sectypes.VENCRYPT) then + status, data = v:handshake_vencrypt() + if not status then + stdnse.debug1("Failed to handshake VeNCrypt: %s", data) + else + result["VeNCrypt auth subtypes"] = v:getVencryptTypesAsTable() + if not none_auth then + for i=1, v.vencrypt.count do + if v.vencrypt.types[i] == vnc.VENCRYPT_SUBTYPES.TLSNONE or + v.vencrypt.types[i] == vnc.VENCRYPT_SUBTYPES.TLSNONE then + none_auth = true + break + end + end + end + end + -- Reset the connection for further tests + v:disconnect() + end + + if v:supportsSecType(v.sectypes.TLS) then + if not v.socket:get_info() then + -- reconnect if necessary + v:connect() + v:handshake() + end + status, data = v:handshake_tls() + if not status then + stdnse.debug1("Failed to handshake TLS: %s", data) + else + result["TLS auth subtypes"] = v:getSecTypesAsTable() + if v:supportsSecType(v.sectypes.NONE) then + none_auth = true + end + end + end + + if none_auth then result["WARNING"] = "Server does not require authentication" end