Massive rework of the storage/notes/reporting

bug/bundler_fix
Stuart Morgan 2015-12-05 21:18:29 +00:00
parent 66ba204c11
commit 09c58e4097
1 changed files with 86 additions and 34 deletions

View File

@ -29,13 +29,16 @@ class Metasploit3 < Msf::Auxiliary
this module will record authorized public keys and hosts so you can this module will record authorized public keys and hosts so you can
track your process. track your process.
Key files may be a single public (unencrypted) key, or several public 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 keys concatenated together as an ASCII text file. Non-key data should be
silently ignored. Private keys will only utilize the public key component silently ignored. Private keys will only utilize the public key component
stored within the key file. stored within the key file.
}, },
'Author' => ['todb', 'hdm'], 'Author' => [
'todb',
'hdm',
'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>', # Reworked the storage (db, credentials, notes, loot) only
],
'License' => MSF_LICENSE 'License' => MSF_LICENSE
) )
@ -116,11 +119,14 @@ class Metasploit3 < Msf::Auxiliary
keepers = [] keepers = []
keys.each do |key| keys.each do |key|
if key =~ /ssh-(dss|rsa)/ if key =~ /ssh-(dss|rsa)/
keepers << key # A public key has been provided
keepers << { :public => key, :private => "" }
next 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 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 next
end end
@ -130,8 +136,8 @@ class Metasploit3 < Msf::Auxiliary
next unless key =~ /\n-----END [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d?\x0a?$/m next unless key =~ /\n-----END [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d?\x0a?$/m
# Shouldn't have binary. # Shouldn't have binary.
next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty? next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty?
# Add more tests to taste. # Add more tests to test
keepers << key keepers << { :public => key, :private => "" }
end end
if keepers.empty? if keepers.empty?
print_error "#{ip}:#{rport} SSH - No valid keys found" print_error "#{ip}:#{rport} SSH - No valid keys found"
@ -142,9 +148,9 @@ class Metasploit3 < Msf::Auxiliary
def pull_cleartext_keys(keys) def pull_cleartext_keys(keys)
cleartext_keys = [] cleartext_keys = []
keys.each do |key| keys.each do |key|
next unless key next unless key[:public]
next if key =~ /Proc-Type:.*ENCRYPTED/ next if key[:private] =~ /Proc-Type:.*ENCRYPTED/
this_key = key.gsub(/\x0d/,"") this_key = { :public => key[:public].gsub(/\x0d/,""), :private => key[:private] }
next if cleartext_keys.include? this_key next if cleartext_keys.include? this_key
cleartext_keys << this_key cleartext_keys << this_key
end end
@ -182,26 +188,26 @@ class Metasploit3 < Msf::Auxiliary
@alerted_with_msg = true @alerted_with_msg = true
end 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}" key_info = "- #{$3.strip}"
end end
accepted = [] accepted = []
opt_hash = { opt_hash = {
:auth_methods => ['publickey'], :auth_methods => ['publickey'],
:msframework => framework, :msframework => framework,
:msfmodule => self, :msfmodule => self,
:port => port, :port => port,
:key_data => key_data, :key_data => key_data[:public],
:disable_agent => true, :disable_agent => true,
:record_auth_info => true, :record_auth_info => true,
:skip_private_keys => true, :skip_private_keys => true,
:config =>false, :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'] :proxies => datastore['Proxies']
} }
@ -244,35 +250,63 @@ class Metasploit3 < Msf::Auxiliary
end end
accepted.each do |key| accepted.each do |key|
print_brute :level => :good, :msg => "Accepted: '#{user}' with key '#{key[:fingerprint]}' #{key_info}" print_brute :level => :good, :msg => "Public key accepted: '#{user}' with key '#{key[:key][:fingerprint]}' #{key_info}"
do_report(ip, rport, user, key, key_data) do_report(ip, rport, user, key, key_data[:public], key_info)
end end
end 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 return unless framework.db.active
keyfile_path = store_keyfile(ip,user,key[:fingerprint],key_data)
cred_hash = { # Store a note relating to the public key test
:host => ip, note_information = {
:port => rport, user: user,
:sname => 'ssh', public_key: key_data,
:user => user, private_key: (key[:data][:private]) ? 'Yes' : 'No',
:pass => keyfile_path, info: key_info
:source_type => "user_supplied",
:type => 'ssh_pubkey',
:proof => "KEY=#{key[:fingerprint]}",
:duplicate_ok => true,
:active => true
} }
this_cred = report_auth_info(cred_hash) 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 end
def existing_loot(ltype, key_id) def existing_loot(ltype, key_id)
framework.db.loots(myworkspace).where(ltype: ltype).select {|l| l.info == key_id}.first framework.db.loots(myworkspace).where(ltype: ltype).select {|l| l.info == key_id}.first
end 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]/,"_") safe_username = user.gsub(/[^A-Za-z0-9]/,"_")
ktype = key_data.match(/ssh-(rsa|dss)/)[1] rescue nil ktype = key_data.match(/ssh-(rsa|dss)/)[1] rescue nil
return unless ktype return unless ktype
@ -291,6 +325,24 @@ class Metasploit3 < Msf::Auxiliary
return keyfile_path return keyfile_path
end 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) def run_host(ip)
# Since SSH collects keys and tries them all on one authentication session, it doesn't # 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, # make sense to iteratively go through all the keys individually. So, ignore the pass variable,