cd00585e12
This involves updating add_host_tag and delete_host_tag and performing some refactoring.
312 lines
10 KiB
Ruby
312 lines
10 KiB
Ruby
module Msf::DBManager::Host
|
|
# TODO: doesn't appear to have any callers. How is this used?
|
|
# Deletes a host and associated data matching this address/comm
|
|
def del_host(wspace, address, comm='')
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
address, scope = address.split('%', 2)
|
|
host = wspace.hosts.find_by_address_and_comm(address, comm)
|
|
host.destroy if host
|
|
}
|
|
end
|
|
|
|
# Deletes Host entries based on the IDs passed in.
|
|
#
|
|
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the Host entries to delete.
|
|
# @return [Array] Array containing the Mdm::Host objects that were successfully deleted.
|
|
def delete_host(opts)
|
|
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
|
|
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
deleted = []
|
|
opts[:ids].each do |host_id|
|
|
host = Mdm::Host.find(host_id)
|
|
begin
|
|
deleted << host.destroy
|
|
rescue # refs suck
|
|
elog("Forcibly deleting #{host.address}")
|
|
deleted << host.delete
|
|
end
|
|
end
|
|
|
|
return deleted
|
|
}
|
|
end
|
|
|
|
#
|
|
# Iterates over the hosts table calling the supplied block with the host
|
|
# instance of each entry.
|
|
#
|
|
def each_host(wspace=framework.db.workspace, &block)
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
wspace.hosts.each do |host|
|
|
block.call(host)
|
|
end
|
|
}
|
|
end
|
|
|
|
# Exactly like report_host but waits for the database to create a host and returns it.
|
|
def find_or_create_host(opts)
|
|
host = get_host(opts.clone)
|
|
return host unless host.nil?
|
|
|
|
report_host(opts)
|
|
end
|
|
|
|
def find_host_by_address_or_id(opts, wspace)
|
|
# Find the host entry in the current workspace by searching on
|
|
# the ID if available. If this isn't possible then try search on
|
|
# the IP address of the host we are currently processing, then save the database
|
|
# entry into the "host" variable.
|
|
if opts[:id]
|
|
host = wspace.hosts.find(opts[:id])
|
|
elsif opts[:address]
|
|
host = wspace.hosts.find_by_address(opts[:address])
|
|
else
|
|
raise ::ArgumentError, 'opts hash did not contain an :id or :address entry!'
|
|
end
|
|
|
|
host
|
|
end
|
|
|
|
def add_host_tag(opts)
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
tag_name = opts[:tag_name] # This will be the string of the tag that we are using.
|
|
|
|
host = find_host_by_address_or_id(opts, wspace)
|
|
|
|
# If a host was found
|
|
if host
|
|
# Set host_id to the ID of the host entry in the database that was found.
|
|
host_id = host[:id]
|
|
|
|
# Then proceed to go ahead and find potential tags that might have been already
|
|
# created that match the one we are trying to add.
|
|
possible_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.id = ? and tags.name = ?", wspace.id, host_id, tag_name).order("tags.id DESC").limit(1)
|
|
|
|
# If one exists, then use it, otherwise create a new Mdm::Tag, and update
|
|
# the data in the database if the entry was found to need updating (aka the tag
|
|
# hasn't already been applied).
|
|
# @type [Mdm::Tag]
|
|
tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
|
|
tag.name = tag_name
|
|
tag.hosts = [host]
|
|
tag.save! if tag.changed?
|
|
tag
|
|
end
|
|
end
|
|
|
|
#@todo This will have to be pulled out if tags are used for more than just hosts
|
|
# ATM it will delete the tag from the tag table, not the host<->tag link
|
|
def delete_host_tag(opts)
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
tag_name = opts[:tag_name]
|
|
tag_ids = []
|
|
|
|
# If the command line included an address or address range then use this.
|
|
# Otherwise delete all entries that match the given tag.
|
|
host = find_host_by_address_or_id(opts, wspace)
|
|
if host
|
|
found_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.id = ? and tags.name = ?", wspace.id, host.id, tag_name)
|
|
found_tags.each do |t|
|
|
tag_ids << t.id
|
|
end
|
|
|
|
deleted_tags = []
|
|
|
|
tag_ids.each do |id|
|
|
tag = Mdm::Tag.find_by_id(id)
|
|
deleted_tags << tag
|
|
tag.destroy
|
|
end
|
|
|
|
return deleted_tags
|
|
end
|
|
end
|
|
|
|
def get_host_tags(opts)
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
host_id = opts[:id]
|
|
|
|
host = wspace.hosts.find(host_id)
|
|
if host
|
|
host.tags
|
|
end
|
|
end
|
|
|
|
#
|
|
# Find a host. Performs no database writes.
|
|
#
|
|
def get_host(opts)
|
|
if opts.kind_of? ::Mdm::Host
|
|
return opts
|
|
elsif opts.kind_of? String
|
|
raise RuntimeError, "This invocation of get_host is no longer supported: #{caller}"
|
|
else
|
|
address = opts[:addr] || opts[:address] || opts[:host] || return
|
|
return address if address.kind_of? ::Mdm::Host
|
|
end
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
|
|
address = Msf::Util::Host.normalize_host(address)
|
|
return wspace.hosts.find_by_address(address)
|
|
}
|
|
end
|
|
|
|
# Returns a list of all hosts in the database
|
|
def hosts(opts)
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
# If we have the ID, there is no point in creating a complex query.
|
|
if opts[:id] && !opts[:id].to_s.empty?
|
|
return Array.wrap(Mdm::Host.find(opts[:id]))
|
|
end
|
|
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
|
|
conditions = {}
|
|
conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if opts[:non_dead]
|
|
conditions[:address] = opts[:address] if opts[:address] && !opts[:address].empty?
|
|
|
|
if opts[:search_term] && !opts[:search_term].empty?
|
|
column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Host, opts[:search_term])
|
|
tag_conditions = Arel::Nodes::Regexp.new(Mdm::Tag.arel_table[:name], Arel::Nodes.build_quoted("(?mi)#{opts[:search_term]}"))
|
|
search_conditions = column_search_conditions.or(tag_conditions)
|
|
wspace.hosts.where(conditions).where(search_conditions).includes(:tags).references(:tags).order(:address)
|
|
else
|
|
wspace.hosts.where(conditions).order(:address)
|
|
end
|
|
}
|
|
end
|
|
|
|
def host_state_changed(host, ostate)
|
|
begin
|
|
framework.events.on_db_host_state(host, ostate)
|
|
rescue ::Exception => e
|
|
wlog("Exception in on_db_host_state event handler: #{e.class}: #{e}")
|
|
wlog("Call Stack\n#{e.backtrace.join("\n")}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Report a host's attributes such as operating system and service pack
|
|
#
|
|
# The opts parameter MUST contain
|
|
# +:host+:: -- the host's ip address
|
|
#
|
|
# The opts parameter can contain:
|
|
# +:state+:: -- one of the Msf::HostState constants
|
|
# +:os_name+:: -- something like "Windows", "Linux", or "Mac OS X"
|
|
# +:os_flavor+:: -- something like "Enterprise", "Pro", or "Home"
|
|
# +:os_sp+:: -- something like "SP2"
|
|
# +:os_lang+:: -- something like "English", "French", or "en-US"
|
|
# +:arch+:: -- one of the ARCHITECTURES listed in metasploit_data_models/app/models/mdm/host.rb
|
|
# +:mac+:: -- the host's MAC address
|
|
# +:scope+:: -- interface identifier for link-local IPv6
|
|
# +:virtual_host+:: -- the name of the virtualization software, eg "VMWare", "QEMU", "Xen", "Docker", etc.
|
|
#
|
|
def report_host(opts)
|
|
|
|
return if !active
|
|
addr = opts.delete(:host) || return
|
|
|
|
# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
|
|
if addr.eql? "Remote Pipe"
|
|
return
|
|
end
|
|
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
|
opts = opts.clone
|
|
opts.delete(:workspace)
|
|
|
|
begin
|
|
retry_attempts ||= 0
|
|
if !addr.kind_of? ::Mdm::Host
|
|
original_addr = addr
|
|
addr = Msf::Util::Host.normalize_host(original_addr)
|
|
|
|
unless ipv46_validator(addr)
|
|
raise ::ArgumentError, "Invalid IP address in report_host(): #{original_addr}"
|
|
end
|
|
|
|
conditions = {address: addr}
|
|
conditions[:comm] = opts[:comm] if !opts[:comm].nil? && opts[:comm].length > 0
|
|
host = wspace.hosts.where(conditions).first_or_initialize
|
|
else
|
|
host = addr
|
|
end
|
|
|
|
ostate = host.state
|
|
|
|
# Truncate the info field at the maximum field length
|
|
if opts[:info]
|
|
opts[:info] = opts[:info][0,65535]
|
|
end
|
|
|
|
# Truncate the name field at the maximum field length
|
|
if opts[:name]
|
|
opts[:name] = opts[:name][0,255]
|
|
end
|
|
|
|
opts.each do |k,v|
|
|
if host.attribute_names.include?(k.to_s)
|
|
unless host.attribute_locked?(k.to_s)
|
|
host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '')
|
|
end
|
|
elsif !v.blank?
|
|
dlog("Unknown attribute for ::Mdm::Host: #{k}")
|
|
end
|
|
end
|
|
host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info
|
|
|
|
# Set default fields if needed
|
|
host.state = Msf::HostState::Alive if host.state.nil? || host.state.empty?
|
|
host.comm = '' unless host.comm
|
|
host.workspace = wspace unless host.workspace
|
|
|
|
begin
|
|
framework.events.on_db_host(host) if host.new_record?
|
|
rescue => e
|
|
wlog("Exception in on_db_host event handler: #{e.class}: #{e}")
|
|
wlog("Call Stack\n#{e.backtrace.join("\n")}")
|
|
end
|
|
|
|
host_state_changed(host, ostate) if host.state != ostate
|
|
|
|
if host.changed?
|
|
msf_import_timestamps(opts, host)
|
|
host.save!
|
|
end
|
|
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
|
# two concurrent report requests for a new host could result in a RecordNotUnique or
|
|
# RecordInvalid exception, simply retry the report once more as an optimistic approach
|
|
retry if (retry_attempts+=1) <= 1
|
|
raise
|
|
end
|
|
|
|
if opts[:task]
|
|
Mdm::TaskHost.create(
|
|
:task => opts[:task],
|
|
:host => host
|
|
)
|
|
end
|
|
|
|
host
|
|
}
|
|
end
|
|
|
|
def update_host(opts)
|
|
::ApplicationRecord.connection_pool.with_connection {
|
|
# process workspace string for update if included in opts
|
|
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
|
|
opts = opts.clone()
|
|
opts[:workspace] = wspace if wspace
|
|
|
|
id = opts.delete(:id)
|
|
host = Mdm::Host.find(id)
|
|
host.update!(opts)
|
|
return host
|
|
}
|
|
end
|
|
end
|