1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-15 12:19:02 +00:00

VNC revamp and extension

This commit is contained in:
dmiller
2016-04-01 22:29:39 +00:00
parent e3bb213e14
commit 3af66a0445
5 changed files with 328 additions and 93 deletions

View File

@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*- # 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 o [NSE] Update to enable smb-os-discovery to augment version detection
for certain SMB related services using data that the script discovers. for certain SMB related services using data that the script discovers.
[Tom Sellers] [Tom Sellers]

View File

@@ -568,51 +568,36 @@ StartTLS = {
local sock = v.socket local sock = v.socket
if v:supportsSecType(vnc.VNC.sectypes.VENCRYPT) then 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 if not status then
return false, "Failed to select VeNCrypt authentication type" return false, string.format("Failed VeNCrypt handshake: %s", data)
end end
local auth_order = {
local status, buf = sock:receive_buf(match.numbytes(2), true) -- X509 types are not anonymous, have real certs
local pos, maj, min = bin.unpack("CC", buf) vnc.VENCRYPT_SUBTYPES.X509VNC,
if maj ~= 0 or min ~= 2 then vnc.VENCRYPT_SUBTYPES.X509SASL,
return false, string.format("Unknown VeNCrypt version: %d.%d", maj, min) vnc.VENCRYPT_SUBTYPES.X509NONE,
end vnc.VENCRYPT_SUBTYPES.X509PLAIN,
sock:send(bin.pack("CC", maj, min)) -- TLS types use anonymous DH handshakes
status, buf = sock:receive_buf(match.numbytes(1), true) vnc.VENCRYPT_SUBTYPES.TLSVNC,
pos, status = bin.unpack("C", buf) vnc.VENCRYPT_SUBTYPES.TLSSASL,
if status ~= 0 then vnc.VENCRYPT_SUBTYPES.TLSNONE,
return false, string.format("Server refused VeNCrypt version %d.%d", maj, min) vnc.VENCRYPT_SUBTYPES.TLSPLAIN,
end -- PLAIN type doesn't use TLS
}
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 best local best
pos = 1 for i=1, #auth_order do
for i=1, nauth do if stdnse.contains(v.vencrypt.types, auth_order[i]) then
local auth best = auth_order[i]
pos, auth = bin.unpack(">I", buf, pos)
if auth >= 260 and auth <= 263 then
-- X509 auth subtype
best = auth
break break
elseif auth >= 257 then
-- other TLS auth subtype (Plain is 256)
-- These are anon types, so no cert available
best = auth
end end
end end
if not best then if not best then
return false, "No TLS VeNCrypt auth subtype received" return false, "No TLS VeNCrypt auth subtype received"
end end
sock:send(bin.pack(">I", best)) 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 if not status or string.byte(buf, 1) ~= 1 then
return false, "VeNCrypt auth subtype refused" return false, "VeNCrypt auth subtype refused"
end end

View File

@@ -34,6 +34,29 @@ _ENV = stdnse.module("vnc", stdnse.seeall)
local HAVE_SSL, openssl = pcall(require,'openssl') 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 function process_error(socket)
local status, tmp = socket:receive_buf(match.numbytes(4), true) local status, tmp = socket:receive_buf(match.numbytes(4), true)
if( not(status) ) then if( not(status) ) then
@@ -47,15 +70,39 @@ local function process_error(socket)
return false, err return false, err
end 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 = { VNC = {
versions = { versions = {
["RFB 003.003\n"] = "3.3", ["RFB 003.003"] = "3.3",
["RFB 003.007\n"] = "3.7", ["RFB 003.007"] = "3.7",
["RFB 003.008\n"] = "3.8", ["RFB 003.008"] = "3.8",
-- Mac Screen Sharing, could probably be used to fingerprint OS -- Mac Screen Sharing, could probably be used to fingerprint OS
["RFB 003.889\n"] = "3.889", ["RFB 003.889"] = "3.889",
}, },
sectypes = { sectypes = {
@@ -127,11 +174,12 @@ VNC = {
-- @return status, true on success, false on failure -- @return status, true on success, false on failure
-- @return error string containing error message if status is false -- @return error string containing error message if status is false
handshake = function(self) handshake = function(self)
local status, data = self.socket:receive_buf(match.numbytes(12), true) local status, data = self.socket:receive_buf("[\r\n]+", true)
if not string.match(data, "^RFB %d%d%d%.%d%d%d[\r\n]") then 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) stdnse.debug1("ERROR: Not a VNC port. Banner: %s", data)
return false, "Not a VNC port." return false, "Not a VNC port."
end end
data = data:sub(1,11)
local vncsec = { local vncsec = {
count = 1, count = 1,
types = {} types = {}
@@ -144,14 +192,14 @@ VNC = {
self.protover = VNC.versions[data] self.protover = VNC.versions[data]
local cli_version = data local cli_version = data
if ( not(self.protover) ) then 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+)") self.protover = string.match(data, "^RFB (%d+%.%d+)")
--return false, ("Unsupported version (%s)"):format(data:sub(1,11)) --return false, ("Unsupported version (%s)"):format(data:sub(1,11))
local versions = { local versions = {
"RFB 003.003\n", "RFB 003.003",
"RFB 003.007\n", "RFB 003.007",
"RFB 003.008\n", "RFB 003.008",
"RFB 003.889\n", "RFB 003.889",
} }
for i=1, #versions do for i=1, #versions do
if versions[i] >= data then if versions[i] >= data then
@@ -161,13 +209,14 @@ VNC = {
end end
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 if ( not(status) ) then
stdnse.debug1("ERROR: VNC:handshake failed to send client version") stdnse.debug1("ERROR: VNC:handshake failed to send client version")
return false, "ERROR: VNC:handshake failed" return false, "ERROR: VNC:handshake failed"
end 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) local status, tmp = self.socket:receive_buf(match.numbytes(4), true)
if( not(status) ) then if( not(status) ) then
return false, "VNC:handshake failed to receive security data" return false, "VNC:handshake failed to receive security data"
@@ -231,18 +280,43 @@ VNC = {
-- @return status true on success, false on failure -- @return status true on success, false on failure
-- @return err string containing error message when status is false -- @return err string containing error message when status is false
login = function( self, username, password, authtype ) login = function( self, username, password, authtype )
if not authtype then if ( not(password) ) then
if self:supportsSecType( VNC.sectypes.VNCAUTH ) then return false, "No password was supplied"
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."
end 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, end,
--- Attempts to login to the VNC service using VNC Authentication --- Attempts to login to the VNC service using VNC Authentication
@@ -252,14 +326,9 @@ VNC = {
-- @return status true on success, false on failure -- @return status true on success, false on failure
-- @return err string containing error message when status is false -- @return err string containing error message when status is false
login_vncauth = function( self, username, password ) 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) ) local status = self.socket:send( bin.pack("C", VNC.sectypes.VNCAUTH) )
if ( not(status) ) then if not status then
return false, "Failed to select authentication type" return false, "Failed to send authentication type"
end end
local status, chall = self.socket:receive_buf(match.numbytes(16), true) local status, chall = self.socket:receive_buf(match.numbytes(16), true)
@@ -274,23 +343,22 @@ VNC = {
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to send authentication response to server" return false, "Failed to send authentication response to server"
end end
return self:check_auth_result()
end,
check_auth_result = function(self)
local status, result = self.socket:receive_buf(match.numbytes(4), true) local status, result = self.socket:receive_buf(match.numbytes(4), true)
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve authentication status from server" return false, "Failed to retrieve authentication status from server"
end end
if ( select(2, bin.unpack("I", result) ) ~= 0 ) then if ( select(2, bin.unpack("I", result) ) ~= 0 ) then
return false, ("Authentication failed with password %s"):format(password) return false, "Authentication failed"
end end
return true, "" return true
end, end,
login_tls = function( self, username, password ) handshake_tls = function(self)
if ( not(password) ) then
return false, "No password was supplied"
end
local status = self.socket:send( bin.pack("C", VNC.sectypes.TLS) ) local status = self.socket:send( bin.pack("C", VNC.sectypes.TLS) )
if not status then if not status then
return false, "Failed to select TLS authentication type" return false, "Failed to select TLS authentication type"
@@ -325,26 +393,129 @@ VNC = {
table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) ) table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) )
end end
self.vncsec = vncsec 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) return self:login(username, password)
end, 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 --- Returns all supported security types as a table
-- --
-- @return table containing a entry for each security type -- @return table containing a entry for each security type
getSecTypesAsTable = function( self ) getSecTypesAsTable = function( self )
local tmp = {} return get_types_as_table(self.vncsec, VNC.sectypes_str)
local typemt = { end,
__tostring = function(me) getVencryptTypesAsTable = function (self)
return ("%s (%s)"):format(me.name, me.type) return get_types_as_table(self.vencrypt, VENCRYPT_SUBTYPES_STR)
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
end, end,
--- Checks if the supplied security type is supported or not --- Checks if the supplied security type is supported or not
@@ -367,6 +538,39 @@ VNC = {
return self.protover return self.protover
end, 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; return _ENV;

View File

@@ -122,8 +122,9 @@ Driver =
end end
status, data = vnc:login( nil, "is_sec_mec_supported?" ) status, data = vnc:login( nil, "is_sec_mec_supported?" )
-- Check secondary auth type after potential TLS handshake that happened during login -- Check whether auth succeeded. This is most likely because one of the
if ( vnc:supportsSecType(vnc.sectypes.NONE) ) then -- NONE auth types was supported, since vnc.lua will just return true in that case.
if status then
return false, "No authentication required" return false, "No authentication required"
end end

View File

@@ -45,26 +45,67 @@ local function fail(err) return stdnse.format_output(false, err) end
action = function(host, port) action = function(host, port)
local vnc = vnc.VNC:new( host, port ) local v = vnc.VNC:new( host, port )
local status, data local status, data
local result = stdnse.output_table() local result = stdnse.output_table()
status, data = vnc:connect() status, data = v:connect()
if ( not(status) ) then return fail(data) end if ( not(status) ) then return fail(data) end
status, data = vnc:handshake() status, data = v:handshake()
if ( not(status) ) then return fail(data) end if ( not(status) ) then return fail(data) end
status, data = vnc:getSecTypesAsTable() data = v:getSecTypesAsTable()
if ( not(status) ) then return fail(data) end
result["Protocol version"] = vnc:getProtocolVersion() result["Protocol version"] = v:getProtocolVersion()
if ( data and #data ~= 0 ) then if ( data and #data ~= 0 ) then
result["Security types"] = data result["Security types"] = data
end 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" result["WARNING"] = "Server does not require authentication"
end end