Land #11126, Update sessions through the DBManager

GSoC/Meterpreter_Web_Console
Brent Cook 2018-12-26 13:15:43 -06:00
commit 0d0356ccdd
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
10 changed files with 138 additions and 67 deletions

View File

@ -20,6 +20,96 @@ module SessionDataProxy
self.log_error(e, "Problem reporting session")
end
end
# Update the attributes of a session entry using opts.
# If opts is a Hash, the values should match the attributes to update and must contain :id.
# If opts is a Msf::Session object, it is converted to a Hash and used for the update.
# The db_record attribute of the Msf::Session object is updated using the returned Mdm::Session.
#
# @param opts [Hash|Msf::Session] Hash containing the updated values. Key should match the attribute to update.
# Must contain :id of record to update. Otherwise, a Msf::Session object is used to update all attributes.
# @return [Mdm::Session] The updated Mdm::Session object.
def update_session(opts)
begin
self.data_service_operation do |data_service|
is_msf_session = false
if !opts.nil? && opts.kind_of?(Msf::Session)
msf_session = opts
is_msf_session = true
tmp_opts = SessionDataProxy.convert_msf_session_to_hash(msf_session)
# only updating session data
opts = tmp_opts[:session_data]
# add back session ID
opts[:id] = msf_session.db_record.id
end
mdm_session = data_service.update_session(opts)
# reassign returned Mdm::Session to the Msf::Session's db_record
msf_session.db_record = mdm_session if is_msf_session
mdm_session
end
rescue => e
self.log_error(e, "Problem updating session")
end
end
# TODO: handle task info
def self.convert_msf_session_to_hash(msf_session)
hash = Hash.new()
hash[:host_data] = parse_host_opts(msf_session)
hash[:session_data] = parse_session_data(msf_session)
if (msf_session.via_exploit)
hash[:vuln_info] = parse_vuln_info(msf_session)
end
return hash
end
#######
private
#######
def self.parse_session_data(msf_session)
hash = Hash.new()
# TODO: what to do with this shiz
hash[:datastore] = msf_session.exploit_datastore.to_h
hash[:desc] = msf_session.info
hash[:local_id] = msf_session.sid
hash[:platform] = msf_session.session_type
hash[:port] = msf_session.session_port
hash[:stype] = msf_session.type
hash[:via_exploit] = msf_session.via_exploit
hash[:via_payload] = msf_session.via_payload
return hash
end
def self.parse_host_opts(msf_session)
hash = Hash.new()
hash[:host] = msf_session.session_host
hash[:arch] = msf_session.arch if msf_session.respond_to?(:arch) and msf_session.arch
hash[:workspace] = msf_session.workspace || msf_session[:workspace]
return hash
end
def self.parse_vuln_info(msf_session)
hash = Hash.new()
if msf_session.via_exploit == "exploit/multi/handler" and msf_session.exploit_datastore['ParentModule']
hash[:mod_fullname] = msf_session.exploit_datastore['ParentModule']
else
hash[:mod_fullname] = msf_session.via_exploit
end
hash[:remote_port] = msf_session.exploit_datastore["RPORT"]
hash[:username] = msf_session.username
hash[:run_id] = msf_session.exploit.user_data.try(:[], :run_id)
hash[:mod_name] = msf_session.exploit.name
hash[:mod_references] = msf_session.exploit.references
return hash
end
end

View File

