e28ee9ca53
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.
286 lines
9.0 KiB
Ruby
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
|