Massive rework of the storage/notes/reporting
parent
66ba204c11
commit
09c58e4097
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue