Land #7804, Switch the creds command to use named options
commit
ac2ceca5e3
|
@ -84,7 +84,6 @@ Feature: Help command
|
||||||
|
|
||||||
Command Description
|
Command Description
|
||||||
------- -----------
|
------- -----------
|
||||||
creds List all credentials in the database
|
|
||||||
db_connect Connect to an existing database
|
db_connect Connect to an existing database
|
||||||
db_disconnect Disconnect from the current database instance
|
db_disconnect Disconnect from the current database instance
|
||||||
db_export Export a file containing the contents of the database
|
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
|
services List all services in the database
|
||||||
vulns List all vulnerabilities in the database
|
vulns List all vulnerabilities in the database
|
||||||
workspace Switch between database workspaces
|
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'
|
require 'tempfile'
|
||||||
|
|
||||||
include Msf::Ui::Console::CommandDispatcher
|
include Msf::Ui::Console::CommandDispatcher
|
||||||
include Metasploit::Credential::Creation
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# The dispatcher's name.
|
# The dispatcher's name.
|
||||||
|
@ -40,7 +39,6 @@ class Db
|
||||||
"vulns" => "List all vulnerabilities in the database",
|
"vulns" => "List all vulnerabilities in the database",
|
||||||
"notes" => "List all notes in the database",
|
"notes" => "List all notes in the database",
|
||||||
"loot" => "List all loot 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_import" => "Import a scan result file (filetype will be auto-detected)",
|
||||||
"db_export" => "Export a file containing the contents of the database",
|
"db_export" => "Export a file containing the contents of the database",
|
||||||
"db_nmap" => "Executes nmap and records the output automatically",
|
"db_nmap" => "Executes nmap and records the output automatically",
|
||||||
|
@ -64,10 +62,6 @@ class Db
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_cred_types
|
|
||||||
%w(password ntlm hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Returns true if the db is connected, prints an error and returns
|
# Returns true if the db is connected, prints an error and returns
|
||||||
# false if not.
|
# false if not.
|
||||||
|
@ -773,7 +767,7 @@ class Db
|
||||||
host_ranges = []
|
host_ranges = []
|
||||||
port_ranges = []
|
port_ranges = []
|
||||||
svcs = []
|
svcs = []
|
||||||
rhosts = []
|
rhosts = []
|
||||||
|
|
||||||
search_term = nil
|
search_term = nil
|
||||||
show_info = false
|
show_info = false
|
||||||
|
@ -861,387 +855,6 @@ class Db
|
||||||
}
|
}
|
||||||
end
|
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
|
def cmd_notes_help
|
||||||
print_line "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]"
|
print_line "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]"
|
||||||
print_line
|
print_line
|
||||||
|
|
|
@ -126,6 +126,8 @@ class Driver < Msf::Ui::Driver
|
||||||
if (framework.db.usable)
|
if (framework.db.usable)
|
||||||
require 'msf/ui/console/command_dispatcher/db'
|
require 'msf/ui/console/command_dispatcher/db'
|
||||||
enstack_dispatcher(CommandDispatcher::Db)
|
enstack_dispatcher(CommandDispatcher::Db)
|
||||||
|
require 'msf/ui/console/command_dispatcher/creds'
|
||||||
|
enstack_dispatcher(CommandDispatcher::Creds)
|
||||||
else
|
else
|
||||||
print_error("***")
|
print_error("***")
|
||||||
if framework.db.error == "disabled"
|
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 :active? }
|
||||||
it { is_expected.to respond_to :arg_host_range }
|
it { is_expected.to respond_to :arg_host_range }
|
||||||
it { is_expected.to respond_to :arg_port_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 }
|
||||||
it { is_expected.to respond_to :cmd_db_autopwn_help }
|
it { is_expected.to respond_to :cmd_db_autopwn_help }
|
||||||
it { is_expected.to respond_to :cmd_db_connect }
|
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_help }
|
||||||
it { is_expected.to respond_to :cmd_workspace_tabs }
|
it { is_expected.to respond_to :cmd_workspace_tabs }
|
||||||
it { is_expected.to respond_to :commands }
|
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_check_driver }
|
||||||
it { is_expected.to respond_to :db_connect_postgresql }
|
it { is_expected.to respond_to :db_connect_postgresql }
|
||||||
it { is_expected.to respond_to :db_find_tools }
|
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 :name }
|
||||||
it { is_expected.to respond_to :set_rhosts_from_addrs }
|
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 "#cmd_db_export" do
|
||||||
describe "-h" do
|
describe "-h" do
|
||||||
it "should show a help message" do
|
it "should show a help message" do
|
||||||
|
|
Loading…
Reference in New Issue