Land #11270, fix miscellaneous loot issues
commit
d078fcd87c
|
@ -10,7 +10,8 @@ module LootApiDoc
|
|||
LTYPE_EXAMPLE = "'file', 'image', 'config_file', etc."
|
||||
PATH_DESC = 'The on-disk path to the loot file.'
|
||||
PATH_EXAMPLE = '/path/to/file.txt'
|
||||
DATA_DESC = 'The contents of the file.'
|
||||
DATA_DESC = "Base64 encoded copy of the file's contents."
|
||||
DATA_EXAMPLE = 'dGhpcyBpcyB0aGUgZmlsZSdzIGNvbnRlbnRz'
|
||||
CONTENT_TYPE_DESC = 'The mime/content type of the file at {#path}. Used to server the file correctly so browsers understand whether to render or download the file.'
|
||||
CONTENT_TYPE_EXAMPLE = 'text/plain'
|
||||
NAME_DESC = 'The name of the loot.'
|
||||
|
@ -18,6 +19,9 @@ module LootApiDoc
|
|||
INFO_DESC = 'Information about the loot.'
|
||||
MODULE_RUN_ID_DESC = 'The ID of the module run record this loot is associated with.'
|
||||
|
||||
# Some of the attributes expect different data when doing a create.
|
||||
CREATE_PATH_DESC = 'The name to give the file on the server. All files are stored in a server configured path, so a full path is not needed. If there is a corresponding file on disk, the given value will be prepended with a unique string to prevent accidental overwrites of other files.'
|
||||
CREATE_PATH_EXAMPLE = 'password_file.txt'
|
||||
|
||||
# Swagger documentation for loot model
|
||||
swagger_schema :Loot do
|
||||
|
@ -28,7 +32,7 @@ module LootApiDoc
|
|||
property :service_id, type: :integer, format: :int32, description: SERVICE_ID_DESC
|
||||
property :ltype, type: :string, description: LTYPE_DESC, example: LTYPE_EXAMPLE
|
||||
property :path, type: :string, description: PATH_DESC, example: PATH_EXAMPLE
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
property :data, type: :string, description: DATA_DESC, example: DATA_EXAMPLE
|
||||
property :content_type, type: :string, description: CONTENT_TYPE_DESC, example: CONTENT_TYPE_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
|
@ -87,8 +91,8 @@ module LootApiDoc
|
|||
property :host, type: :string, format: :ipv4, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :service, '$ref': :Service
|
||||
property :ltype, type: :string, description: LTYPE_DESC, example: LTYPE_EXAMPLE, required: true
|
||||
property :path, type: :string, description: PATH_DESC, example: PATH_EXAMPLE, required: true
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
property :path, type: :string, description: CREATE_PATH_DESC, example: CREATE_PATH_EXAMPLE, required: true
|
||||
property :data, type: :string, description: DATA_DESC, example: DATA_EXAMPLE
|
||||
property :ctype, type: :string, description: CONTENT_TYPE_DESC, example: CONTENT_TYPE_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE, required: true
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
|
@ -206,7 +210,14 @@ module LootApiDoc
|
|||
key :description, 'The updated attributes to overwrite to the loot.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Loot
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host_id, type: :integer, format: :int32, description: HOST_ID_DESC
|
||||
property :service_id, type: :integer, format: :int32, description: SERVICE_ID_DESC
|
||||
property :ltype, type: :string, description: LTYPE_DESC, example: LTYPE_EXAMPLE, required: true
|
||||
property :path, type: :string, description: CREATE_PATH_DESC, example: CREATE_PATH_EXAMPLE, required: true
|
||||
property :ctype, type: :string, description: CONTENT_TYPE_DESC, example: CONTENT_TYPE_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE, required: true
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,16 +8,22 @@ module RemoteLootDataService
|
|||
|
||||
def loot(opts = {})
|
||||
path = get_path_select(opts, LOOT_API_PATH)
|
||||
# TODO: Add an option to toggle whether the file data is returned or not
|
||||
loots = json_to_mdm_object(self.get_data(path, nil, opts), LOOT_MDM_CLASS, [])
|
||||
# Save a local copy of the file
|
||||
loots.each do |loot|
|
||||
if loot.data
|
||||
local_path = File.join(Msf::Config.loot_directory, File.basename(loot.path))
|
||||
loot.path = process_file(loot.data, local_path)
|
||||
data = self.get_data(path, nil, opts)
|
||||
rv = json_to_mdm_object(data, LOOT_MDM_CLASS, [])
|
||||
parsed_body = JSON.parse(data.response.body, symbolize_names: true)
|
||||
data = parsed_body[:data]
|
||||
data.each do |loot|
|
||||
# TODO: Add an option to toggle whether the file data is returned or not
|
||||
if loot[:data] && !loot[:data].empty?
|
||||
local_path = File.join(Msf::Config.loot_directory, File.basename(loot[:path]))
|
||||
rv[data.index(loot)].path = process_file(loot[:data], local_path)
|
||||
end
|
||||
if loot[:host]
|
||||
host_object = to_ar(RemoteHostDataService::HOST_MDM_CLASS.constantize, loot[:host])
|
||||
rv[data.index(loot)].host = host_object
|
||||
end
|
||||
end
|
||||
loots
|
||||
rv
|
||||
end
|
||||
|
||||
def report_loot(opts)
|
||||
|
|
|
@ -10,18 +10,18 @@ module Msf::DBManager::Loot
|
|||
# This methods returns a list of all loot in the database
|
||||
#
|
||||
def loots(opts)
|
||||
data = opts.delete(:data)
|
||||
# Remove path from search conditions as this won't accommodate remote data
|
||||
# service usage where the client and server storage locations differ.
|
||||
opts.delete(:path)
|
||||
search_term = opts.delete(:search_term)
|
||||
|
||||
::ActiveRecord::Base.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::Loot.find(opts[:id]))
|
||||
end
|
||||
|
||||
# Remove path from search conditions as this won't accommodate remote data
|
||||
# service usage where the client and server storage locations differ.
|
||||
opts.delete(:path)
|
||||
search_term = opts.delete(:search_term)
|
||||
data = opts.delete(:data)
|
||||
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
||||
opts[:workspace_id] = wspace.id
|
||||
|
||||
|
@ -99,10 +99,19 @@ module Msf::DBManager::Loot
|
|||
def update_loot(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
|
||||
# Prevent changing the data field to ensure the file contents remain the same as what was originally looted.
|
||||
raise ArgumentError, "Updating the data attribute is not permitted." if opts[:data]
|
||||
opts[:workspace] = wspace if wspace
|
||||
|
||||
id = opts.delete(:id)
|
||||
loot = Mdm::Loot.find(id)
|
||||
|
||||
# If the user updates the path attribute (or filename) we need to update the file
|
||||
# on disk to reflect that.
|
||||
if opts[:path] && File.exists?(loot.path)
|
||||
File.rename(loot.path, opts[:path])
|
||||
end
|
||||
|
||||
loot.update!(opts)
|
||||
return loot
|
||||
}
|
||||
|
|
|
@ -30,9 +30,8 @@ module HostServlet
|
|||
begin
|
||||
sanitized_params = sanitize_params(params, env['rack.request.query_hash'])
|
||||
data = get_db.hosts(sanitized_params)
|
||||
includes = [:loots]
|
||||
data = data.first if is_single_object?(data, sanitized_params)
|
||||
set_json_data_response(response: data, includes: includes)
|
||||
set_json_data_response(response: data)
|
||||
rescue => e
|
||||
print_error_and_create_response(error: e, message: 'There was an error retrieving hosts:', code: 500)
|
||||
end
|
||||
|
|
|
@ -26,9 +26,7 @@ module LootServlet
|
|||
sanitized_params = sanitize_params(params, env['rack.request.query_hash'])
|
||||
data = get_db.loots(sanitized_params)
|
||||
includes = [:host]
|
||||
data.each do |loot|
|
||||
loot.data = Base64.urlsafe_encode64(loot.data) if loot.data
|
||||
end
|
||||
data = encode_loot_data(data)
|
||||
data = data.first if is_single_object?(data, sanitized_params)
|
||||
set_json_data_response(response: data, includes: includes)
|
||||
rescue => e
|
||||
|
@ -43,12 +41,13 @@ module LootServlet
|
|||
job = lambda { |opts|
|
||||
if opts[:data]
|
||||
filename = File.basename(opts[:path])
|
||||
local_path = File.join(Msf::Config.loot_directory, filename)
|
||||
local_path = File.join(Msf::Config.loot_directory, "#{SecureRandom.hex(10)}-#{filename}")
|
||||
opts[:path] = process_file(opts[:data], local_path)
|
||||
opts[:data] = Base64.urlsafe_decode64(opts[:data])
|
||||
end
|
||||
|
||||
get_db.report_loot(opts)
|
||||
data = get_db.report_loot(opts)
|
||||
encode_loot_data(data)
|
||||
}
|
||||
exec_report_job(request, &job)
|
||||
}
|
||||
|
@ -61,7 +60,16 @@ module LootServlet
|
|||
opts = parse_json_request(request, false)
|
||||
tmp_params = sanitize_params(params)
|
||||
opts[:id] = tmp_params[:id] if tmp_params[:id]
|
||||
db_record = get_db.loots(opts).first
|
||||
# Give the file a unique name to prevent accidental overwrites. Only do this if there is actually a file
|
||||
# on disk. If there is not a file on disk we assume that this DB record is for tracking a file outside
|
||||
# of metasploit, so we don't want to assign them a unique file name and overwrite that.
|
||||
if opts[:path] && File.exists?(db_record.path)
|
||||
filename = File.basename(opts[:path])
|
||||
opts[:path] = File.join(Msf::Config.loot_directory, "#{SecureRandom.hex(10)}-#{filename}")
|
||||
end
|
||||
data = get_db.update_loot(opts)
|
||||
data = encode_loot_data(data)
|
||||
set_json_data_response(response: data)
|
||||
rescue => e
|
||||
print_error_and_create_response(error: e, message: 'There was an error updating the loot:', code: 500)
|
||||
|
@ -75,6 +83,10 @@ module LootServlet
|
|||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
data = get_db.delete_loot(opts)
|
||||
# The rails delete operation returns a frozen object. We need to Base64 encode the data
|
||||
# before converting to JSON. So we'll work with a duplicate of the original if it is frozen.
|
||||
data.map! { |loot| loot.dup if loot.frozen? }
|
||||
data = encode_loot_data(data)
|
||||
set_json_data_response(response: data)
|
||||
rescue => e
|
||||
print_error_and_create_response(error: e, message: 'There was an error deleting the loot:', code: 500)
|
||||
|
|
|
@ -131,6 +131,13 @@ module ServletHelper
|
|||
response
|
||||
end
|
||||
|
||||
def encode_loot_data(data)
|
||||
Array.wrap(data).each do |loot|
|
||||
loot.data = Base64.urlsafe_encode64(loot.data) if loot.data && !loot.data.empty?
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
# Get Warden::Proxy object from the Rack environment.
|
||||
# @return [Warden::Proxy] The Warden::Proxy object from the Rack environment.
|
||||
def warden
|
||||
|
|
Loading…
Reference in New Issue