343 lines
9.6 KiB
Ruby
343 lines
9.6 KiB
Ruby
module Msf::DBManager::Host
|
|
# Deletes a host and associated data matching this address/comm
|
|
def del_host(wspace, address, comm='')
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
address, scope = address.split('%', 2)
|
|
host = wspace.hosts.find_by_address_and_comm(address, comm)
|
|
host.destroy if host
|
|
}
|
|
end
|
|
|
|
#
|
|
# Iterates over the hosts table calling the supplied block with the host
|
|
# instance of each entry.
|
|
#
|
|
def each_host(wspace=workspace, &block)
|
|
::ActiveRecord::Base.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)
|
|
report_host(opts)
|
|
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 invokation 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
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
wspace = opts.delete(:workspace) || workspace
|
|
if wspace.kind_of? String
|
|
wspace = find_workspace(wspace)
|
|
end
|
|
|
|
address = normalize_host(address)
|
|
return wspace.hosts.find_by_address(address)
|
|
}
|
|
end
|
|
|
|
# Look for an address across all comms
|
|
def has_host?(wspace,addr)
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
address, scope = addr.split('%', 2)
|
|
wspace.hosts.find_by_address(addr)
|
|
}
|
|
end
|
|
|
|
# Returns a list of all hosts in the database
|
|
def hosts(wspace = workspace, only_up = false, addresses = nil)
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
conditions = {}
|
|
conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
|
|
conditions[:address] = addresses if addresses
|
|
wspace.hosts.where(conditions).order(:address)
|
|
}
|
|
end
|
|
|
|
#
|
|
# Returns something suitable for the +:host+ parameter to the various report_* methods
|
|
#
|
|
# Takes a Host object, a Session object, an Msf::Session object or a String
|
|
# address
|
|
#
|
|
def normalize_host(host)
|
|
return host if defined?(::Mdm) && host.kind_of?(::Mdm::Host)
|
|
norm_host = nil
|
|
|
|
if (host.kind_of? String)
|
|
|
|
if Rex::Socket.is_ipv4?(host)
|
|
# If it's an IPv4 addr with a port on the end, strip the port
|
|
if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/
|
|
norm_host = $1
|
|
else
|
|
norm_host = host
|
|
end
|
|
elsif Rex::Socket.is_ipv6?(host)
|
|
# If it's an IPv6 addr, drop the scope
|
|
address, scope = host.split('%', 2)
|
|
norm_host = address
|
|
else
|
|
norm_host = Rex::Socket.getaddress(host, true)
|
|
end
|
|
elsif defined?(::Mdm) && host.kind_of?(::Mdm::Session)
|
|
norm_host = host.host
|
|
elsif host.respond_to?(:session_host)
|
|
# Then it's an Msf::Session object
|
|
norm_host = host.session_host
|
|
end
|
|
|
|
# If we got here and don't have a norm_host yet, it could be a
|
|
# Msf::Session object with an empty or nil tunnel_host and tunnel_peer;
|
|
# see if it has a socket and use its peerhost if so.
|
|
if (
|
|
norm_host.nil? &&
|
|
host.respond_to?(:sock) &&
|
|
host.sock.respond_to?(:peerhost) &&
|
|
host.sock.peerhost.to_s.length > 0
|
|
)
|
|
norm_host = session.sock.peerhost
|
|
end
|
|
# If We got here and still don't have a real host, there's nothing left
|
|
# to try, just log it and return what we were given
|
|
if !norm_host
|
|
dlog("Host could not be normalized: #{host.inspect}")
|
|
norm_host = host
|
|
end
|
|
|
|
norm_host
|
|
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 ARCH_* constants
|
|
# +:mac+:: -- the host's MAC address
|
|
# +:scope+:: -- interface identifier for link-local IPv6
|
|
# +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", 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
|
|
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
wspace = opts.delete(:workspace) || workspace
|
|
if wspace.kind_of? String
|
|
wspace = find_workspace(wspace)
|
|
end
|
|
|
|
ret = { }
|
|
|
|
if !addr.kind_of? ::Mdm::Host
|
|
addr = normalize_host(addr)
|
|
|
|
unless ipv46_validator(addr)
|
|
raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
|
|
end
|
|
|
|
if opts[:comm] and opts[:comm].length > 0
|
|
host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize
|
|
else
|
|
host = wspace.hosts.where(address: addr).first_or_initialize
|
|
end
|
|
else
|
|
host = addr
|
|
end
|
|
|
|
# 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
|
|
|
|
if opts[:os_name]
|
|
os_name, os_flavor = split_windows_os_name(opts[:os_name])
|
|
opts[:os_name] = os_name if os_name.present?
|
|
if opts[:os_flavor].present?
|
|
opts[:os_flavor] = os_flavor + opts[:os_flavor]
|
|
else
|
|
opts[:os_flavor] = os_flavor
|
|
end
|
|
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
|
|
host.comm = '' if !host.comm
|
|
host.workspace = wspace if !host.workspace
|
|
|
|
if host.changed?
|
|
msf_import_timestamps(opts,host)
|
|
host.save!
|
|
end
|
|
|
|
if opts[:task]
|
|
Mdm::TaskHost.create(
|
|
:task => opts[:task],
|
|
:host => host
|
|
)
|
|
end
|
|
|
|
host
|
|
}
|
|
end
|
|
|
|
def split_windows_os_name(os_name)
|
|
return [] if os_name.nil?
|
|
flavor_match = os_name.match(/Windows\s+(.*)/)
|
|
return [] if flavor_match.nil?
|
|
["Windows", flavor_match.captures.first]
|
|
end
|
|
|
|
#
|
|
# Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
|
|
#
|
|
# The opts parameter MUST contain the following entries
|
|
# +:host+:: -- the host's ip address
|
|
# +:info+:: -- the information hash
|
|
# * 'Computer' -- the host name
|
|
# * 'OS' -- the operating system string
|
|
# * 'Architecture' -- the hardware architecture
|
|
# * 'System Language' -- the system language
|
|
#
|
|
# The opts parameter can contain:
|
|
# +:workspace+:: -- the workspace for this host
|
|
#
|
|
def update_host_via_sysinfo(opts)
|
|
|
|
return if !active
|
|
addr = opts.delete(:host) || return
|
|
info = opts.delete(:info) || return
|
|
|
|
# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
|
|
if addr.eql? "Remote Pipe"
|
|
return
|
|
end
|
|
|
|
::ActiveRecord::Base.connection_pool.with_connection {
|
|
wspace = opts.delete(:workspace) || workspace
|
|
if wspace.kind_of? String
|
|
wspace = find_workspace(wspace)
|
|
end
|
|
|
|
if !addr.kind_of? ::Mdm::Host
|
|
addr = normalize_host(addr)
|
|
addr, scope = addr.split('%', 2)
|
|
opts[:scope] = scope if scope
|
|
|
|
unless ipv46_validator(addr)
|
|
raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
|
|
end
|
|
|
|
if opts[:comm] and opts[:comm].length > 0
|
|
host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize
|
|
else
|
|
host = wspace.hosts.where(address: addr).first_or_initialize
|
|
end
|
|
else
|
|
host = addr
|
|
end
|
|
|
|
res = {}
|
|
|
|
if info['Computer']
|
|
res[:name] = info['Computer']
|
|
end
|
|
|
|
if info['Architecture']
|
|
res[:arch] = info['Architecture'].split(/\s+/).first
|
|
end
|
|
|
|
if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
|
|
res[:os_name] = "Windows"
|
|
res[:os_flavor] = $1.strip
|
|
build = $2.strip
|
|
|
|
if build =~ /Service Pack (\d+)/
|
|
res[:os_sp] = "SP" + $1
|
|
end
|
|
end
|
|
|
|
if info["System Language"]
|
|
case info["System Language"]
|
|
when /^en_/
|
|
res[:os_lang] = "English"
|
|
end
|
|
end
|
|
|
|
|
|
# Truncate the info field at the maximum field length
|
|
if res[:info]
|
|
res[:info] = res[:info][0,65535]
|
|
end
|
|
|
|
# Truncate the name field at the maximum field length
|
|
if res[:name]
|
|
res[:name] = res[:name][0,255]
|
|
end
|
|
|
|
res.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 Host: #{k}")
|
|
end
|
|
end
|
|
|
|
# Set default fields if needed
|
|
host.state = Msf::HostState::Alive if !host.state
|
|
host.comm = '' if !host.comm
|
|
host.workspace = wspace if !host.workspace
|
|
|
|
if host.changed?
|
|
host.save!
|
|
end
|
|
|
|
host
|
|
}
|
|
end
|
|
end
|