diff --git a/CHANGELOG b/CHANGELOG index 5444bc8dc..d7d499bea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added one script (vnc-brute) that performs password guessing against + VNC using the new brute library and another (vnc-info) that lists supported + security mechanisms. [Patrik] + o [NSE] Added a new brute library that provides a basic framework and logic for password guessing scripts. [Patrik] diff --git a/nselib/vnc.lua b/nselib/vnc.lua new file mode 100644 index 000000000..8f0a33cd9 --- /dev/null +++ b/nselib/vnc.lua @@ -0,0 +1,387 @@ +-- The VNC library provides some basic functionality needed in order to +-- communicate with VNC servers, and derivates such as Tight- or Ultra- +-- VNC. +-- +-- Summary +-- ------- +-- The library currently supports the VNC Authentication security type only. +-- This security type is supported by default in VNC, TightVNC and +-- "Remote Desktop Sharing" in eg. Ubuntu. For servers that do not support +-- this authentication security type the login method will fail. +-- +-- Overview +-- -------- +-- The library contains the following classes: +-- +-- o VNC +-- - This class contains the core functions needed to communicate with VNC +-- +-- o VNCSocket +-- - This is a copy of the DB2Socket class which provides fundamental buffering +-- +-- + +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- @author "Patrik Karlsson " + +-- Version 0.1 + +-- Created 07/07/2010 - v0.1 - created by Patrik Karlsson + +module(... or "vnc", package.seeall) +require "bin" + +local HAVE_SSL = false + +if pcall(require,'openssl') then + HAVE_SSL = true +end + +VNC = { + + -- We currently support version 3.8 of the protocol only + versions = { + ["RFB 003.003\n"] = "3.3", + ["RFB 003.007\n"] = "3.7", + ["RFB 003.008\n"] = "3.8", + + -- Mac Screen Sharing, could probably be used to fingerprint OS + ["RFB 003.889\n"] = "3.889", + }, + + sectypes = { + INVALID = 0, + NONE = 1, + VNCAUTH = 2, + RA2 = 5, + RA2NE = 6, + TIGHT = 16, + ULTRA = 17, + TNS = 18, + VENCRYPT = 19, + GTK_VNC_SASL = 20, + MD5 = 21, + COLIN_DEAN_XVP = 22, + MAC_OSX_SECTYPE_30 = 30, + MAC_OSX_SECTYPE_35 = 35, + }, + + -- Security types are fetched from the rfbproto.pdf + sectypes_str = { + [0] = "Invalid security type", + [1] = "None", + [2] = "VNC Authentication", + [5] = "RA2", + [6] = "RA2ne", + [16]= "Tight", + [17]= "Ultra", + [18]= "TLS", + [19]= "VeNCrypt", + [20]= "GTK-VNC SASL", + [21]= "MD5 hash authentication", + [22]= "Colin Dean xvp", + + -- Mac OS X screen sharing uses 30 and 35 + [30]= "Mac OS X security type (30)", + [35]= "Mac OS X security type (35)", + }, + + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.vncsocket = VNCSocket:new() + o.cli_version = nmap.registry.args['vnc-brute.version'] or "RFB 003.889\n" + return o + end, + + --- Connects the VNC socket + connect = function(self) + local data, status, msg + + if ( not(HAVE_SSL) ) then + return false, "The VNC module requires OpenSSL support" + end + + status, msg = self.vncsocket:connect(self.host, self.port, "tcp") + return status, msg + end, + + --- Disconnects the VNC socket + disconnect = function(self) + self.vncsocket:close() + end, + + --- Performs the VNC handshake and determines + -- o The RFB Protocol to use + -- o The supported authentication security types + -- + -- @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.vncsocket:recv( 12 ) + local vncsec = {} + local tmp + + if ( not(status) ) then + return status, "ERROR: VNC:handshake failed to receive protocol version" + end + + self.protover = VNC.versions[data] + if ( not(self.protover) ) then + stdnse.print_debug("ERROR: VNC:handshake unsupported version (%s)", data:sub(1,11)) + return false, ("Unsupported version (%s)"):format(data:sub(1,11)) + end + + status = self.vncsocket:send( self.cli_version ) + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to send client version") + return false, "ERROR: VNC:handshake failed" + end + + if ( self.protover == "3.3" ) then + vncsec.count = 1 + vncsec.types = {} + + status, tmp = self.vncsocket:recv(4) + if( not(status) ) then + return false, "VNC:handshake failed to receive security data" + end + + vncsec.types[1] = select(2, bin.unpack("I", tmp) ) + self.vncsec = vncsec + + -- do we have an invalid security type, if so we need to handle an + -- error condition + if ( vncsec.types[1] == 0 ) then + local len, err + + status, tmp = self.vncsocket:recv(4) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + len = select(2, bin.unpack(">I", tmp) ) + status, err = self.vncsocket:recv(len) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + + return false, err + end + else + status, tmp = self.vncsocket:recv(1) + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") + return false, "ERROR: VNC:handshake failed to receive security data" + end + + vncsec.count = select(2, bin.unpack("C", tmp)) + if ( vncsec.count == 0 ) then + local len, err + status, tmp = self.vncsocket:recv(4) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + len = select(2, bin.unpack(">I", tmp) ) + status, err = self.vncsocket:recv(len) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + + return false, err + end + + status, tmp = self.vncsocket:recv(vncsec.count) + + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") + return false, "ERROR: VNC:handshake failed to receive security data" + end + + vncsec.types = {} + for i=1, vncsec.count do + table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) ) + end + + self.vncsec = vncsec + end + + return true + end, + + --- Creates the password bit-flip needed before DES encryption + -- + -- @param password string containing the password to process + -- @retun password string containing the processed password + createVNCDESKey = function( self, password ) + local _, bitstr + local newpass = "" + + if ( #password < 8 ) then + for i=1, (8 - #password) do + password = password .. string.char(0x00) + end + end + + for i=1, 8 do + _, bitstr = bin.unpack("B", password, i) + newpass = newpass .. bin.pack("B", bitstr:reverse()) + end + + return newpass + end, + + --- Attempts to login to the VNC service + -- Currently the only supported auth sectype is VNC Authentication + -- + -- @param username string, could be anything when VNCAuth is used + -- @param password string containing the password to use for authentication + -- @return status true on success, false on failure + -- @return err string containing error message when status is false + login = function( self, username, password ) + local status, result + local chall, resp, key + + if ( not(password) ) then + return false, "No password was supplied" + end + + if ( not( self:supportsSecType( VNC.sectypes.VNCAUTH ) ) ) then + return false, "The server does not support the \"VNC Authentication\" security type." + end + + -- Announce that we support VNC Authentication + status = self.vncsocket:send( bin.pack("C", VNC.sectypes.VNCAUTH) ) + if ( not(status) ) then + return false, "Failed to select authentication type" + end + + status, chall = self.vncsocket:recv( 16 ) + if ( not(status) ) then + return false, "Failed to receive authentication challenge" + end + + key = self:createVNCDESKey(password) + resp = openssl.encrypt("des-ecb", key, nil, chall, false ) + + status = self.vncsocket:send( resp ) + if ( not(status) ) then + return false, "Failed to send authentication response to server" + end + + status, result = self.vncsocket:recv(4) + 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) + end + + return true, "" + end, + + --- Returns all supported security types as a table of strings + -- + -- @return table containing a string entry for each security type + getSecTypesAsStringTable = function( self ) + local tmp = {} + for i=1, self.vncsec.count do + table.insert( tmp, VNC.sectypes_str[self.vncsec.types[i]] or ("Unknown security type (%d)"):format(self.vncsec.types[i]) ) + end + return true, tmp + end, + + --- Checks if the supplied security type is supported or not + -- + -- @param sectype number containing the security type to check for + -- @return status true if supported, false if not supported + supportsSecType = function( self, sectype ) + for i=1, self.vncsec.count do + if ( self.vncsec.types[i] == sectype ) then + return true + end + end + return false + end, + + --- Returns the protocol version reported by the server + -- + -- @param version string containing the version number + getProtocolVersion = function( self ) + return self.protover + end, + +} + +VNCSocket = +{ + retries = 3, + + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + o.Socket = nmap.new_socket() + o.Buffer = nil + return o + end, + + + --- Establishes a connection. + -- + -- @param hostid Hostname or IP address. + -- @param port Port number. + -- @param protocol "tcp", "udp", or + -- @return Status (true or false). + -- @return Error code (if status is false). + connect = function( self, hostid, port, protocol ) + -- VNC servers sometimes take a long time to respond 10seconds seems ok + self.Socket:set_timeout(10000) + return self.Socket:connect( hostid, port, protocol ) + end, + + --- Closes an open connection. + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + close = function( self ) + return self.Socket:close() + end, + + --- Opposed to the socket:receive_bytes function, that returns + -- at least x bytes, this function returns the amount of bytes requested. + -- + -- @param count of bytes to read + -- @return true on success, false on failure + -- @return data containing bytes read from the socket + -- err containing error message if status is false + recv = function( self, count ) + local status, data + + self.Buffer = self.Buffer or "" + + if ( #self.Buffer < count ) then + status, data = self.Socket:receive_bytes( count - #self.Buffer ) + if ( not(status) ) then + return false, data + end + self.Buffer = self.Buffer .. data + end + + data = self.Buffer:sub( 1, count ) + self.Buffer = self.Buffer:sub( count + 1) + + return true, data + end, + + --- Sends data over the socket + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + send = function( self, data ) + return self.Socket:send( data ) + end, +} \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 929795388..bc317897f 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -127,5 +127,7 @@ Entry { filename = "ssl-enum-ciphers.nse", categories = { "discovery", "intrusiv Entry { filename = "sslv2.nse", categories = { "default", "safe", } } Entry { filename = "telnet-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "upnp-info.nse", categories = { "default", "safe", } } +Entry { filename = "vnc-brute.nse", categories = { "auth", "intrusive", } } +Entry { filename = "vnc-info.nse", categories = { "discovery", "safe", } } Entry { filename = "whois.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "x11-access.nse", categories = { "auth", "default", "safe", } } diff --git a/scripts/vnc-brute.nse b/scripts/vnc-brute.nse new file mode 100644 index 000000000..2d520d8bb --- /dev/null +++ b/scripts/vnc-brute.nse @@ -0,0 +1,141 @@ +description = [[ +Performs password guessing against VNC +]] + +--- +-- @usage +-- nmap --script vnc-brute -p 5900 +-- +-- @output +-- PORT STATE SERVICE REASON +-- 5900/tcp open vnc syn-ack +-- | vnc-brute: +-- | Accounts +-- |_ 123456 => Login correct +-- +-- Summary +-- ------- +-- x The Driver class contains the driver implementation used by the brute +-- library +-- +-- + +-- +-- Version 0.1 +-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "auth"} + +require 'shortport' +require 'brute' +require 'vnc' + +portrule = shortport.port_or_service(5901, "vnc", "tcp", "open") + +Driver = +{ + + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, + + connect = function( self ) + local status, data + self.vnc = vnc.VNC:new( self.host.ip, self.port.number ) + status, data = self.vnc:connect() + if ( not(status) ) then + local err = brute.Error:new( "VNC connect failed" ) + -- This might be temporary, set the retry flag + err:setRetry( true ) + return false, err + end + return true + end, + --- Attempts to login to the VNC server + -- + -- @param username string containing the login username + -- @param password string containing the login password + -- @return status, true on success, false on failure + -- @return brute.Error object on failure + -- brute.Account object on success + login = function( self, username, password ) + + local status, data = self.vnc:handshake() + if ( not(status) and data:match("Too many authentication failures") ) then + local err = brute.Error:new( data ) + err:setAbort( true ) + return false, err + elseif ( not(status) ) then + local err = brute.Error:new( "VNC handshake failed" ) + -- This might be temporary, set the retry flag + err:setRetry( true ) + return false, err + end + + status, data = self.vnc:login( nil, password ) + + if ( status ) then + return true, brute.Account:new("", password, "OPEN") + elseif ( not( data:match("Authentication failed") ) ) then + local err = brute.Error:new( data ) + -- This might be temporary, set the retry flag + err:setRetry( true ) + return false, err + end + + return false, brute.Error:new( "Incorrect password" ) + + end, + + disconnect = function( self ) + self.vnc:disconnect() + end, + + check = function( self ) + local vnc = vnc.VNC:new( self.host.ip, self.port.number ) + local status, data + + status, data = vnc:connect() + if ( not(status) ) then + return stdnse.format_output( false, data ) + end + + status, data = vnc:handshake() + if ( not(status) ) then + return stdnse.format_output( false, data ) + end + + if ( vnc:supportsSecType(vnc.sectypes.NONE) ) then + return false, "No authentication required" + end + + status, data = vnc:login( nil, "is_sec_mec_supported?" ) + if ( data:match("The server does not support.*security type") ) then + return stdnse.format_output( false, " \n " .. data ) + end + + return true + end, + +} + + +action = function(host, port) + local status, result + local engine = brute.Engine:new(Driver, host, port ) + + engine.options.firstonly = true + engine.options:setOption( "passonly", true ) + + status, result = engine:start() + + return result +end \ No newline at end of file diff --git a/scripts/vnc-info.nse b/scripts/vnc-info.nse new file mode 100644 index 000000000..75bc5b587 --- /dev/null +++ b/scripts/vnc-info.nse @@ -0,0 +1,57 @@ +description = [[ +Queries a VNC server for the supported security types +]] + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +--- +-- @output +-- PORT STATE SERVICE +-- 5900/tcp open vnc +-- | vnc-info: +-- | Protocol version: 3.889 +-- | Security types: +-- | Mac OS X security type (30) +-- |_ Mac OS X security type (35) +-- + +-- Version 0.2 + +-- Created 07/07/2010 - v0.1 - created by Patrik Karlsson +-- Revised 08/14/2010 - v0.2 - changed so that errors are reported even without debugging + +require 'shortport' +require 'vnc' + +portrule = shortport.port_or_service( {5900, 5901, 5902} , "vnc", "tcp", "open") + +action = function(host, port) + + local vnc = vnc.VNC:new( host.ip, port.number ) + local status, data + local result = {} + + status, data = vnc:connect() + if ( not(status) ) then return " \n ERROR: " .. data end + + status, data = vnc:handshake() + if ( not(status) ) then return " \n ERROR: " .. data end + + status, data = vnc:getSecTypesAsStringTable() + if ( not(status) ) then return " \n ERROR: " .. data end + + table.insert(result, ("Protocol version: %s"):format(vnc:getProtocolVersion()) ) + + if ( data and #data ~= 0 ) then + data.name = "Security types:" + table.insert( result, data ) + end + + if ( vnc:supportsSecType(vnc.sectypes.NONE) ) then + table.insert(result, "WARNING: Server does not require authentication") + end + + return stdnse.format_output(status, result) +end \ No newline at end of file