diff --git a/data/sql/migrate/20100824151500_add_exploited_table.rb b/data/sql/migrate/20100824151500_add_exploited_table.rb new file mode 100644 index 0000000000..b7897d3832 --- /dev/null +++ b/data/sql/migrate/20100824151500_add_exploited_table.rb @@ -0,0 +1,16 @@ +class AddExploitedTable < ActiveRecord::Migration + def self.up + create_table :exploited_hosts do |t| + t.integer :host_id, :null => false + t.integer :service_id + t.string :session_uuid, :limit => 8 + t.string :name, :limit => 2048 + t.string :payload, :limit => 2048 + t.timestamps + end + end + def self.down + drop_table :exploited_hosts + end +end + diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 5c46f075d9..9fa73b2303 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -453,6 +453,13 @@ class DBManager ) end + # + # This method returns a list of all exploited hosts in the database. + # + def exploited_hosts(wspace=workspace) + wspace.exploited_hosts + end + # # This method iterates the notes table calling the supplied block with the # note instance of each entry. @@ -685,6 +692,12 @@ class DBManager end end + def each_exploited_host(wspace=workspace,&block) + wspace.exploited_hosts.each do |eh| + block.call(eh) + end + end + # # Find or create a vuln matching this service/name # @@ -802,6 +815,70 @@ class DBManager Ref.find_by_name(name) end + def report_exploit(opts={}) + return if not active + raise ArgumentError.new("Missing required option :host") if opts[:host].nil? + wait = opts[:wait] + wspace = opts.delete(:workspace) || workspace + host = nil + addr = nil + sname = opts.delete(:sname) + port = opts.delete(:port) + proto = opts.delete(:proto) || "tcp" + name = opts.delete(:name) + payload = opts.delete(:payload) + session_uuid = opts.delete(:session_uuid) + + if opts[:host].kind_of? Host + host = opts[:host] + else + report_host({:workspace => wspace, :host => opts[:host]}) + addr = opts[:host] + end + + if opts[:service].kind_of? Service + service = opts[:service] + elsif port + report_service(:host => host, :port => port, :proto => proto, :name => sname) + service = get_service(wspace, host, proto, port) + else + service = nil + end + + ret = {} + + task = queue( + Proc.new { + if host + host.updated_at = host.created_at + host.state = HostState::Alive + host.save! + else + host = get_host(:workspace => wspace, :address => addr) + end + exploit_info = { + :workspace => wspace, + :host_id => host.id, + :name => name, + :payload => payload, + } + exploit_info[:service_id] = service.id if service + exploit_info[:session_uuid] = session_uuid if session_uuid + exploit_record = ExploitedHost.create(exploit_info) + exploit_record.save! + + ret[:exploit] = exploit_record + } + ) + + if wait + return nil if task.wait() != :done + return ret[:exploit] + end + return task + + end + # # Deletes a host and associated data matching this address/comm diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index aa95c8b76c..bda6d5a1f3 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -318,14 +318,32 @@ class FrameworkEventSubscriber # If the exploit used was multi/handler, though, we don't know what # it's vulnerable to, so it isn't really useful to save it. if session.via_exploit and session.via_exploit != "exploit/multi/handler" + wspace = framework.db.find_workspace(session.workspace) + host = wspace.hosts.find_by_address(address) + port = session.exploit_datastore["RPORT"] + service = (port ? host.services.find_by_port(port) : nil) mod = framework.modules.create(session.via_exploit) - info = { - :host => address, + vuln_info = { + :host => host.address, :name => session.via_exploit, :refs => mod.references, - :workspace => framework.db.find_workspace(session.workspace) + :workspace => wspace } - framework.db.report_vuln(info) + framework.db.report_vuln(vuln_info) + # Exploit info is like vuln info, except it's /just/ for storing + # successful exploits in an unserialized way. Yes, there is + # duplication, but it makes exporting a score card about a + # million times easier. TODO: See if vuln/exploit can get fixed up + # to one useful table. + exploit_info = { + :name => session.via_exploit, + :payload => session.via_payload, + :workspace => wspace, + :host => host, + :service => service, + :session_uuid => session.uuid + } + ret = framework.db.report_exploit(exploit_info) end end end diff --git a/lib/msf/core/model.rb b/lib/msf/core/model.rb index 205676643b..52322dcead 100644 --- a/lib/msf/core/model.rb +++ b/lib/msf/core/model.rb @@ -13,6 +13,7 @@ require 'msf/core/model/service' require 'msf/core/model/workspace' require 'msf/core/model/vuln' require 'msf/core/model/cred' +require 'msf/core/model/exploited_host' require 'msf/core/model/wmap_target' require 'msf/core/model/wmap_request' diff --git a/lib/msf/core/model/exploited_host.rb b/lib/msf/core/model/exploited_host.rb new file mode 100644 index 0000000000..fab647cac2 --- /dev/null +++ b/lib/msf/core/model/exploited_host.rb @@ -0,0 +1,12 @@ +module Msf +class DBManager + +class ExploitedHost < ActiveRecord::Base + include DBSave + belongs_to :host + belongs_to :service + belongs_to :workspace +end + +end +end diff --git a/lib/msf/core/model/host.rb b/lib/msf/core/model/host.rb index a000b0a817..f7a2e58492 100644 --- a/lib/msf/core/model/host.rb +++ b/lib/msf/core/model/host.rb @@ -13,6 +13,7 @@ class Host < ActiveRecord::Base has_many :service_notes, :through => :services has_many :creds, :through => :services + has_many :exploited_hosts, :dependent => :destroy validates_exclusion_of :address, :in => ['127.0.0.1'] validates_uniqueness_of :address, :scope => :workspace_id diff --git a/lib/msf/core/model/service.rb b/lib/msf/core/model/service.rb index 6ad45df876..23355c1e0f 100644 --- a/lib/msf/core/model/service.rb +++ b/lib/msf/core/model/service.rb @@ -6,6 +6,7 @@ class Service < ActiveRecord::Base has_many :vulns, :dependent => :destroy has_many :notes, :dependent => :destroy has_many :creds, :dependent => :destroy + has_many :exploited_hosts, :dependent => :destroy belongs_to :host serialize :info diff --git a/lib/msf/core/model/workspace.rb b/lib/msf/core/model/workspace.rb index 24354f5848..cbd92b09ab 100644 --- a/lib/msf/core/model/workspace.rb +++ b/lib/msf/core/model/workspace.rb @@ -17,6 +17,7 @@ class Workspace < ActiveRecord::Base has_many :clients, :through => :hosts has_many :vulns, :through => :hosts has_many :creds, :dependent => :destroy + has_many :exploited_hosts, :through => :hosts #has_many :notes, :through => :hosts diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 619b1b7dd2..9141619af0 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -53,6 +53,7 @@ class Db "db_vulns" => "List all vulnerabilities in the database", "db_notes" => "List all notes in the database", "db_creds" => "List all credentials in the database", + "db_exploited" => "List all exploited hosts in the database", "db_add_host" => "Add one or more hosts to the database", "db_add_port" => "Add a port to a host", "db_add_note" => "Add a note to a host", @@ -425,6 +426,52 @@ class Db print_status "Found #{creds_returned} credential#{creds_returned == 1 ? "" : "s"}." end + # Returns exploited hosts. Takes a similiar set of options as db_creds + def cmd_db_exploited(*args) + return unless active? + if args.size > 1 + print_status "Usage: db_exploited [host=1.2.3.4/24|port=1-1024|service=ssh,smb,etc]" + print_status " Note, only one of host, port, or service can be used at a time." + return + end + search_term = nil + search_param = nil + exploited_returned = 0 + if args[0] =~ /^[\s]*(host|port|service)=(.*)/i + search_term = $1.downcase + search_param = $2.downcase + end + framework.db.each_exploited_host(framework.db.workspace) do |eh| + case search_term + when "host" + begin + rw = Rex::Socket::RangeWalker.new(search_param) + next unless rw.include? eh.host.address + rescue + print_error "Invalid host parameter." + break + end + when "port" + if search_param =~ /([0-9]+)-([0-9]+)/ + ports = Range.new($1,$2) + else + ports = Range.new(search_param,search_param) + end + next unless ports.include? eh.service.port.to_s + when "service" + svcs = search_param.split(/[\s]*,[\s]*/) + next unless svcs.include? eh.service.name + end + if eh.service + print_status("Time: #{eh.updated_at} Host Info: host=#{eh.host.address} port=#{eh.service.port} proto=#{eh.service.proto} sname=#{eh.service.name} exploit=#{eh.name}") + else + print_status("Time: #{eh.updated_at} Host Info: host=#{eh.host.address} exploit=#{eh.name}") + end + exploited_returned += 1 + end + print_status "Found #{exploited_returned} exploited host#{exploited_returned == 1 ? "" : "s"}." + end + def cmd_db_notes(*args) return unless active? hosts = nil