Files
metasploit-gs/lib/msf/core/db_manager/host.rb
T
Spencer McIntyre e28ee9ca53 Improve an error message when addr is nil
The normalized value can be nil when the hostname failed to resolve.
That is not helpful in the exception information, so use the original
value.
2022-03-15 14:01:26 -04:00

286 lines
9.0 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 add_host_tag(opts)
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
host_id = opts[:id]
tag_name = opts[:tag_name]
host = wspace.hosts.find(host_id)
if host
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)
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)
host_id = opts[:id]
tag_name = opts[:tag_name]
tag_ids = []
host = wspace.hosts.find(host_id)
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
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