diff --git a/.github/workflows/ldap_acceptance.yml b/.github/workflows/ldap_acceptance.yml index 851249f4e2e47..a60944c6a6905 100644 --- a/.github/workflows/ldap_acceptance.yml +++ b/.github/workflows/ldap_acceptance.yml @@ -33,6 +33,8 @@ on: - 'metsploit-framework.gemspec' - 'Gemfile.lock' - '**/**ldap**' + - 'lib/metasploit/framework/tcp/**' + - 'lib/metasploit/framework/login_scanner/**' - 'spec/acceptance/**' - 'spec/support/acceptance/**' - 'spec/acceptance_spec_helper.rb' diff --git a/.github/workflows/postgres_acceptance.yml b/.github/workflows/postgres_acceptance.yml index c440949de10a0..d93f5d3f04490 100644 --- a/.github/workflows/postgres_acceptance.yml +++ b/.github/workflows/postgres_acceptance.yml @@ -33,6 +33,8 @@ on: - 'metsploit-framework.gemspec' - 'Gemfile.lock' - '**/**postgres**' + - 'lib/metasploit/framework/tcp/**' + - 'lib/metasploit/framework/login_scanner/**' - 'spec/acceptance/**' - 'spec/support/acceptance/**' - 'spec/acceptance_spec_helper.rb' diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb index 6d0e5e9c8576e..5e42052d6ccf3 100644 --- a/lib/metasploit/framework/login_scanner/base.rb +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -45,6 +45,9 @@ module Base # @!attribute bruteforce_speed # @return [Integer] The desired speed, with 5 being 'fast' and 0 being 'slow.' attr_accessor :bruteforce_speed + # @!attribute sslkeylogfile + # @return [String] The SSL key log file path + attr_accessor :sslkeylogfile validates :connection_timeout, presence: true, diff --git a/lib/metasploit/framework/login_scanner/mssql.rb b/lib/metasploit/framework/login_scanner/mssql.rb index e56699fa60683..69fc0f1638904 100644 --- a/lib/metasploit/framework/login_scanner/mssql.rb +++ b/lib/metasploit/framework/login_scanner/mssql.rb @@ -77,7 +77,7 @@ def attempt_login(credential) } begin - client = Rex::Proto::MSSQL::Client.new(framework_module, framework, host, port, proxies) + client = Rex::Proto::MSSQL::Client.new(framework_module, framework, host, port, proxies, sslkeylogfile: sslkeylogfile) if client.mssql_login(credential.public, credential.private, '', credential.realm) result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL if use_client_as_proof diff --git a/lib/metasploit/framework/mssql/tdssslproxy.rb b/lib/metasploit/framework/mssql/tdssslproxy.rb index 1e020c31bba8e..87628ec85f401 100644 --- a/lib/metasploit/framework/mssql/tdssslproxy.rb +++ b/lib/metasploit/framework/mssql/tdssslproxy.rb @@ -38,8 +38,9 @@ class TDSSSLProxy TYPE_PRE_LOGIN_MESSAGE = 18 STATUS_END_OF_MESSAGE = 0x01 - def initialize(sock) + def initialize(sock, sslkeylogfile: nil) @tdssock = sock + @sslkeylogfile = sslkeylogfile @s1, @s2 = Rex::Socket.tcp_socket_pair end @@ -48,10 +49,27 @@ def cleanup @t1.join end + def write_to_keylog_file(ctx, sslkeylogfile) + # writing to the sslkeylogfile is required, it adds support for network capture decryption which is useful to + # decrypt TLS traffic in wireshark + if sslkeylogfile + unless ctx.respond_to?(:keylog_cb) + raise 'Unable to create sslkeylogfile - Ruby 3.2 or above required for this functionality' + end + + ctx.keylog_cb = proc do |_sock, line| + File.open(sslkeylogfile, 'ab') do |file| + file.write("#{line}\n") + end + end + end + end + def setup_ssl @running = true @t1 = Thread.start { ssl_setup_thread } ctx = OpenSSL::SSL::SSLContext.new(:SSLv23) + write_to_keylog_file(ctx, @sslkeylogfile) ctx.ciphers = "ALL:!ADH:!EXPORT:!SSLv2:!SSLv3:+HIGH:+MEDIUM" @ssl_socket = OpenSSL::SSL::SSLSocket.new(@s1, ctx) @ssl_socket.connect diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb index aa12ed95faa09..ae03c04bb53a0 100644 --- a/lib/metasploit/framework/tcp/client.rb +++ b/lib/metasploit/framework/tcp/client.rb @@ -89,6 +89,7 @@ def connect(global = true, opts={}) 'SSL' => dossl, 'SSLVersion' => opts['SSLVersion'] || ssl_version, 'SSLVerifyMode' => opts['SSLVerifyMode'] || ssl_verify_mode, + 'SSLKeyLogFile' => opts['SSLKeyLogFile'] || sslkeylogfile, 'SSLCipher' => opts['SSLCipher'] || ssl_cipher, 'Proxies' => proxies, 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i, diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index 9008aa18ad51f..cc0eb8aae5832 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -38,6 +38,7 @@ def initialize(info = {}) OptBool.new('REMOVE_USERPASS_FILE', [ true, "Automatically delete the USERPASS_FILE on module completion", false]), OptBool.new('PASSWORD_SPRAY', [true, "Reverse the credential pairing order. For each password, attempt every possible user.", false]), OptInt.new('TRANSITION_DELAY', [false, "Amount of time (in minutes) to delay before transitioning to the next user in the array (or password when PASSWORD_SPRAY=true)", 0]), + OptString.new('SSLKeyLogFile', [ false, 'The SSL key log file', ENV['SSLKeyLogFile']]), OptInt.new('MaxGuessesPerService', [ false, "Maximum number of credentials to try per service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@guesses_per_service OptInt.new('MaxMinutesPerService', [ false, "Maximum time in minutes to bruteforce the service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@brute_start_time OptInt.new('MaxGuessesPerUser', [ false, %q{ diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index 8ae3472ac20fb..d5ee0822d1c05 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -25,6 +25,9 @@ class Client attr_accessor :ssl_version attr_accessor :ssl_verify_mode attr_accessor :ssl_cipher + # @!attribute sslkeylogfile + # @return [String] The SSL key log file path + attr_accessor :sslkeylogfile attr_accessor :proxies attr_accessor :connection_timeout attr_accessor :send_lm @@ -50,7 +53,7 @@ class Client # @return [String] The database name this client is currently connected to. attr_accessor :current_database - def initialize(framework_module, framework, rhost, rport = 1433, proxies = nil) + def initialize(framework_module, framework, rhost, rport = 1433, proxies = nil, sslkeylogfile: nil) @framework_module = framework_module @framework = framework @connection_timeout = framework_module.datastore['ConnectTimeout'] || 30 @@ -68,6 +71,7 @@ def initialize(framework_module, framework, rhost, rport = 1433, proxies = nil) @rhost = rhost @rport = rport @proxies = proxies + @sslkeylogfile = sslkeylogfile @current_database = '' end @@ -336,7 +340,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification if tdsencryption == true - proxy = TDSSSLProxy.new(sock) + proxy = TDSSSLProxy.new(sock, sslkeylogfile: sslkeylogfile) proxy.setup_ssl resp = proxy.send_recv(pkt) else @@ -454,7 +458,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt if self.tdsencryption == true - proxy = TDSSSLProxy.new(sock) + proxy = TDSSSLProxy.new(sock, sslkeylogfile: sslkeylogfile) proxy.setup_ssl resp = mssql_ssl_send_recv(pkt, proxy) proxy.cleanup