@@ -29,13 +29,16 @@ def initialize
29
29
this module will record authorized public keys and hosts so you can
30
30
track your process.
31
31
32
-
33
32
Key files may be a single public (unencrypted) key, or several public
34
33
keys concatenated together as an ASCII text file. Non-key data should be
35
34
silently ignored. Private keys will only utilize the public key component
36
35
stored within the key file.
37
36
} ,
38
- 'Author' => [ 'todb' , 'hdm' ] ,
37
+ 'Author' => [
38
+ 'todb' ,
39
+ 'hdm' ,
40
+ 'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>' , # Reworked the storage (db, credentials, notes, loot) only
41
+ ] ,
39
42
'License' => MSF_LICENSE
40
43
)
41
44
@@ -116,11 +119,14 @@ def validate_keys(keys)
116
119
keepers = [ ]
117
120
keys . each do |key |
118
121
if key =~ /ssh-(dss|rsa)/
119
- keepers << key
122
+ # A public key has been provided
123
+ keepers << { :public => key , :private => "" }
120
124
next
121
- else # Use the mighty SSHKey library from James Miller to convert them on the fly.
125
+ else
126
+ # Use the mighty SSHKey library from James Miller to convert them on the fly.
127
+ # This is where a PRIVATE key has been provided
122
128
ssh_version = SSHKey . new ( key ) . ssh_public_key rescue nil
123
- keepers << ssh_version if ssh_version
129
+ keepers << { :public => ssh_version , :private => key } if ssh_version
124
130
next
125
131
end
126
132
@@ -130,8 +136,8 @@ def validate_keys(keys)
130
136
next unless key =~ /\n -----END [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d ?\x0a ?$/m
131
137
# Shouldn't have binary.
132
138
next unless key . scan ( /[\x00 -\x08 \x0b \x0c \x0e -\x1f \x80 -\xff ]/ ) . empty?
133
- # Add more tests to taste.
134
- keepers << key
139
+ # Add more tests to test
140
+ keepers << { :public => key , :private => "" }
135
141
end
136
142
if keepers . empty?
137
143
print_error "#{ ip } :#{ rport } SSH - No valid keys found"
@@ -142,9 +148,9 @@ def validate_keys(keys)
142
148
def pull_cleartext_keys ( keys )
143
149
cleartext_keys = [ ]
144
150
keys . each do |key |
145
- next unless key
146
- next if key =~ /Proc-Type:.*ENCRYPTED/
147
- this_key = key . gsub ( /\x0d / , "" )
151
+ next unless key [ :public ]
152
+ next if key [ :private ] =~ /Proc-Type:.*ENCRYPTED/
153
+ this_key = { :public => key [ :public ] . gsub ( /\x0d / , "" ) , :private => key [ :private ] }
148
154
next if cleartext_keys . include? this_key
149
155
cleartext_keys << this_key
150
156
end
@@ -182,26 +188,26 @@ def do_login(ip, port, user)
182
188
@alerted_with_msg = true
183
189
end
184
190
191
+
185
192
cleartext_keys . each_with_index do |key_data , key_idx |
186
- key_info = ""
187
193
188
- if key_data =~ /ssh\- (rsa|dss)\s +([^\s ]+)\s +(.*)/
194
+ key_info = ""
195
+ if key_data [ :public ] =~ /ssh\- (rsa|dss)\s +([^\s ]+)\s +(.*)/
189
196
key_info = "- #{ $3. strip } "
190
197
end
191
198
192
-
193
199
accepted = [ ]
194
200
opt_hash = {
195
201
:auth_methods => [ 'publickey' ] ,
196
202
:msframework => framework ,
197
203
:msfmodule => self ,
198
204
:port => port ,
199
- :key_data => key_data ,
205
+ :key_data => key_data [ :public ] ,
200
206
:disable_agent => true ,
201
207
:record_auth_info => true ,
202
208
:skip_private_keys => true ,
203
209
:config => false ,
204
- :accepted_key_callback => Proc . new { |key | accepted << key } ,
210
+ :accepted_key_callback => Proc . new { |key | accepted << { :data => key_data , : key => key , :info => key_info } } ,
205
211
:proxies => datastore [ 'Proxies' ]
206
212
}
207
213
@@ -231,7 +237,7 @@ def do_login(ip, port, user)
231
237
rescue Net ::SSH ::Disconnect , ::EOFError
232
238
return :connection_disconnect
233
239
rescue Net ::SSH ::AuthenticationFailed
234
- rescue Net ::SSH ::Exception => e
240
+ rescue Net ::SSH ::Exception
235
241
return [ :fail , nil ] # For whatever reason.
236
242
end
237
243
@@ -244,35 +250,64 @@ def do_login(ip, port, user)
244
250
end
245
251
246
252
accepted . each do |key |
247
- print_brute :level => :good , :msg => "Accepted: '#{ user } ' with key '#{ key [ :fingerprint ] } ' #{ key_info } "
248
- do_report ( ip , rport , user , key , key_data )
253
+ private_key_present = ( key [ :data ] [ :private ] !="" ) ? 'Yes' : 'No'
254
+ print_brute :level => :good , :msg => "Public key accepted: '#{ user } ' with key '#{ key [ :key ] [ :fingerprint ] } ' (Private Key: #{ private_key_present } ) #{ key_info } "
255
+ do_report ( ip , rport , user , key )
249
256
end
250
257
end
251
258
end
252
259
253
- def do_report ( ip , port , user , key , key_data )
260
+ def do_report ( ip , port , user , key )
254
261
return unless framework . db . active
255
- keyfile_path = store_keyfile ( ip , user , key [ :fingerprint ] , key_data )
256
- cred_hash = {
257
- :host => ip ,
258
- :port => rport ,
259
- :sname => 'ssh' ,
260
- :user => user ,
261
- :pass => keyfile_path ,
262
- :source_type => "user_supplied" ,
263
- :type => 'ssh_pubkey' ,
264
- :proof => "KEY=#{ key [ :fingerprint ] } " ,
265
- :duplicate_ok => true ,
266
- :active => true
262
+
263
+ store_public_keyfile ( ip , user , key [ :fingerprint ] , key [ :data ] [ :public ] )
264
+ private_key_present = ( key [ :data ] [ :private ] !="" ) ? 'Yes' : 'No'
265
+
266
+ # Store a note relating to the public key test
267
+ note_information = {
268
+ user : user ,
269
+ public_key : key [ :data ] [ :public ] ,
270
+ private_key : private_key_present ,
271
+ info : key [ :info ]
267
272
}
268
- this_cred = report_auth_info ( cred_hash )
273
+ report_note ( host : ip , port : port , type : "ssh.publickey.accepted" , data : note_information , update : :unique_data )
274
+
275
+ if key [ :data ] [ :private ] != ""
276
+ # Store these keys in loot
277
+ private_keyfile_path = store_private_keyfile ( ip , user , key [ :fingerprint ] , key [ :data ] [ :private ] )
278
+
279
+ # Use the proper credential method to store credentials that we have
280
+ service_data = {
281
+ address : ip ,
282
+ port : port ,
283
+ service_name : 'ssh' ,
284
+ protocol : 'tcp' ,
285
+ workspace_id : myworkspace_id
286
+ }
287
+
288
+ credential_data = {
289
+ module_fullname : self . fullname ,
290
+ origin_type : :service ,
291
+ private_data : key [ :data ] [ :private ] ,
292
+ private_type : :ssh_key ,
293
+ username : key [ :key ] [ :user ] ,
294
+ } . merge ( service_data )
295
+
296
+ login_data = {
297
+ core : create_credential ( credential_data ) ,
298
+ last_attempted_at : DateTime . now ,
299
+ status : Metasploit ::Model ::Login ::Status ::SUCCESSFUL ,
300
+ proof : private_keyfile_path
301
+ } . merge ( service_data )
302
+ create_credential_login ( login_data )
303
+ end
269
304
end
270
305
271
306
def existing_loot ( ltype , key_id )
272
307
framework . db . loots ( myworkspace ) . where ( ltype : ltype ) . select { |l | l . info == key_id } . first
273
308
end
274
309
275
- def store_keyfile ( ip , user , key_id , key_data )
310
+ def store_public_keyfile ( ip , user , key_id , key_data )
276
311
safe_username = user . gsub ( /[^A-Za-z0-9]/ , "_" )
277
312
ktype = key_data . match ( /ssh-(rsa|dss)/ ) [ 1 ] rescue nil
278
313
return unless ktype
@@ -291,12 +326,30 @@ def store_keyfile(ip,user,key_id,key_data)
291
326
return keyfile_path
292
327
end
293
328
329
+ def store_private_keyfile ( ip , user , key_id , key_data )
330
+ safe_username = user . gsub ( /[^A-Za-z0-9]/ , "_" )
331
+ ktype = key_data . match ( /-----BEGIN ([RD]SA) (?:PRIVATE|PUBLIC) KEY-----/ ) [ 1 ] . downcase rescue nil
332
+ return unless ktype
333
+ ltype = "host.unix.ssh.#{ user } _#{ ktype } _private"
334
+ keyfile = existing_loot ( ltype , key_id )
335
+ return keyfile . path if keyfile
336
+ keyfile_path = store_loot (
337
+ ltype ,
338
+ "application/octet-stream" , # Text, but always want to mime-type attach it
339
+ ip ,
340
+ ( key_data + "\n " ) ,
341
+ "#{ safe_username } _#{ ktype } .private" ,
342
+ key_id
343
+ )
344
+ return keyfile_path
345
+ end
346
+
294
347
def run_host ( ip )
295
348
# Since SSH collects keys and tries them all on one authentication session, it doesn't
296
349
# make sense to iteratively go through all the keys individually. So, ignore the pass variable,
297
350
# and try all available keys for all users.
298
351
each_user_pass do |user , pass |
299
- ret , proof = do_login ( ip , rport , user )
352
+ ret , _ = do_login ( ip , rport , user )
300
353
case ret
301
354
when :connection_error
302
355
vprint_error "#{ ip } :#{ rport } SSH - Could not connect"
0 commit comments