metasploit-framework/lib/msf/core/db.rb

711 lines
14 KiB
Ruby

module Msf
###
#
# The states that a host can be in.
#
###
module HostState
#
# The host is alive.
#
Alive = "alive"
#
# The host is dead.
#
Dead = "down"
#
# The host state is unknown.
#
Unknown = "unknown"
end
###
#
# The states that a service can be in.
#
###
module ServiceState
#
# The service is alive.
#
Up = "up"
#
# The service is dead.
#
Dead = "down"
#
# The service state is unknown.
#
Unknown = "unknown"
end
###
#
# Events that can occur in the host/service database.
#
###
module DatabaseEvent
#
# Called when an existing host's state changes
#
def on_db_host_state(context, host, ostate)
end
#
# Called when an existing service's state changes
#
def on_db_service_state(context, host, port, ostate)
end
#
# Called when a new host is added to the database. The host parameter is
# of type Host.
#
def on_db_host(context, host)
end
#
# Called when a new client is added to the database. The client
# parameter is of type Client.
#
def on_db_client(context, client)
end
#
# Called when a new service is added to the database. The service
# parameter is of type Service.
#
def on_db_service(context, service)
end
#
# Called when an applicable vulnerability is found for a service. The vuln
# parameter is of type Vuln.
#
def on_db_vuln(context, vuln)
end
#
# Called when a new reference is created.
#
def on_db_ref(context, ref)
end
end
###
#
# The DB module ActiveRecord definitions for the DBManager
#
###
class DBManager
#
# Determines if the database is functional
#
def check
res = Host.find(:all)
end
#
# Reports a host as being in a given state by address.
#
def report_host_state(mod, addr, state, context = nil)
# TODO: use the current thread's Comm to find the host
comm = ''
host = get_host(context, addr, comm)
ostate = host.state
host.state = state
host.save
framework.events.on_db_host_state(context, host, ostate)
return host
end
#
# Report a host's attributes such as operating system and service pack
#
# At the time of this writing, the opts parameter can contain:
# :state -- one of the Msf::HostState constants
# :os_name -- one of the Msf::Auxiliary::Report::OperatingSystems constants
# :os_flavor -- something like "XP" or "Gentoo"
# :os_sp -- something like "SP2"
# :os_lang -- something like "English" or "French"
# :arch -- one of the ARCH_* constants
#
# See <MSF install dir>/data/sql/*.sql for more info
#
def report_host(mod, addr, opts = {}, context = nil)
report_host_state(mod, addr, opts[:state] || Msf::HostState::Alive)
opts.delete(:state)
host = get_host(context, addr, '')
opts.each { |k,v|
if (host.attribute_names.include?(k.to_s))
host[k] = v
else
dlog("Unknown attribute for Host: #{k}")
end
}
host.save
return host
end
#
# Report a client running on a host.
#
# opts must contain :ua_string
# opts can contain :ua_name and :ua_ver
#
def report_client(mod, addr, opts = {}, context = nil)
if opts[:ua_string].nil?
elog("report_client requires a ua_string", 'db', LEV_0, caller)
return
end
# TODO: use the current thread's Comm to find the host
comm = ''
host = get_host(context, addr, comm)
cli = get_client(context, host, opts[:ua_string])
opts.each { |k,v|
if (cli.attribute_names.include?(k.to_s))
cli[k] = v
else
dlog("Unknown attribute for Client: #{k}")
end
}
cli.save
return cli
end
#
# This method reports a host's service state.
#
def report_service_state(mod, addr, proto, port, state, context = nil)
# TODO: use the current thread's Comm to find the host
comm = ''
host = get_host(context, addr, comm)
port = get_service(context, host, proto, port, state)
ostate = port.state
port.state = state
port.save
if (ostate != state)
framework.events.on_db_service_state(context, host, port, ostate)
end
return port
end
def workspaces
Workspace.find(:all)
end
#
# This method iterates the hosts table calling the supplied block with the
# host instance of each entry.
#
def each_host(&block)
workspace.hosts.each do |host|
block.call(host)
end
end
#
# This methods returns a list of all hosts in the database
#
def hosts(only_up = false, addresses = nil)
conditions = {}
conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
conditions[:address] = addresses if addresses
workspace.hosts.all(:conditions => conditions, :order => :address)
end
#
# This method iterates the services table calling the supplied block with the
# service instance of each entry.
#
def each_service(&block)
services.each do |service|
block.call(service)
end
end
#
# This methods returns a list of all services in the database
#
def services(only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
conditions = {}
conditions[:state] = [Msf::ServiceState::Up, Msf::ServiceState::Unknown] if only_up
conditions[:proto] = proto if proto
conditions["hosts.address"] = addresses if addresses
conditions[:port] = ports if ports
conditions[:name] = names if names
workspace.services.all(:include => :host, :conditions => conditions, :order => "hosts.address, port")
end
#
# This method iterates the vulns table calling the supplied block with the
# vuln instance of each entry.
#
def each_vuln(&block)
workspace.vulns.each do |vulns|
block.call(vulns)
end
end
#
# This methods returns a list of all vulnerabilities in the database
#
def vulns
workspace.vulns
end
#
# This method iterates the notes table calling the supplied block with the
# note instance of each entry.
#
def each_note(&block)
workspace.notes.each do |note|
block.call(note)
end
end
#
# Find a note matching this host address and note type
#
def find_note(address, ntype)
host = workspace.hosts.find_by_address(address)
return nil if host.nil?
host.notes.find_by_ntype(ntype)
end
#
# This methods returns a list of all notes in the database
#
def notes
workspace.notes
end
def default_workspace
Workspace.default
end
def find_workspace(name)
Workspace.find_by_name(name)
end
#
# Creates a new workspace in the database
#
def add_workspace(context, name)
Workspace.find_or_create_by_name(name)
end
#
# Find or create a host matching this address/comm
#
def get_host(context, address, comm='')
if comm.length > 0
host = workspace.hosts.find_by_address_and_comm(address, comm)
else
host = workspace.hosts.find_by_address(address)
end
if (not host)
host = workspace.hosts.create(
:address => address,
:comm => comm,
:state => HostState::Unknown,
:created => Time.now)
framework.events.on_db_host(context, host)
end
return host
end
#
# Find or create a client on host that matches ua_string
#
def get_client(context, host, ua_string, comm='')
# Allow host to be an address to look up
if !host.kind_of? Host
host = get_host(context, host, comm)
end
return nil if host.nil?
rec = host.clients.find_by_ua_string(ua_string)
if (not rec)
rec = host.clients.create(
:ua_string => ua_string,
:created => Time.now
)
framework.events.on_db_client(context, rec)
end
return rec
end
#
# Find or create a service matching this host/proto/port/state
#
def get_service(context, host, proto, port, state=ServiceState::Up)
rec = host.services.find_by_proto_and_port(proto, port)
if (not rec)
rec = host.services.create(
:proto => proto,
:port => port,
:state => state,
:created => Time.now
)
framework.events.on_db_service(context, rec)
end
return rec
end
#
# Find or create a vuln matching this service/name
#
def get_vuln(context, host, service, name, data='')
vuln = nil
if (service)
vuln = Vuln.find(:first, :conditions => [ "name = ? and service_id = ? and host_id = ?", name, service.id, host.id])
else
vuln = Vuln.find(:first, :conditions => [ "name = ? and host_id = ?", name, host.id])
end
if (not vuln)
vuln = Vuln.create(
:service_id => service ? service.id : 0,
:host_id => host.id,
:name => name,
:data => data,
:created => Time.now
)
framework.events.on_db_vuln(context, vuln)
end
return vuln
end
#
# Find or create a reference matching this name
#
def get_ref(context, name)
ref = Ref.find_by_name(name)
if (not ref)
ref = Ref.create(
:name => name,
:created => Time.now
)
framework.events.on_db_ref(context, ref)
end
return ref
end
#
# Find or create a note matching this type/data
#
def get_note(context, host, ntype, data)
rec = host.notes.find_by_ntype_and_data(ntype, data)
if (not rec)
rec = host.notes.create(
:ntype => ntype,
:data => data,
:created => Time.now
)
framework.events.on_db_note(context, rec)
end
return rec
end
#
# Deletes a host and associated data matching this address/comm
#
def del_host(context, address, comm='')
host = workspace.hosts.find_by_address_and_comm(address, comm)
host.destroy if host
end
#
# Deletes a port and associated vulns matching this port
#
def del_service(context, address, proto, port, comm='')
host = get_host(context, address, comm)
return unless host
host.services.find(:all, :conditions => { :proto => proto, :port => port}).destroy_all
end
#
# Find a reference matching this name
#
def has_ref?(name)
Ref.find_by_name(name)
end
#
# Find a vulnerability matching this name
#
def has_vuln?(name)
Vuln.find_by_name(name)
end
#
# Look for an address across all comms
#
def has_host?(addr)
workspace.hosts.find_by_address(addr)
end
def vuln_add_refs(context, vuln, refs)
return vuln if not refs
rids = refs.map{|r| get_ref(context, "#{r[0]}-#{r[1]}") }
vuln.refs << rids
vuln
end
#
# WMAP
# Support methods
#
#
# WMAP
# Selected host
#
def selected_host
selhost = WmapTarget.find(:first, :conditions => ["selected != 0"] )
if selhost
return selhost.host
else
return
end
end
#
# WMAP
# Selected port
#
def selected_port
WmapTarget.find(:first, :conditions => ["selected != 0"] ).port
end
#
# WMAP
# Selected ssl
#
def selected_ssl
WmapTarget.find(:first, :conditions => ["selected != 0"] ).ssl
end
#
# WMAP
# Selected id
#
def selected_id
WmapTarget.find(:first, :conditions => ["selected != 0"] ).object_id
end
#
# WMAP
# This method iterates the requests table identifiying possible targets
# This method wiil be remove on second phase of db merging.
#
def each_distinct_target(&block)
request_distinct_targets.each do |target|
block.call(target)
end
end
#
# WMAP
# This method returns a list of all possible targets available in requests
# This method wiil be remove on second phase of db merging.
#
def request_distinct_targets
WmapRequest.find(:all, :select => 'DISTINCT host,address,port,ssl')
end
#
# WMAP
# This method iterates the requests table returning a list of all requests of a specific target
#
def each_request_target_with_path(&block)
target_requests('AND wmap_requests.path IS NOT NULL').each do |req|
block.call(req)
end
end
#
# WMAP
# This method iterates the requests table returning a list of all requests of a specific target
#
def each_request_target_with_query(&block)
target_requests('AND wmap_requests.query IS NOT NULL').each do |req|
block.call(req)
end
end
#
# WMAP
# This method iterates the requests table returning a list of all requests of a specific target
#
def each_request_target_with_body(&block)
target_requests('AND wmap_requests.body IS NOT NULL').each do |req|
block.call(req)
end
end
#
# WMAP
# This method iterates the requests table returning a list of all requests of a specific target
#
def each_request_target_with_headers(&block)
target_requests('AND wmap_requests.headers IS NOT NULL').each do |req|
block.call(req)
end
end
#
# WMAP
# This method iterates the requests table returning a list of all requests of a specific target
#
def each_request_target(&block)
target_requests('').each do |req|
block.call(req)
end
end
#
# WMAP
# This method returns a list of all requests from target
#
def target_requests(extra_condition)
WmapRequest.find(:all, :conditions => ["wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port])
end
#
# WMAP
# This method iterates the requests table calling the supplied block with the
# request instance of each entry.
#
def each_request(&block)
requests.each do |request|
block.call(request)
end
end
#
# WMAP
# This method allows to query directly the requests table. To be used mainly by modules
#
def request_sql(host,port,extra_condition)
WmapRequest.find(:all, :conditions => ["wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",host,port])
end
#
# WMAP
# This methods returns a list of all targets in the database
#
def requests
WmapRequest.find(:all)
end
#
# WMAP
# This method iterates the targets table calling the supplied block with the
# target instance of each entry.
#
def each_target(&block)
targets.each do |target|
block.call(target)
end
end
#
# WMAP
# This methods returns a list of all targets in the database
#
def targets
WmapTarget.find(:all)
end
#
# WMAP
# This methods deletes all targets from targets table in the database
#
def delete_all_targets
WmapTarget.delete_all
end
#
# WMAP
# Find a target matching this id
#
def get_target(id)
target = WmapTarget.find(:first, :conditions => [ "id = ?", id])
return target
end
#
# WMAP
# Create a target
#
def create_target(host,port,ssl,sel)
tar = WmapTarget.create(
:host => host,
:address => host,
:port => port,
:ssl => ssl,
:selected => sel
)
#framework.events.on_db_target(context, rec)
end
#
# WMAP
# Create a request (by hand)
#
def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response)
req = WmapRequest.create(
:host => host,
:address => host,
:port => port,
:ssl => ssl,
:meth => meth,
:path => path,
:headers => headers,
:query => query,
:body => body,
:respcode => respcode,
:resphead => resphead,
:response => response,
:created => Time.now
)
#framework.events.on_db_request(context, rec)
end
#
# WMAP
# Quick way to query the database (used by wmap_sql)
#
def sql_query(sqlquery)
ActiveRecord::Base.connection.select_all(sqlquery)
end
end
end