@ -14,8 +14,7 @@ module RemoteSessionDataService
def report_session(opts)
session = opts[:session]
if (session.kind_of? Msf::Session)
opts = convert_msf_session_to_hash(session)
opts[:session_dto] = true
opts = SessionDataProxy.convert_msf_session_to_hash(session)
elsif (opts[:host])
opts[:host] = opts[:host].address
end
@ -25,60 +24,14 @@ module RemoteSessionDataService
session.db_record = sess_db
end
#######
private
#######
# TODO: handle task info
def convert_msf_session_to_hash(msf_session)
hash = Hash.new()
hash[:host_data] = parse_host_opts(msf_session)
hash[:session_data] = parse_session_data(msf_session)
if (msf_session.via_exploit)
hash[:vuln_info] = parse_vuln_info(msf_session)
def update_session(opts)
path = SESSION_API_PATH
if opts && opts[:id]
id = opts.delete(:id)
path = "#{SESSION_API_PATH}/#{id}"
end
return hash
json_to_mdm_object(self.put_data(path, opts), SESSION_MDM_CLASS, []).first
end
def parse_session_data(msf_session)
hash = Hash.new()
# TODO: what to do with this shiz
hash[:datastore] = msf_session.exploit_datastore.to_h
hash[:desc] = msf_session.info
hash[:local_id] = msf_session.sid
hash[:platform] = msf_session.session_type
hash[:port] = msf_session.session_port
hash[:stype] = msf_session.type
hash[:via_exploit] = msf_session.via_exploit
hash[:via_payload] = msf_session.via_payload
return hash
end
def parse_host_opts(msf_session)
hash = Hash.new()
hash[:host] = msf_session.session_host
hash[:arch] = msf_session.arch if msf_session.respond_to?(:arch) and msf_session.arch
hash[:workspace] = msf_session.workspace || msf_session[:workspace]
return hash
end
def parse_vuln_info(msf_session)
hash = Hash.new()
if msf_session.via_exploit == "exploit/multi/handler" and msf_session.exploit_datastore['ParentModule']
hash[:mod_fullname] = msf_session.exploit_datastore['ParentModule']
else
hash[:mod_fullname] = msf_session.via_exploit
end
hash[:remote_port] = msf_session.exploit_datastore["RPORT"]
hash[:username] = msf_session.username
hash[:run_id] = msf_session.exploit.user_data.try(:[], :run_id)
hash[:mod_name] = msf_session.exploit.name
hash[:mod_references] = msf_session.exploit.references
return hash
end
end

View File

@ -6,4 +6,8 @@ module SessionDataService
def report_session(opts)
raise 'SessionDataService#report_session is not implemented'
end
def update_session(opts)
raise 'SessionDataService#update_session is not implemented'
end
end

View File

@ -522,8 +522,7 @@ class Meterpreter < Rex::Post::Meterpreter::Client
})
if self.db_record
self.db_record.desc = safe_info
self.db_record.save!
framework.db.update_session(self)
end
# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint

View File

@ -258,9 +258,9 @@ module Msf::DBManager::Host
msf_import_timestamps(opts, host)
host.save!
end
rescue ActiveRecord::RecordNotUnique
# two concurrent report requests for a new host could result in a RecordNotUnique exception
# simply retry the report once more as an optimistic approach
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

View File

@ -178,6 +178,20 @@ module Msf::DBManager::Session
}
end
# Update the attributes of a session entry with the values in opts.
# The values in opts should match the attributes to update.
#
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Session] The updated Mdm::Session object.
def update_session(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
id = opts.delete(:id)
::Mdm::Session.update(id, opts)
}
end
# Clean out any stale sessions that have been orphaned by a dead framework instance.
# @param last_seen_interval [Integer] interval, in seconds, open sessions are marked as alive
def remove_stale_sessions(last_seen_interval)

View File

@ -285,9 +285,7 @@ module Session
def cleanup
if db_record and framework.db.active
::ActiveRecord::Base.connection_pool.with_connection {
db_record.closed_at = Time.now.utc
# ignore exceptions
db_record.save
framework.db.update_session(id: db_record.id, closed_at: Time.now.utc, close_reason: db_record.close_reason)
db_record = nil
}
end

View File

@ -119,8 +119,7 @@ class SessionManager < Hash
# as recently seen. This notifies other framework instances that this
# session is being maintained.
if s.db_record
s.db_record.last_seen = Time.now.utc
s.db_record.save
s.db_record = framework.db.update_session(id: s.db_record.id, last_seen: Time.now.utc)
end
end
end

View File

@ -11,6 +11,7 @@ module SessionServlet
def self.registered(app)
app.get SessionServlet.api_path_with_id, &get_session
app.post SessionServlet.api_path, &report_session
app.put SessionServlet.api_path_with_id, &update_session
end
#######
@ -50,4 +51,19 @@ module SessionServlet
}
end
def self.update_session
lambda {
warden.authenticate!
begin
opts = parse_json_request(request, false)
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_session(opts)
set_json_data_response(response: data)
rescue => e
print_error_and_create_response(error: e, message: 'There was an error updating the session:', code: 500)
end
}
end
end

View File

@ -51,11 +51,9 @@ module UserServlet
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_user(opts)
# Only return the single object if the id parameter is present
data = data.first if !sanitized_params[:id].nil? && data.count == 1
set_json_data_response(response: data)
rescue => e
print_error_and_create_response(error: e, message: 'There was an error creating the user:', code: 500)
print_error_and_create_response(error: e, message: 'There was an error updating the user:', code: 500)
end
}
end