Land #7804, Switch the creds command to use named options
commit
ac2ceca5e3
|
@ -84,7 +84,6 @@ Feature: Help command
|
|||
|
||||
Command Description
|
||||
------- -----------
|
||||
creds List all credentials in the database
|
||||
db_connect Connect to an existing database
|
||||
db_disconnect Disconnect from the current database instance
|
||||
db_export Export a file containing the contents of the database
|
||||
|
@ -98,5 +97,15 @@ Feature: Help command
|
|||
services List all services in the database
|
||||
vulns List all vulnerabilities in the database
|
||||
workspace Switch between database workspaces
|
||||
|
||||
|
||||
Credentials Backend Commands
|
||||
============================
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
creds List all credentials in the database
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -0,0 +1,505 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rexml/document'
|
||||
require 'rex/parser/nmap_xml'
|
||||
require 'msf/core/db_export'
|
||||
|
||||
module Msf
|
||||
module Ui
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
|
||||
class Creds
|
||||
require 'tempfile'
|
||||
|
||||
include Msf::Ui::Console::CommandDispatcher
|
||||
include Metasploit::Credential::Creation
|
||||
|
||||
#
|
||||
# The dispatcher's name.
|
||||
#
|
||||
def name
|
||||
"Credentials Backend"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the hash of commands supported by this dispatcher.
|
||||
#
|
||||
def commands
|
||||
{
|
||||
"creds" => "List all credentials in the database"
|
||||
}
|
||||
end
|
||||
|
||||
def allowed_cred_types
|
||||
%w(password ntlm hash)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if the db is connected, prints an error and returns
|
||||
# false if not.
|
||||
#
|
||||
# All commands that require an active database should call this before
|
||||
# doing anything.
|
||||
# TODO: abstract the db methothds to a mixin that can be used by both dispatchers
|
||||
#
|
||||
def active?
|
||||
if not framework.db.active
|
||||
print_error("Database not connected")
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
#
|
||||
# Miscellaneous option helpers
|
||||
#
|
||||
|
||||
# Parse +arg+ into a {Rex::Socket::RangeWalker} and append the result into +host_ranges+
|
||||
#
|
||||
# @note This modifies +host_ranges+ in place
|
||||
#
|
||||
# @param arg [String] The thing to turn into a RangeWalker
|
||||
# @param host_ranges [Array] The array of ranges to append
|
||||
# @param required [Boolean] Whether an empty +arg+ should be an error
|
||||
# @return [Boolean] true if parsing was successful or false otherwise
|
||||
def arg_host_range(arg, host_ranges, required=false)
|
||||
if (!arg and required)
|
||||
print_error("Missing required host argument")
|
||||
return false
|
||||
end
|
||||
begin
|
||||
rw = Rex::Socket::RangeWalker.new(arg)
|
||||
rescue
|
||||
print_error("Invalid host parameter, #{arg}.")
|
||||
return false
|
||||
end
|
||||
|
||||
if rw.valid?
|
||||
host_ranges << rw
|
||||
else
|
||||
print_error("Invalid host parameter, #{arg}.")
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
#
|
||||
# Can return return active or all, on a certain host or range, on a
|
||||
# certain port or range, and/or on a service name.
|
||||
#
|
||||
def cmd_creds(*args)
|
||||
return unless active?
|
||||
|
||||
# Short-circuit help
|
||||
if args.delete "-h"
|
||||
cmd_creds_help
|
||||
return
|
||||
end
|
||||
|
||||
subcommand = args.shift
|
||||
|
||||
case subcommand
|
||||
when 'help'
|
||||
cmd_creds_help
|
||||
when 'add'
|
||||
creds_add(*args)
|
||||
else
|
||||
# then it's not actually a subcommand
|
||||
args.unshift(subcommand) if subcommand
|
||||
creds_search(*args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# TODO: this needs to be cleaned up to use the new syntax
|
||||
#
|
||||
def cmd_creds_help
|
||||
print_line
|
||||
print_line "With no sub-command, list credentials. If an address range is"
|
||||
print_line "given, show only credentials with logins on hosts within that"
|
||||
print_line "range."
|
||||
|
||||
print_line
|
||||
print_line "Usage - Listing credentials:"
|
||||
print_line " creds [filter options] [address range]"
|
||||
print_line
|
||||
print_line "Usage - Adding credentials:"
|
||||
print_line " creds add uses the following named parameters."
|
||||
{
|
||||
user: 'Public, usually a username',
|
||||
password: 'Private, private_type Password.',
|
||||
ntlm: 'Private, private_type NTLM Hash.',
|
||||
'ssh-key': 'Private, private_type SSH key, must be a file path.',
|
||||
hash: 'Private, private_type Nonreplayable hash',
|
||||
realm: 'Realm, ',
|
||||
'realm-type': "Realm, realm_type (#{Metasploit::Model::Realm::Key::SHORT_NAMES.keys.join(' ')}), defaults to domain."
|
||||
}.each_pair do |keyword, description|
|
||||
print_line " #{keyword.to_s.ljust 10}: #{description}"
|
||||
end
|
||||
print_line
|
||||
print_line "Examples: Adding"
|
||||
print_line " # Add a user, password and realm"
|
||||
print_line " creds add user:admin password:notpassword realm:workgroup"
|
||||
print_line " # Add a user and password"
|
||||
print_line " creds add user:guest password:'guest password'"
|
||||
print_line " # Add a password"
|
||||
print_line " creds add password:'password without username'"
|
||||
print_line " # Add a user with an NTLMHash"
|
||||
print_line " creds add user:admin ntlm:E2FC15074BF7751DD408E6B105741864:A1074A69B1BDE45403AB680504BBDD1A"
|
||||
print_line " # Add a NTLMHash"
|
||||
print_line " creds add ntlm:E2FC15074BF7751DD408E6B105741864:A1074A69B1BDE45403AB680504BBDD1A"
|
||||
print_line " # Add a user with an SSH key"
|
||||
print_line " creds add user:sshadmin ssh-key:/path/to/id_rsa"
|
||||
print_line " # Add a SSH key"
|
||||
print_line " creds add ssh-key:/path/to/id_rsa"
|
||||
print_line " # Add a user and a NonReplayableHash"
|
||||
print_line " creds add user:other hash:d19c32489b870735b5f587d76b934283"
|
||||
print_line " # Add a NonReplayableHash"
|
||||
print_line " creds add hash:d19c32489b870735b5f587d76b934283"
|
||||
|
||||
print_line
|
||||
print_line "General options"
|
||||
print_line " -h,--help Show this help information"
|
||||
print_line " -o <file> Send output to a file in csv format"
|
||||
print_line " -d Delete one or more credentials"
|
||||
print_line
|
||||
print_line "Filter options for listing"
|
||||
print_line " -P,--password <regex> List passwords that match this regex"
|
||||
print_line " -p,--port <portspec> List creds with logins on services matching this port spec"
|
||||
print_line " -s <svc names> List creds matching comma-separated service names"
|
||||
print_line " -u,--user <regex> List users that match this regex"
|
||||
print_line " -t,--type <type> List creds that match the following types: #{allowed_cred_types.join(',')}"
|
||||
print_line " -O,--origins List creds that match these origins"
|
||||
print_line " -R,--rhosts Set RHOSTS from the results of the search"
|
||||
|
||||
print_line
|
||||
print_line "Examples, listing:"
|
||||
print_line " creds # Default, returns all credentials"
|
||||
print_line " creds 1.2.3.4/24 # nmap host specification"
|
||||
print_line " creds -p 22-25,445 # nmap port specification"
|
||||
print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services"
|
||||
print_line " creds -t ntlm # All NTLM creds"
|
||||
print_line
|
||||
|
||||
print_line "Example, deleting:"
|
||||
print_line " # Delete all SMB credentials"
|
||||
print_line " creds -d -s smb"
|
||||
print_line
|
||||
end
|
||||
|
||||
# @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential`
|
||||
# @param username [String]
|
||||
# @param password [String]
|
||||
# @param realm [String]
|
||||
# @param realm_type [String] A key in `Metasploit::Model::Realm::Key::SHORT_NAMES`
|
||||
def creds_add(*args)
|
||||
params = args.inject({}) do |hsh, n|
|
||||
opt = n.split(':') # Splitting the string on colons.
|
||||
hsh[opt[0]] = opt[1..-1].join(':') # everything before the first : is the key, reasembling everything after the colon. why ntlm hashes
|
||||
hsh
|
||||
end
|
||||
|
||||
begin
|
||||
params.assert_valid_keys('user','password','realm','realm-type','ntlm','ssh-key','hash','host','port')
|
||||
rescue ArgumentError => e
|
||||
print_error(e.message)
|
||||
end
|
||||
|
||||
# Verify we only have one type of private
|
||||
if params.slice('password','ntlm','ssh-key','hash').length > 1
|
||||
private_keys = params.slice('password','ntlm','ssh-key','hash').keys
|
||||
print_error("You can only specify a single Private type. Private types given: #{private_keys.join(', ')}")
|
||||
return
|
||||
end
|
||||
|
||||
data = {
|
||||
workspace_id: framework.db.workspace,
|
||||
origin_type: :import,
|
||||
filename: 'msfconsole'
|
||||
}
|
||||
|
||||
data[:username] = params['user'] if params.key? 'user'
|
||||
|
||||
if params.key? 'realm'
|
||||
if params.key? 'realm-type'
|
||||
if Metasploit::Model::Realm::Key::SHORT_NAMES.key? params['realm-type']
|
||||
data[:realm_key] = Metasploit::Model::Realm::Key::SHORT_NAMES[params['realm-type']]
|
||||
else
|
||||
valid = Metasploit::Model::Realm::Key::SHORT_NAMES.keys.map{|n|"'#{n}'"}.join(", ")
|
||||
print_error("Invalid realm type: #{params['realm_type']}. Valid Values: #{valid}")
|
||||
end
|
||||
else
|
||||
data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
end
|
||||
data[:realm_value] = params['realm']
|
||||
end
|
||||
|
||||
if params.key? 'password'
|
||||
data[:private_type] = :password
|
||||
data[:private_data] = params['password']
|
||||
end
|
||||
|
||||
if params.key? 'ntlm'
|
||||
data[:private_type] = :ntlm_hash
|
||||
data[:private_data] = params['ntlm']
|
||||
end
|
||||
|
||||
if params.key? 'ssh-key'
|
||||
begin
|
||||
key_data = File.read(params['ssh-key'])
|
||||
rescue ::Errno::EACCES, ::Errno::ENOENT => e
|
||||
print_error("Failed to add ssh key: #{e}")
|
||||
end
|
||||
data[:private_type] = :ssh_key
|
||||
data[:private_data] = key_data
|
||||
end
|
||||
|
||||
if params.key? 'hash'
|
||||
data[:private_type] = :nonreplayable_hash
|
||||
data[:private_data] = params['hash']
|
||||
end
|
||||
|
||||
begin
|
||||
create_credential(data)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
print_error("Failed to add #{data['private_type']}: #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
def creds_search(*args)
|
||||
host_ranges = []
|
||||
origin_ranges = []
|
||||
port_ranges = []
|
||||
svcs = []
|
||||
rhosts = []
|
||||
|
||||
set_rhosts = false
|
||||
|
||||
#cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ]
|
||||
cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type' ]
|
||||
user = nil
|
||||
delete_count = 0
|
||||
|
||||
while (arg = args.shift)
|
||||
case arg
|
||||
when '-o'
|
||||
output_file = args.shift
|
||||
if (!output_file)
|
||||
print_error("Invalid output filename")
|
||||
return
|
||||
end
|
||||
output_file = ::File.expand_path(output_file)
|
||||
when "-p","--port"
|
||||
unless (arg_port_range(args.shift, port_ranges, true))
|
||||
return
|
||||
end
|
||||
when "-t","--type"
|
||||
ptype = args.shift
|
||||
if (!ptype)
|
||||
print_error("Argument required for -t")
|
||||
return
|
||||
end
|
||||
when "-s","--service"
|
||||
service = args.shift
|
||||
if (!service)
|
||||
print_error("Argument required for -s")
|
||||
return
|
||||
end
|
||||
svcs = service.split(/[\s]*,[\s]*/)
|
||||
when "-P","--password"
|
||||
pass = args.shift
|
||||
if (!pass)
|
||||
print_error("Argument required for -P")
|
||||
return
|
||||
end
|
||||
when "-u","--user"
|
||||
user = args.shift
|
||||
if (!user)
|
||||
print_error("Argument required for -u")
|
||||
return
|
||||
end
|
||||
when "-d"
|
||||
mode = :delete
|
||||
when '-R', '--rhosts'
|
||||
set_rhosts = true
|
||||
when '-O', '--origins'
|
||||
hosts = args.shift
|
||||
if !hosts
|
||||
print_error("Argument required for -O")
|
||||
return
|
||||
end
|
||||
arg_host_range(hosts, origin_ranges)
|
||||
else
|
||||
# Anything that wasn't an option is a host to search for
|
||||
unless (arg_host_range(arg, host_ranges))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If we get here, we're searching. Delete implies search
|
||||
|
||||
if ptype
|
||||
type = case ptype
|
||||
when 'password'
|
||||
Metasploit::Credential::Password
|
||||
when 'hash'
|
||||
Metasploit::Credential::PasswordHash
|
||||
when 'ntlm'
|
||||
Metasploit::Credential::NTLMHash
|
||||
else
|
||||
print_error("Unrecognized credential type #{ptype} -- must be one of #{allowed_cred_types.join(',')}")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# normalize
|
||||
ports = port_ranges.flatten.uniq
|
||||
svcs.flatten!
|
||||
tbl_opts = {
|
||||
'Header' => "Credentials",
|
||||
'Columns' => cred_table_columns
|
||||
}
|
||||
|
||||
tbl = Rex::Text::Table.new(tbl_opts)
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace )
|
||||
query = query.includes(:private, :public, :logins).references(:private, :public, :logins)
|
||||
query = query.includes(logins: [ :service, { service: :host } ])
|
||||
|
||||
if type.present?
|
||||
query = query.where(metasploit_credential_privates: { type: type })
|
||||
end
|
||||
|
||||
if svcs.present?
|
||||
query = query.where(Mdm::Service[:name].in(svcs))
|
||||
end
|
||||
|
||||
if ports.present?
|
||||
query = query.where(Mdm::Service[:port].in(ports))
|
||||
end
|
||||
|
||||
if user.present?
|
||||
# If we have a user regex, only include those that match
|
||||
query = query.where('"metasploit_credential_publics"."username" ~* ?', user)
|
||||
end
|
||||
|
||||
if pass.present?
|
||||
# If we have a password regex, only include those that match
|
||||
query = query.where('"metasploit_credential_privates"."data" ~* ?', pass)
|
||||
end
|
||||
|
||||
if host_ranges.any? || ports.any? || svcs.any?
|
||||
# Only find Cores that have non-zero Logins if the user specified a
|
||||
# filter based on host, port, or service name
|
||||
query = query.where(Metasploit::Credential::Login[:id].not_eq(nil))
|
||||
end
|
||||
|
||||
query.find_each do |core|
|
||||
|
||||
# Exclude non-blank username creds if that's what we're after
|
||||
if user == "" && core.public && !(core.public.username.blank?)
|
||||
next
|
||||
end
|
||||
|
||||
# Exclude non-blank password creds if that's what we're after
|
||||
if pass == "" && core.private && !(core.private.data.blank?)
|
||||
next
|
||||
end
|
||||
|
||||
origin = ''
|
||||
if core.origin.kind_of?(Metasploit::Credential::Origin::Service)
|
||||
origin = core.origin.service.host.address
|
||||
elsif core.origin.kind_of?(Metasploit::Credential::Origin::Session)
|
||||
origin = core.origin.session.host.address
|
||||
end
|
||||
|
||||
if !origin.empty? && origin_ranges.present? && !origin_ranges.any? {|range| range.include?(origin) }
|
||||
next
|
||||
end
|
||||
|
||||
if core.logins.empty? && origin_ranges.empty?
|
||||
tbl << [
|
||||
"", # host
|
||||
"", # cred
|
||||
"", # service
|
||||
core.public,
|
||||
core.private,
|
||||
core.realm,
|
||||
core.private ? core.private.class.model_name.human : "",
|
||||
]
|
||||
else
|
||||
core.logins.each do |login|
|
||||
# If none of this Core's associated Logins is for a host within
|
||||
# the user-supplied RangeWalker, then we don't have any reason to
|
||||
# print it out. However, we treat the absence of ranges as meaning
|
||||
# all hosts.
|
||||
if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) }
|
||||
next
|
||||
end
|
||||
|
||||
row = [ login.service.host.address ]
|
||||
row << origin
|
||||
rhosts << login.service.host.address
|
||||
if login.service.name.present?
|
||||
row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})"
|
||||
else
|
||||
row << "#{login.service.port}/#{login.service.proto}"
|
||||
end
|
||||
|
||||
row += [
|
||||
core.public,
|
||||
core.private,
|
||||
core.realm,
|
||||
core.private ? core.private.class.model_name.human : "",
|
||||
]
|
||||
tbl << row
|
||||
end
|
||||
end
|
||||
if mode == :delete
|
||||
core.destroy
|
||||
delete_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
if output_file.nil?
|
||||
print_line(tbl.to_s)
|
||||
else
|
||||
# create the output file
|
||||
::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) }
|
||||
print_status("Wrote creds to #{output_file}")
|
||||
end
|
||||
|
||||
# Finally, handle the case where the user wants the resulting list
|
||||
# of hosts to go into RHOSTS.
|
||||
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
|
||||
print_status("Deleted #{delete_count} creds") if delete_count > 0
|
||||
}
|
||||
end
|
||||
|
||||
def cmd_creds_tabs(str, words)
|
||||
case words.length
|
||||
when 1
|
||||
# subcommands
|
||||
tabs = [ 'add-ntlm', 'add-password', 'add-hash', 'add-ssh-key', ]
|
||||
when 2
|
||||
tabs = if words[1] == 'add-ssh-key'
|
||||
tab_complete_filenames(str, words)
|
||||
else
|
||||
[]
|
||||
end
|
||||
#when 5
|
||||
# tabs = Metasploit::Model::Realm::Key::SHORT_NAMES.keys
|
||||
else
|
||||
tabs = []
|
||||
end
|
||||
return tabs
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -14,7 +14,6 @@ class Db
|
|||
require 'tempfile'
|
||||
|
||||
include Msf::Ui::Console::CommandDispatcher
|
||||
include Metasploit::Credential::Creation
|
||||
|
||||
#
|
||||
# The dispatcher's name.
|
||||
|
@ -40,7 +39,6 @@ class Db
|
|||
"vulns" => "List all vulnerabilities in the database",
|
||||
"notes" => "List all notes in the database",
|
||||
"loot" => "List all loot in the database",
|
||||
"creds" => "List all credentials in the database",
|
||||
"db_import" => "Import a scan result file (filetype will be auto-detected)",
|
||||
"db_export" => "Export a file containing the contents of the database",
|
||||
"db_nmap" => "Executes nmap and records the output automatically",
|
||||
|
@ -64,10 +62,6 @@ class Db
|
|||
]
|
||||
end
|
||||
|
||||
def allowed_cred_types
|
||||
%w(password ntlm hash)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if the db is connected, prints an error and returns
|
||||
# false if not.
|
||||
|
@ -773,7 +767,7 @@ class Db
|
|||
host_ranges = []
|
||||
port_ranges = []
|
||||
svcs = []
|
||||
rhosts = []
|
||||
rhosts = []
|
||||
|
||||
search_term = nil
|
||||
show_info = false
|
||||
|
@ -861,387 +855,6 @@ class Db
|
|||
}
|
||||
end
|
||||
|
||||
def cmd_creds_help
|
||||
print_line
|
||||
print_line "With no sub-command, list credentials. If an address range is"
|
||||
print_line "given, show only credentials with logins on hosts within that"
|
||||
print_line "range."
|
||||
|
||||
print_line
|
||||
print_line "Usage - Listing credentials:"
|
||||
print_line " creds [filter options] [address range]"
|
||||
print_line
|
||||
print_line "Usage - Adding credentials:"
|
||||
print_line " creds add-ntlm <user> <ntlm hash> [domain]"
|
||||
print_line " creds add-password <user> <password> [realm] [realm-type]"
|
||||
print_line " creds add-ssh-key <user> </path/to/id_rsa> [realm-type]"
|
||||
print_line "Where [realm type] can be one of:"
|
||||
Metasploit::Model::Realm::Key::SHORT_NAMES.each do |short, description|
|
||||
print_line " #{short} - #{description}"
|
||||
end
|
||||
|
||||
print_line
|
||||
print_line "General options"
|
||||
print_line " -h,--help Show this help information"
|
||||
print_line " -o <file> Send output to a file in csv format"
|
||||
print_line " -d Delete one or more credentials"
|
||||
print_line
|
||||
print_line "Filter options for listing"
|
||||
print_line " -P,--password <regex> List passwords that match this regex"
|
||||
print_line " -p,--port <portspec> List creds with logins on services matching this port spec"
|
||||
print_line " -s <svc names> List creds matching comma-separated service names"
|
||||
print_line " -u,--user <regex> List users that match this regex"
|
||||
print_line " -t,--type <type> List creds that match the following types: #{allowed_cred_types.join(',')}"
|
||||
print_line " -O,--origins List creds that match these origins"
|
||||
print_line " -R,--rhosts Set RHOSTS from the results of the search"
|
||||
|
||||
print_line
|
||||
print_line "Examples, listing:"
|
||||
print_line " creds # Default, returns all credentials"
|
||||
print_line " creds 1.2.3.4/24 # nmap host specification"
|
||||
print_line " creds -p 22-25,445 # nmap port specification"
|
||||
print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services"
|
||||
print_line " creds -t ntlm # All NTLM creds"
|
||||
print_line
|
||||
|
||||
print_line
|
||||
print_line "Examples, adding:"
|
||||
print_line " # Add a user with an NTLMHash"
|
||||
print_line " creds add-ntlm alice 5cfe4c82d9ab8c66590f5b47cd6690f1:978a2e2e1dec9804c6b936f254727f9a"
|
||||
print_line " # Add a user with a blank password and a domain"
|
||||
print_line " creds add-password bob '' contosso"
|
||||
print_line " # Add a user with an SSH key"
|
||||
print_line " creds add-ssh-key root /root/.ssh/id_rsa"
|
||||
print_line
|
||||
|
||||
print_line "Example, deleting:"
|
||||
print_line " # Delete all SMB credentials"
|
||||
print_line " creds -d -s smb"
|
||||
print_line
|
||||
end
|
||||
|
||||
# @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential`
|
||||
# @param username [String]
|
||||
# @param password [String]
|
||||
# @param realm [String]
|
||||
# @param realm_type [String] A key in `Metasploit::Model::Realm::Key::SHORT_NAMES`
|
||||
def creds_add(private_type, username, password=nil, realm=nil, realm_type=nil)
|
||||
cred_data = {
|
||||
username: username,
|
||||
private_data: password,
|
||||
private_type: private_type,
|
||||
workspace_id: framework.db.workspace,
|
||||
origin_type: :import,
|
||||
filename: "msfconsole"
|
||||
}
|
||||
if realm.present?
|
||||
if realm_type.present?
|
||||
realm_key = Metasploit::Model::Realm::Key::SHORT_NAMES[realm_type]
|
||||
if realm_key.nil?
|
||||
valid = Metasploit::Model::Realm::Key::SHORT_NAMES.keys.map{|n|"'#{n}'"}.join(", ")
|
||||
print_error("Invalid realm type: #{realm_type}. Valid values: #{valid}")
|
||||
return
|
||||
end
|
||||
end
|
||||
realm_key ||= Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
cred_data.merge!(
|
||||
realm_value: realm,
|
||||
realm_key: realm_key
|
||||
)
|
||||
end
|
||||
|
||||
begin
|
||||
create_credential(cred_data)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
print_error("Failed to add #{private_type}: #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
def creds_add_non_replayable_hash(*args)
|
||||
creds_add(:non_replayable_hash, *args)
|
||||
end
|
||||
|
||||
def creds_add_ntlm_hash(*args)
|
||||
creds_add(:ntlm_hash, *args)
|
||||
end
|
||||
|
||||
def creds_add_password(*args)
|
||||
creds_add(:password, *args)
|
||||
end
|
||||
|
||||
def creds_add_ssh_key(username, *args)
|
||||
key_file, realm = args
|
||||
begin
|
||||
key_data = File.read(key_file)
|
||||
rescue ::Errno::EACCES, ::Errno::ENOENT => e
|
||||
print_error("Failed to add ssh key: #{e}")
|
||||
else
|
||||
creds_add(:ssh_key, username, key_data, realm)
|
||||
end
|
||||
end
|
||||
|
||||
def creds_search(*args)
|
||||
host_ranges = []
|
||||
origin_ranges = []
|
||||
port_ranges = []
|
||||
svcs = []
|
||||
rhosts = []
|
||||
|
||||
set_rhosts = false
|
||||
|
||||
#cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ]
|
||||
cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type' ]
|
||||
user = nil
|
||||
delete_count = 0
|
||||
|
||||
while (arg = args.shift)
|
||||
case arg
|
||||
when '-o'
|
||||
output_file = args.shift
|
||||
if (!output_file)
|
||||
print_error("Invalid output filename")
|
||||
return
|
||||
end
|
||||
output_file = ::File.expand_path(output_file)
|
||||
when "-p","--port"
|
||||
unless (arg_port_range(args.shift, port_ranges, true))
|
||||
return
|
||||
end
|
||||
when "-t","--type"
|
||||
ptype = args.shift
|
||||
if (!ptype)
|
||||
print_error("Argument required for -t")
|
||||
return
|
||||
end
|
||||
when "-s","--service"
|
||||
service = args.shift
|
||||
if (!service)
|
||||
print_error("Argument required for -s")
|
||||
return
|
||||
end
|
||||
svcs = service.split(/[\s]*,[\s]*/)
|
||||
when "-P","--password"
|
||||
pass = args.shift
|
||||
if (!pass)
|
||||
print_error("Argument required for -P")
|
||||
return
|
||||
end
|
||||
when "-u","--user"
|
||||
user = args.shift
|
||||
if (!user)
|
||||
print_error("Argument required for -u")
|
||||
return
|
||||
end
|
||||
when "-d"
|
||||
mode = :delete
|
||||
when '-R', '--rhosts'
|
||||
set_rhosts = true
|
||||
when '-O', '--origins'
|
||||
hosts = args.shift
|
||||
if !hosts
|
||||
print_error("Argument required for -O")
|
||||
return
|
||||
end
|
||||
arg_host_range(hosts, origin_ranges)
|
||||
else
|
||||
# Anything that wasn't an option is a host to search for
|
||||
unless (arg_host_range(arg, host_ranges))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If we get here, we're searching. Delete implies search
|
||||
|
||||
if ptype
|
||||
type = case ptype
|
||||
when 'password'
|
||||
Metasploit::Credential::Password
|
||||
when 'hash'
|
||||
Metasploit::Credential::PasswordHash
|
||||
when 'ntlm'
|
||||
Metasploit::Credential::NTLMHash
|
||||
else
|
||||
print_error("Unrecognized credential type #{ptype} -- must be one of #{allowed_cred_types.join(',')}")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# normalize
|
||||
ports = port_ranges.flatten.uniq
|
||||
svcs.flatten!
|
||||
tbl_opts = {
|
||||
'Header' => "Credentials",
|
||||
'Columns' => cred_table_columns
|
||||
}
|
||||
|
||||
tbl = Rex::Text::Table.new(tbl_opts)
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace )
|
||||
query = query.includes(:private, :public, :logins).references(:private, :public, :logins)
|
||||
query = query.includes(logins: [ :service, { service: :host } ])
|
||||
|
||||
if type.present?
|
||||
query = query.where(metasploit_credential_privates: { type: type })
|
||||
end
|
||||
|
||||
if svcs.present?
|
||||
query = query.where(Mdm::Service[:name].in(svcs))
|
||||
end
|
||||
|
||||
if ports.present?
|
||||
query = query.where(Mdm::Service[:port].in(ports))
|
||||
end
|
||||
|
||||
if user.present?
|
||||
# If we have a user regex, only include those that match
|
||||
query = query.where('"metasploit_credential_publics"."username" ~* ?', user)
|
||||
end
|
||||
|
||||
if pass.present?
|
||||
# If we have a password regex, only include those that match
|
||||
query = query.where('"metasploit_credential_privates"."data" ~* ?', pass)
|
||||
end
|
||||
|
||||
if host_ranges.any? || ports.any? || svcs.any?
|
||||
# Only find Cores that have non-zero Logins if the user specified a
|
||||
# filter based on host, port, or service name
|
||||
query = query.where(Metasploit::Credential::Login[:id].not_eq(nil))
|
||||
end
|
||||
|
||||
query.find_each do |core|
|
||||
|
||||
# Exclude non-blank username creds if that's what we're after
|
||||
if user == "" && core.public && !(core.public.username.blank?)
|
||||
next
|
||||
end
|
||||
|
||||
# Exclude non-blank password creds if that's what we're after
|
||||
if pass == "" && core.private && !(core.private.data.blank?)
|
||||
next
|
||||
end
|
||||
|
||||
origin = ''
|
||||
if core.origin.kind_of?(Metasploit::Credential::Origin::Service)
|
||||
origin = core.origin.service.host.address
|
||||
elsif core.origin.kind_of?(Metasploit::Credential::Origin::Session)
|
||||
origin = core.origin.session.host.address
|
||||
end
|
||||
|
||||
if !origin.empty? && origin_ranges.present? && !origin_ranges.any? {|range| range.include?(origin) }
|
||||
next
|
||||
end
|
||||
|
||||
if core.logins.empty? && origin_ranges.empty?
|
||||
tbl << [
|
||||
"", # host
|
||||
"", # cred
|
||||
"", # service
|
||||
core.public,
|
||||
core.private,
|
||||
core.realm,
|
||||
core.private ? core.private.class.model_name.human : "",
|
||||
]
|
||||
else
|
||||
core.logins.each do |login|
|
||||
# If none of this Core's associated Logins is for a host within
|
||||
# the user-supplied RangeWalker, then we don't have any reason to
|
||||
# print it out. However, we treat the absence of ranges as meaning
|
||||
# all hosts.
|
||||
if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) }
|
||||
next
|
||||
end
|
||||
|
||||
row = [ login.service.host.address ]
|
||||
row << origin
|
||||
rhosts << login.service.host.address
|
||||
if login.service.name.present?
|
||||
row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})"
|
||||
else
|
||||
row << "#{login.service.port}/#{login.service.proto}"
|
||||
end
|
||||
|
||||
row += [
|
||||
core.public,
|
||||
core.private,
|
||||
core.realm,
|
||||
core.private ? core.private.class.model_name.human : "",
|
||||
]
|
||||
tbl << row
|
||||
end
|
||||
end
|
||||
if mode == :delete
|
||||
core.destroy
|
||||
delete_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
if output_file.nil?
|
||||
print_line(tbl.to_s)
|
||||
else
|
||||
# create the output file
|
||||
::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) }
|
||||
print_status("Wrote creds to #{output_file}")
|
||||
end
|
||||
|
||||
# Finally, handle the case where the user wants the resulting list
|
||||
# of hosts to go into RHOSTS.
|
||||
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
|
||||
print_status("Deleted #{delete_count} creds") if delete_count > 0
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Can return return active or all, on a certain host or range, on a
|
||||
# certain port or range, and/or on a service name.
|
||||
#
|
||||
def cmd_creds(*args)
|
||||
return unless active?
|
||||
|
||||
# Short-circuit help
|
||||
if args.delete "-h"
|
||||
cmd_creds_help
|
||||
return
|
||||
end
|
||||
|
||||
subcommand = args.shift
|
||||
case subcommand
|
||||
when "add-ntlm"
|
||||
creds_add_ntlm_hash(*args)
|
||||
when "add-password"
|
||||
creds_add_password(*args)
|
||||
when "add-hash"
|
||||
creds_add_non_replayable_hash(*args)
|
||||
when "add-ssh-key"
|
||||
creds_add_ssh_key(*args)
|
||||
else
|
||||
# then it's not actually a subcommand
|
||||
args.unshift(subcommand) if subcommand
|
||||
creds_search(*args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def cmd_creds_tabs(str, words)
|
||||
case words.length
|
||||
when 1
|
||||
# subcommands
|
||||
tabs = [ 'add-ntlm', 'add-password', 'add-hash', 'add-ssh-key', ]
|
||||
when 2
|
||||
tabs = if words[1] == 'add-ssh-key'
|
||||
tab_complete_filenames(str, words)
|
||||
else
|
||||
[]
|
||||
end
|
||||
#when 5
|
||||
# tabs = Metasploit::Model::Realm::Key::SHORT_NAMES.keys
|
||||
else
|
||||
tabs = []
|
||||
end
|
||||
return tabs
|
||||
end
|
||||
|
||||
def cmd_notes_help
|
||||
print_line "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]"
|
||||
print_line
|
||||
|
|
|
@ -126,6 +126,8 @@ class Driver < Msf::Ui::Driver
|
|||
if (framework.db.usable)
|
||||
require 'msf/ui/console/command_dispatcher/db'
|
||||
enstack_dispatcher(CommandDispatcher::Db)
|
||||
require 'msf/ui/console/command_dispatcher/creds'
|
||||
enstack_dispatcher(CommandDispatcher::Creds)
|
||||
else
|
||||
print_error("***")
|
||||
if framework.db.error == "disabled"
|
||||
|
|
|
@ -0,0 +1,449 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/ui'
|
||||
require 'msf/ui/console/command_dispatcher/creds'
|
||||
|
||||
RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
include_context 'Msf::DBManager'
|
||||
include_context 'Msf::UIDriver'
|
||||
|
||||
subject(:creds) do
|
||||
described_class.new(driver)
|
||||
end
|
||||
|
||||
it { is_expected.to respond_to :active? }
|
||||
it { is_expected.to respond_to :creds_add }
|
||||
it { is_expected.to respond_to :creds_search }
|
||||
|
||||
describe '#cmd_creds' do
|
||||
let(:username) { 'thisuser' }
|
||||
let(:password) { 'thispass' }
|
||||
let(:realm) { 'thisrealm' }
|
||||
let(:realm_type) { 'Active Directory Domain' }
|
||||
describe '-u' do
|
||||
let(:nomatch_username) { 'thatuser' }
|
||||
let(:nomatch_password) { 'thatpass' }
|
||||
let(:blank_username) { '' }
|
||||
let(:blank_password) { '' }
|
||||
let(:nonblank_username) { 'nonblank_user' }
|
||||
let(:nonblank_password) { 'nonblank_pass' }
|
||||
|
||||
let!(:origin) { FactoryGirl.create(:metasploit_credential_origin_import) }
|
||||
|
||||
before(:example) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_password, data: password)
|
||||
pub = FactoryGirl.create(:metasploit_credential_username, username: username)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
blank_pub = FactoryGirl.create(:metasploit_credential_blank_username)
|
||||
nonblank_priv = FactoryGirl.create(:metasploit_credential_password, data: nonblank_password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: nonblank_priv,
|
||||
public: blank_pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
nonblank_pub = FactoryGirl.create(:metasploit_credential_username, username: nonblank_username)
|
||||
blank_priv = FactoryGirl.create(:metasploit_credential_password, data: blank_password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: blank_priv,
|
||||
public: nonblank_pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
|
||||
context 'when the credential is present' do
|
||||
it 'should show a user that matches the given expression' do
|
||||
creds.cmd_creds('-u', username)
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------',
|
||||
' thisuser thispass Password'
|
||||
])
|
||||
end
|
||||
|
||||
it 'should match a regular expression' do
|
||||
creds.cmd_creds('-u', "^#{username}$")
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------',
|
||||
' thisuser thispass Password'
|
||||
])
|
||||
end
|
||||
|
||||
it 'should return nothing for a non-matching regular expression' do
|
||||
creds.cmd_creds('-u', "^#{nomatch_username}$")
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------'
|
||||
])
|
||||
end
|
||||
|
||||
context 'and when the username is blank' do
|
||||
it 'should show a user that matches the given expression' do
|
||||
creds.cmd_creds('-u', blank_username)
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------',
|
||||
' nonblank_pass Password'
|
||||
])
|
||||
end
|
||||
end
|
||||
context 'and when the password is blank' do
|
||||
it 'should show a user that matches the given expression' do
|
||||
creds.cmd_creds('-P', blank_password)
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------',
|
||||
' nonblank_user Password'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the credential is absent' do
|
||||
context 'due to a nonmatching username' do
|
||||
it 'should return a blank set' do
|
||||
creds.cmd_creds('-u', nomatch_username)
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------'
|
||||
])
|
||||
end
|
||||
end
|
||||
context 'due to a nonmatching password' do
|
||||
it 'should return a blank set' do
|
||||
creds.cmd_creds('-P', nomatch_password)
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '-t' do
|
||||
context 'with an invalid type' do
|
||||
it 'should print the list of valid types' do
|
||||
creds.cmd_creds('-t', 'asdf')
|
||||
expect(@error).to match_array [
|
||||
'Unrecognized credential type asdf -- must be one of password,ntlm,hash'
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid types' do
|
||||
let(:ntlm_hash) { '1443d06412d8c0e6e72c57ef50f76a05:27c433245e4763d074d30a05aae0af2c' }
|
||||
|
||||
let!(:pub) do
|
||||
FactoryGirl.create(:metasploit_credential_username, username: username)
|
||||
end
|
||||
let!(:password_core) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_password, data: password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
|
||||
# # Somehow this is hitting a unique constraint on Cores with the same
|
||||
# # Public, even though it has a different Private. Skip for now
|
||||
# let!(:ntlm_core) do
|
||||
# priv = FactoryGirl.create(:metasploit_credential_ntlm_hash, data: ntlm_hash)
|
||||
# FactoryGirl.create(:metasploit_credential_core,
|
||||
# origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
# private: priv,
|
||||
# public: pub,
|
||||
# realm: nil,
|
||||
# workspace: framework.db.workspace)
|
||||
# end
|
||||
# let!(:nonreplayable_core) do
|
||||
# priv = FactoryGirl.create(:metasploit_credential_nonreplayable_hash, data: 'asdf')
|
||||
# FactoryGirl.create(:metasploit_credential_core,
|
||||
# origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
# private: priv,
|
||||
# public: pub,
|
||||
# realm: nil,
|
||||
# workspace: framework.db.workspace)
|
||||
# end
|
||||
|
||||
after(:example) do
|
||||
# ntlm_core.destroy
|
||||
password_core.destroy
|
||||
# nonreplayable_core.destroy
|
||||
end
|
||||
|
||||
context 'password' do
|
||||
it 'should show just the password' do
|
||||
creds.cmd_creds('-t', 'password')
|
||||
# Table matching really sucks
|
||||
expect(@output).to eq([
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host origin service public private realm private_type',
|
||||
'---- ------ ------- ------ ------- ----- ------------',
|
||||
' thisuser thispass Password'
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'ntlm' do
|
||||
it 'should show just the ntlm' do
|
||||
skip 'Weird uniqueness constraint on Core (workspace_id, public_id)'
|
||||
|
||||
creds.cmd_creds('-t', 'ntlm')
|
||||
# Table matching really sucks
|
||||
expect(@output).to =~ [
|
||||
'Credentials',
|
||||
'===========',
|
||||
'',
|
||||
'host service public private realm private_type',
|
||||
'---- ------- ------ ------- ----- ------------',
|
||||
" thisuser #{ntlm_hash} NTLM hash"
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'add' do
|
||||
let(:pub) { FactoryGirl.create(:metasploit_credential_username, username: username) }
|
||||
let(:priv) { FactoryGirl.create(:metasploit_credential_password, data: password) }
|
||||
let(:r) { FactoryGirl.create(:metasploit_credential_realm, key: realm_type, value: realm) }
|
||||
|
||||
context 'username password and realm' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "password:#{password}", "realm:#{realm}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: r,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "password:#{password}", "realm:#{realm}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'username and realm' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "realm:#{realm}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: nil,
|
||||
public: pub,
|
||||
realm: r,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "realm:#{realm}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'username and password' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "password:#{password}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "password:#{password}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'password and realm' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "password:#{password}", "realm:#{realm}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: nil,
|
||||
realm: r,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "password:#{password}", "realm:#{realm}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'username' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: nil,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'private_types' do
|
||||
context 'password' do
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "password:#{password}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: nil,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "password:#{password}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
context 'ntlm' do
|
||||
let(:priv) { FactoryGirl.create(:metasploit_credential_ntlm_hash) }
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "ntlm:#{priv.data}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: nil,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "ntlm:#{priv.data}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
context 'hash' do
|
||||
let(:priv) { FactoryGirl.create(:metasploit_credential_nonreplayable_hash) }
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "hash:#{priv.data}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: nil,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "hash:#{priv.data}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
context 'ssh-key' do
|
||||
let(:priv) { FactoryGirl.create(:metasploit_credential_ssh_key) }
|
||||
before(:each) do
|
||||
@file = Tempfile.new('id_rsa')
|
||||
@file.write(priv.data)
|
||||
@file.close
|
||||
end
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "ssh-key:#{@file.path}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "user:#{username}", "ssh-key:#{@file.path}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'realm-types' do
|
||||
Metasploit::Model::Realm::Key::SHORT_NAMES.each do |short_name, long_name|
|
||||
context "#{short_name}" do
|
||||
let(:r) { FactoryGirl.create(:metasploit_credential_realm, key: long_name) }
|
||||
it 'creates a core if one does not exist' do
|
||||
expect {
|
||||
creds.cmd_creds('add', "realm:#{r.value}", "realm-type:#{short_name}")
|
||||
}.to change { Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
it 'does not create a core if it already exists' do
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: nil,
|
||||
public: nil,
|
||||
realm: r,
|
||||
workspace: framework.db.workspace)
|
||||
expect {
|
||||
creds.cmd_creds('add', "realm:#{r.value}", "realm-type:#{short_name}")
|
||||
}.to_not change { Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,8 +14,6 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
|
|||
it { is_expected.to respond_to :active? }
|
||||
it { is_expected.to respond_to :arg_host_range }
|
||||
it { is_expected.to respond_to :arg_port_range }
|
||||
it { is_expected.to respond_to :cmd_creds_help }
|
||||
it { is_expected.to respond_to :cmd_creds_tabs }
|
||||
it { is_expected.to respond_to :cmd_db_autopwn }
|
||||
it { is_expected.to respond_to :cmd_db_autopwn_help }
|
||||
it { is_expected.to respond_to :cmd_db_connect }
|
||||
|
@ -47,12 +45,6 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
|
|||
it { is_expected.to respond_to :cmd_workspace_help }
|
||||
it { is_expected.to respond_to :cmd_workspace_tabs }
|
||||
it { is_expected.to respond_to :commands }
|
||||
it { is_expected.to respond_to :creds_add }
|
||||
it { is_expected.to respond_to :creds_add_non_replayable_hash }
|
||||
it { is_expected.to respond_to :creds_add_ntlm_hash }
|
||||
it { is_expected.to respond_to :creds_add_password }
|
||||
it { is_expected.to respond_to :creds_add_ssh_key }
|
||||
it { is_expected.to respond_to :creds_search }
|
||||
it { is_expected.to respond_to :db_check_driver }
|
||||
it { is_expected.to respond_to :db_connect_postgresql }
|
||||
it { is_expected.to respond_to :db_find_tools }
|
||||
|
@ -63,258 +55,6 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
|
|||
it { is_expected.to respond_to :name }
|
||||
it { is_expected.to respond_to :set_rhosts_from_addrs }
|
||||
|
||||
describe "#cmd_creds" do
|
||||
let(:username) { "thisuser" }
|
||||
let(:password) { "thispass" }
|
||||
|
||||
describe "-u" do
|
||||
let(:nomatch_username) { "thatuser" }
|
||||
let(:nomatch_password) { "thatpass" }
|
||||
let(:blank_username) { "" }
|
||||
let(:blank_password) { "" }
|
||||
let(:nonblank_username) { "nonblank_user" }
|
||||
let(:nonblank_password) { "nonblank_pass" }
|
||||
|
||||
let!(:origin) { FactoryGirl.create(:metasploit_credential_origin_import) }
|
||||
|
||||
before(:example) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_password, data: password)
|
||||
pub = FactoryGirl.create(:metasploit_credential_username, username: username)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
blank_pub = FactoryGirl.create(:metasploit_credential_blank_username)
|
||||
nonblank_priv = FactoryGirl.create(:metasploit_credential_password, data: nonblank_password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: nonblank_priv,
|
||||
public: blank_pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
nonblank_pub = FactoryGirl.create(:metasploit_credential_username, username: nonblank_username)
|
||||
blank_priv = FactoryGirl.create(:metasploit_credential_password, data: blank_password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: origin,
|
||||
private: blank_priv,
|
||||
public: nonblank_pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
|
||||
context "when the credential is present" do
|
||||
it "should show a user that matches the given expression" do
|
||||
db.cmd_creds("-u", username)
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------",
|
||||
" thisuser thispass Password"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should match a regular expression' do
|
||||
subject.cmd_creds("-u", "^#{username}$")
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------",
|
||||
" thisuser thispass Password"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should return nothing for a non-matching regular expression' do
|
||||
subject.cmd_creds("-u", "^#{nomatch_username}$")
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------"
|
||||
])
|
||||
end
|
||||
|
||||
context "and when the username is blank" do
|
||||
it "should show a user that matches the given expression" do
|
||||
db.cmd_creds("-u", blank_username)
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------",
|
||||
" nonblank_pass Password"
|
||||
])
|
||||
end
|
||||
end
|
||||
context "and when the password is blank" do
|
||||
it "should show a user that matches the given expression" do
|
||||
db.cmd_creds("-P", blank_password)
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------",
|
||||
" nonblank_user Password"
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the credential is absent" do
|
||||
context "due to a nonmatching username" do
|
||||
it "should return a blank set" do
|
||||
db.cmd_creds("-u", nomatch_username)
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------"
|
||||
])
|
||||
end
|
||||
end
|
||||
context "due to a nonmatching password" do
|
||||
it "should return a blank set" do
|
||||
db.cmd_creds("-P", nomatch_password)
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------"
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "-t" do
|
||||
context "with an invalid type" do
|
||||
it "should print the list of valid types" do
|
||||
db.cmd_creds("-t", "asdf")
|
||||
expect(@error).to match_array [
|
||||
"Unrecognized credential type asdf -- must be one of password,ntlm,hash"
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context "with valid types" do
|
||||
let(:ntlm_hash) { "1443d06412d8c0e6e72c57ef50f76a05:27c433245e4763d074d30a05aae0af2c" }
|
||||
|
||||
let!(:pub) do
|
||||
FactoryGirl.create(:metasploit_credential_username, username: username)
|
||||
end
|
||||
let!(:password_core) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_password, data: password)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
|
||||
=begin
|
||||
# Somehow this is hitting a unique constraint on Cores with the same
|
||||
# Public, even though it has a different Private. Skip for now
|
||||
let!(:ntlm_core) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_ntlm_hash, data: ntlm_hash)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
let!(:nonreplayable_core) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_nonreplayable_hash, data: 'asdf')
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
=end
|
||||
|
||||
after(:example) do
|
||||
#ntlm_core.destroy
|
||||
password_core.destroy
|
||||
#nonreplayable_core.destroy
|
||||
end
|
||||
|
||||
context "password" do
|
||||
it "should show just the password" do
|
||||
db.cmd_creds("-t", "password")
|
||||
# Table matching really sucks
|
||||
expect(@output).to eq([
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host origin service public private realm private_type",
|
||||
"---- ------ ------- ------ ------- ----- ------------",
|
||||
" thisuser thispass Password"
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context "ntlm" do
|
||||
it "should show just the ntlm" do
|
||||
skip "Weird uniqueness constraint on Core (workspace_id, public_id)"
|
||||
|
||||
db.cmd_creds("-t", "ntlm")
|
||||
# Table matching really sucks
|
||||
expect(@output).to =~ [
|
||||
"Credentials",
|
||||
"===========",
|
||||
"",
|
||||
"host service public private realm private_type",
|
||||
"---- ------- ------ ------- ----- ------------",
|
||||
" thisuser #{ntlm_hash } NTLM hash"
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "add-password" do
|
||||
context "when no core exists" do
|
||||
it "should add a Core" do
|
||||
expect {
|
||||
subject.cmd_creds("add-password", username, password)
|
||||
}.to change{ Metasploit::Credential::Core.count }.by 1
|
||||
end
|
||||
end
|
||||
context "when a core already exists" do
|
||||
before(:example) do
|
||||
priv = FactoryGirl.create(:metasploit_credential_password, data: password)
|
||||
pub = FactoryGirl.create(:metasploit_credential_username, username: username)
|
||||
FactoryGirl.create(:metasploit_credential_core,
|
||||
origin: FactoryGirl.create(:metasploit_credential_origin_import),
|
||||
private: priv,
|
||||
public: pub,
|
||||
realm: nil,
|
||||
workspace: framework.db.workspace)
|
||||
end
|
||||
it "should not add a Core" do
|
||||
expect {
|
||||
subject.cmd_creds("add-password", username, password)
|
||||
}.to_not change{ Metasploit::Credential::Core.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#cmd_db_export" do
|
||||
describe "-h" do
|
||||
it "should show a help message" do
|
||||
|
|
Loading…
Reference in New Issue