@@ -88,7 +88,7 @@ def session_setup(result, scanner, fingerprint, cred_core_private_id)
8888 'PASS_FILE' => nil ,
8989 'USERNAME' => result . credential . public ,
9090 'CRED_CORE_PRIVATE_ID' => cred_core_private_id ,
91- 'SSH_KEYFILE_B64' => [ result . credential . private ] . pack ( "m*" ) . gsub ( "\n " , "" ) ,
91+ 'SSH_KEYFILE_B64' => [ result . credential . private ] . pack ( 'm*' ) . gsub ( "\n " , '' ) ,
9292 'KEY_PATH' => nil
9393 }
9494
@@ -113,12 +113,12 @@ def session_setup(result, scanner, fingerprint, cred_core_private_id)
113113 def run_host ( ip )
114114 print_status ( "#{ ip } :#{ rport } SSH - Testing Cleartext Keys" )
115115
116- if datastore [ " USER_FILE" ] . blank? && datastore [ " USERNAME" ] . blank?
117- validation_reason = " At least one of USER_FILE or USERNAME must be given"
116+ if datastore [ ' USER_FILE' ] . blank? && datastore [ ' USERNAME' ] . blank?
117+ validation_reason = ' At least one of USER_FILE or USERNAME must be given'
118118 raise Msf ::OptionValidateError . new (
119119 {
120- " USER_FILE" => validation_reason ,
121- " USERNAME" => validation_reason
120+ ' USER_FILE' => validation_reason ,
121+ ' USERNAME' => validation_reason
122122 }
123123 )
124124 end
@@ -132,7 +132,7 @@ def run_host(ip)
132132 )
133133
134134 unless keys . valid?
135- print_error ( " Files that failed to be read:" )
135+ print_error ( ' Files that failed to be read:' )
136136 keys . error_list . each do |err |
137137 print_line ( "\t - #{ err } " )
138138 end
@@ -150,7 +150,7 @@ def run_host(ip)
150150 key_sources . append ( 'PRIVATE_KEY' )
151151 end
152152
153- print_brute : level => :vstatus , :ip => ip , : msg => "Testing #{ key_count } #{ 'key' . pluralize ( key_count ) } from #{ key_sources . join ( ' and ' ) } "
153+ print_brute level : :vstatus , ip : ip , msg : "Testing #{ key_count } #{ 'key' . pluralize ( key_count ) } from #{ key_sources . join ( ' and ' ) } "
154154 scanner = Metasploit ::Framework ::LoginScanner ::SSH . new (
155155 configure_login_scanner (
156156 host : ip ,
@@ -176,36 +176,40 @@ def run_host(ip)
176176 )
177177 case result . status
178178 when Metasploit ::Model ::Login ::Status ::SUCCESSFUL
179- print_brute :level => :good , :ip => ip , :msg => "Success: '#{ result . credential } ' '#{ result . proof . to_s . gsub ( /[\r \n \e \b \a ]/ , ' ' ) } '"
180- credential_core = create_credential ( credential_data )
181- credential_data [ :core ] = credential_core
182- create_credential_login ( credential_data )
183- tmp_key = result . credential . private
184- ssh_key = SSHKey . new tmp_key
179+ print_brute level : :good , ip : ip , msg : "Success: '#{ result . credential } ' '#{ result . proof . to_s . gsub ( /[\r \n \e \b \a ]/ , ' ' ) } '"
180+ ssh_key = Net ::SSH ::KeyFactory . load_data_private_key ( credential_data [ :private_data ] , datastore [ 'key_pass' ] , false )
181+
182+ begin
183+ credential_core = create_credential ( credential_data )
184+ credential_data [ :core ] = credential_core
185+ create_credential_login ( credential_data )
186+ rescue ::StandardError => e
187+ print_brute level : :info , ip : ip , msg : "Failed to create credential: #{ e . class } #{ e } "
188+ print_brute level : :warn , ip : ip , msg : 'We do not currently support storing password protected SSH keys: https://github.com/rapid7/metasploit-framework/issues/20598'
189+ credential_core = nil
190+ end
191+
185192 if datastore [ 'CreateSession' ]
186- if credential_core . is_a? Metasploit ::Credential ::Core
187- session_setup ( result , scanner , ssh_key . fingerprint , credential_core . private_id )
188- else
189- session_setup ( result , scanner , ssh_key . fingerprint , nil )
190- end
193+ cred_id = credential_core . is_a? ( Metasploit ::Credential ::Core ) ? credential_core . private_id : nil
194+ session_setup ( result , scanner , ssh_key . public_key . fingerprint , cred_id )
191195 end
192196 if datastore [ 'GatherProof' ] && scanner . get_platform ( result . proof ) == 'unknown'
193- msg = " While a session may have opened, it may be bugged. If you experience issues with it, re-run this module with"
197+ msg = ' While a session may have opened, it may be bugged. If you experience issues with it, re-run this module with'
194198 msg << " 'set gatherproof false'. Also consider submitting an issue at github.com/rapid7/metasploit-framework with"
195- msg << " device details so it can be handled in the future."
196- print_brute : level => :error , :ip => ip , : msg => msg
199+ msg << ' device details so it can be handled in the future.'
200+ print_brute level : :error , ip : ip , msg : msg
197201 end
198202 :next_user
199203 when Metasploit ::Model ::Login ::Status ::UNABLE_TO_CONNECT
200204 if datastore [ 'VERBOSE' ]
201- print_brute : level => :verror , :ip => ip , : msg => "Could not connect: #{ result . proof } "
205+ print_brute level : :verror , ip : ip , msg : "Could not connect: #{ result . proof } "
202206 end
203207 scanner . ssh_socket . close if scanner . ssh_socket && !scanner . ssh_socket . closed?
204208 invalidate_login ( credential_data )
205209 :abort
206210 when Metasploit ::Model ::Login ::Status ::INCORRECT
207211 if datastore [ 'VERBOSE' ]
208- print_brute : level => :verror , :ip => ip , : msg => "Failed: '#{ result . credential } '"
212+ print_brute level : :verror , ip : ip , msg : "Failed: '#{ result . credential } '"
209213 end
210214 invalidate_login ( credential_data )
211215 scanner . ssh_socket . close if scanner . ssh_socket && !scanner . ssh_socket . closed?
@@ -224,7 +228,7 @@ class KeyCollection < Metasploit::Framework::CredentialCollection
224228
225229 # Override CredentialCollection#has_privates?
226230 def has_privates?
227- ! @key_data . empty ?
231+ @key_data . present ?
228232 end
229233
230234 def realm
@@ -235,49 +239,62 @@ def valid?
235239 @error_list = [ ]
236240 @key_data = Set . new
237241
238- unless @private_key . present? || @key_path . present?
239- raise RuntimeError , "No key path or key provided"
242+ if @private_key . present?
243+ results = validate_private_key ( @private_key )
244+ elsif @key_path . present?
245+ results = validate_key_path ( @key_path )
246+ else
247+ @error_list << 'No key path or key provided'
248+ raise RuntimeError , 'No key path or key provided'
240249 end
241250
242- if @key_path . present?
243- if File . directory? ( @key_path )
244- @key_files ||= Dir . entries ( @key_path ) . reject { |f | f =~ /^\x2e |\x2e pub$/ }
245- @key_files . each do |f |
246- begin
247- data = read_key ( File . join ( @key_path , f ) )
248- @key_data << data if valid_key? ( data )
249- rescue StandardError => e
250- @error_list << "#{ File . join ( @key_path , f ) } : #{ e } "
251- end
252- end
253- elsif File . file? ( @key_path )
254- begin
255- data = read_key ( @key_path )
256- @key_data << data if valid_key? ( data )
257- rescue StandardError => e
258- @error_list << "#{ @key_path } could not be read, #{ e } "
259- end
260- else
261- raise RuntimeError , "Invalid key path"
262- end
251+ if results [ :key_data ] . present?
252+ @key_data . merge ( results [ :key_data ] )
253+ else
254+ @error_list . concat ( results [ :error_list ] ) if results [ :error_list ] . present?
263255 end
264256
265- if @private_key . present?
266- data = Net ::SSH ::KeyFactory . load_data_private_key ( @private_key , @password , false ) . to_s
267- if valid_key? ( data )
268- @key_data << data
269- else
270- raise RuntimeError , "Invalid private key"
257+ @key_data . present?
258+ end
259+
260+ def validate_private_key ( private_key )
261+ key_data = Set . new
262+ error_list = [ ]
263+ begin
264+ if Net ::SSH ::KeyFactory . load_data_private_key ( private_key , @password , false ) . present?
265+ key_data << private_key
271266 end
267+ rescue StandardError => e
268+ error_list << "Error validating private key: #{ e } "
272269 end
273-
274- !@key_data . empty?
270+ { key_data : key_data , error_list : error_list }
275271 end
276272
277- def valid_key? ( key_data )
278- !!( key_data . match ( /BEGIN [RECD]SA PRIVATE KEY/ ) && !key_data . match ( /Proc-Type:.*ENCRYPTED/ ) )
273+ def validate_key_path ( key_path )
274+ key_data = Set . new
275+ error_list = [ ]
276+
277+ if File . file? ( key_path )
278+ key_files = [ key_path ]
279+ elsif File . directory? ( key_path )
280+ key_files = Dir . entries ( key_path ) . reject { |f | f =~ /^\x2e |\x2e pub$/ } . map { |f | File . join ( key_path , f ) }
281+ else
282+ return { key_data : nil , error : "#{ key_path } Invalid key path" }
283+ end
284+
285+ key_files . each do |f |
286+ begin
287+ if read_key ( f ) . present?
288+ key_data << File . read ( f )
289+ end
290+ rescue StandardError => e
291+ error_list << "#{ f } : #{ e } "
292+ end
293+ end
294+ { key_data : key_data , error_list : error_list }
279295 end
280296
297+
281298 def each
282299 prepended_creds . each { |c | yield c }
283300
@@ -307,7 +324,7 @@ def each_key
307324
308325 def read_key ( file_path )
309326 @cache ||= { }
310- @cache [ file_path ] ||= Net ::SSH ::KeyFactory . load_data_private_key ( File . read ( file_path ) , password , false , key_path ) . to_s
327+ @cache [ file_path ] ||= Net ::SSH ::KeyFactory . load_private_key ( file_path , password , false )
311328 @cache [ file_path ]
312329 end
313330 end
0 commit comments