From 09c58e4097eb32a4a1ab8b2cc2c820a518597cb6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sat, 5 Dec 2015 21:18:29 +0000 Subject: [PATCH] Massive rework of the storage/notes/reporting --- .../scanner/ssh/ssh_identify_pubkeys.rb | 120 +++++++++++++----- 1 file changed, 86 insertions(+), 34 deletions(-) diff --git a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb index 3bc9ed96fb..0cabae22d2 100644 --- a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb +++ b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb @@ -29,13 +29,16 @@ class Metasploit3 < Msf::Auxiliary this module will record authorized public keys and hosts so you can track your process. - Key files may be a single public (unencrypted) key, or several public keys concatenated together as an ASCII text file. Non-key data should be silently ignored. Private keys will only utilize the public key component stored within the key file. }, - 'Author' => ['todb', 'hdm'], + 'Author' => [ + 'todb', + 'hdm', + 'Stuart Morgan ', # Reworked the storage (db, credentials, notes, loot) only + ], 'License' => MSF_LICENSE ) @@ -116,11 +119,14 @@ class Metasploit3 < Msf::Auxiliary keepers = [] keys.each do |key| if key =~ /ssh-(dss|rsa)/ - keepers << key + # A public key has been provided + keepers << { :public => key, :private => "" } next - else # Use the mighty SSHKey library from James Miller to convert them on the fly. + else + # Use the mighty SSHKey library from James Miller to convert them on the fly. + # This is where a PRIVATE key has been provided ssh_version = SSHKey.new(key).ssh_public_key rescue nil - keepers << ssh_version if ssh_version + keepers << { :public => ssh_version, :private => key } if ssh_version next end @@ -130,8 +136,8 @@ class Metasploit3 < Msf::Auxiliary next unless key =~ /\n-----END [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d?\x0a?$/m # Shouldn't have binary. next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty? - # Add more tests to taste. - keepers << key + # Add more tests to test + keepers << { :public => key, :private => "" } end if keepers.empty? print_error "#{ip}:#{rport} SSH - No valid keys found" @@ -142,9 +148,9 @@ class Metasploit3 < Msf::Auxiliary def pull_cleartext_keys(keys) cleartext_keys = [] keys.each do |key| - next unless key - next if key =~ /Proc-Type:.*ENCRYPTED/ - this_key = key.gsub(/\x0d/,"") + next unless key[:public] + next if key[:private] =~ /Proc-Type:.*ENCRYPTED/ + this_key = { :public => key[:public].gsub(/\x0d/,""), :private => key[:private] } next if cleartext_keys.include? this_key cleartext_keys << this_key end @@ -182,26 +188,26 @@ class Metasploit3 < Msf::Auxiliary @alerted_with_msg = true end - cleartext_keys.each_with_index do |key_data,key_idx| - key_info = "" - if key_data =~ /ssh\-(rsa|dss)\s+([^\s]+)\s+(.*)/ + cleartext_keys.each_with_index do |key_data,key_idx| + + key_info = "" + if key_data[:public] =~ /ssh\-(rsa|dss)\s+([^\s]+)\s+(.*)/ key_info = "- #{$3.strip}" end - accepted = [] opt_hash = { :auth_methods => ['publickey'], :msframework => framework, :msfmodule => self, :port => port, - :key_data => key_data, + :key_data => key_data[:public], :disable_agent => true, :record_auth_info => true, :skip_private_keys => true, :config =>false, - :accepted_key_callback => Proc.new {|key| accepted << key }, + :accepted_key_callback => Proc.new {|key| accepted << { :data => key_data, :key => key } }, :proxies => datastore['Proxies'] } @@ -244,35 +250,63 @@ class Metasploit3 < Msf::Auxiliary end accepted.each do |key| - print_brute :level => :good, :msg => "Accepted: '#{user}' with key '#{key[:fingerprint]}' #{key_info}" - do_report(ip, rport, user, key, key_data) + print_brute :level => :good, :msg => "Public key accepted: '#{user}' with key '#{key[:key][:fingerprint]}' #{key_info}" + do_report(ip, rport, user, key, key_data[:public], key_info) end end end - def do_report(ip, port, user, key, key_data) + def do_report(ip, port, user, key, key_data, key_info) return unless framework.db.active - keyfile_path = store_keyfile(ip,user,key[:fingerprint],key_data) - cred_hash = { - :host => ip, - :port => rport, - :sname => 'ssh', - :user => user, - :pass => keyfile_path, - :source_type => "user_supplied", - :type => 'ssh_pubkey', - :proof => "KEY=#{key[:fingerprint]}", - :duplicate_ok => true, - :active => true - } - this_cred = report_auth_info(cred_hash) + + # Store a note relating to the public key test + note_information = { + user: user, + public_key: key_data, + private_key: (key[:data][:private]) ? 'Yes' : 'No', + info: key_info + } + report_note(host: ip, port: port, type: "ssh.publickey", data: note_information, update: :unique_data) + + + if key[:data][:private] + # Store these keys in loot + public_keyfile_path = store_public_keyfile(ip,user,key[:fingerprint],key_data) + private_keyfile_path = store_private_keyfile(ip,user,key[:fingerprint],key[:data][:private]) + + # Use the proper credential method to store credentials that we have + service_data = { + address: ip, + port: port, + service_name: 'ssh', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: key[:data][:private], + private_type: :ssh_key, + username: key[:key][:user], + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL, + proof: private_keyfile_path + }.merge(service_data) + create_credential_login(login_data) + + end end def existing_loot(ltype, key_id) framework.db.loots(myworkspace).where(ltype: ltype).select {|l| l.info == key_id}.first end - def store_keyfile(ip,user,key_id,key_data) + def store_public_keyfile(ip,user,key_id,key_data) safe_username = user.gsub(/[^A-Za-z0-9]/,"_") ktype = key_data.match(/ssh-(rsa|dss)/)[1] rescue nil return unless ktype @@ -291,6 +325,24 @@ class Metasploit3 < Msf::Auxiliary return keyfile_path end + def store_private_keyfile(ip,user,key_id,key_data) + safe_username = user.gsub(/[^A-Za-z0-9]/,"_") + ktype = key_data.match(/-----BEGIN ([RD]SA) (?:PRIVATE|PUBLIC) KEY-----/)[1].downcase rescue nil + return unless ktype + ltype = "host.unix.ssh.#{user}_#{ktype}_private" + keyfile = existing_loot(ltype, key_id) + return keyfile.path if keyfile + keyfile_path = store_loot( + ltype, + "application/octet-stream", # Text, but always want to mime-type attach it + ip, + (key_data + "\n"), + "#{safe_username}_#{ktype}.private", + key_id + ) + return keyfile_path + end + def run_host(ip) # Since SSH collects keys and tries them all on one authentication session, it doesn't # make sense to iteratively go through all the keys individually. So, ignore the pass variable,