Skip to content

Commit

Permalink
Refactor existing fetch work
Browse files Browse the repository at this point in the history
* Build the HTTPS server on top of HTTP instead of the other way around
* Set the fetch service to nil after it has been cleaned up
* Don't capitalize the H in the word handler
* Check if the fetch_service is truthy before cleaning it up
* Remove the unused FetchServerName datastore option
* Fixup the description text
* Don't allow slashes in fetch file names
* Also add the #fetch_bindnetloc method
  • Loading branch information
zeroSteiner committed Jan 4, 2024
1 parent d5a59ce commit 0fd5517
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 148 deletions.
25 changes: 8 additions & 17 deletions lib/msf/core/payload/adapter/fetch.rb
Expand Up @@ -5,19 +5,18 @@ def initialize(*args)
register_options(
[
Msf::OptBool.new('FETCH_DELETE', [true, 'Attempt to delete the binary after execution', false]),
Msf::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces.', Rex::Text.rand_text_alpha(rand(8..12))], regex:/^[\S]*$/),
Msf::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces or slashes', Rex::Text.rand_text_alpha(rand(8..12))], regex: /^[^\s\/\\]*$/),
Msf::OptPort.new('FETCH_SRVPORT', [true, 'Local port to use for serving payload', 8080]),
Msf::OptAddressRoutable.new('FETCH_SRVHOST', [ false, 'Local IP to use for serving payload']),
Msf::OptString.new('FETCH_URIPATH', [ false, 'Local URI to use for serving payload', '']),
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces.', ''], regex:/^[\S]*$/)
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', ''], regex:/^[\S]*$/)
]
)
register_advanced_options(
[
Msf::OptAddress.new('FetchListenerBindAddress', [ false, 'The specific IP address to bind to to serve the payload if different from FETCH_SRVHOST']),
Msf::OptPort.new('FetchListenerBindPort', [false, 'The port to bind to if different from FETCH_SRVPORT']),
Msf::OptBool.new('FetchHandlerDisable', [true, 'Disable fetch handler', false]),
Msf::OptString.new('FetchServerName', [true, 'Fetch Server Name', 'Apache'])
Msf::OptBool.new('FetchHandlerDisable', [true, 'Disable fetch handler', false])
]
)
@delete_resource = true
Expand All @@ -27,7 +26,6 @@ def initialize(*args)
@remote_destination_win = nil
@remote_destination_nix = nil
@windows = nil

end

# If no fetch URL is provided, we generate one based off the underlying payload data
Expand Down Expand Up @@ -77,6 +75,10 @@ def fetch_bindport
datastore['FetchListenerBindPort'].blank? ? srvport : datastore['FetchListenerBindPort']
end

def fetch_bindnetloc
Rex::Socket.to_authority(fetch_bindhost, fetch_bindport)
end

def generate(opts = {})
datastore['FETCH_SRVHOST'] = datastore['LHOST'] if datastore['FETCH_SRVHOST'].blank?
fail_with(Msf::Module::Failure::BadConfig, 'FETCH_SRVHOST required') if datastore['FETCH_SRVHOST'].blank?
Expand Down Expand Up @@ -130,13 +132,7 @@ def srvhost
end

def srvnetloc
netloc = srvhost
if Rex::Socket.is_ipv6?(netloc)
netloc = "[#{netloc}]:#{srvport}"
else
netloc = "#{netloc}:#{srvport}"
end
netloc
Rex::Socket.to_authority(srvhost, srvport)
end

def srvport
Expand All @@ -148,10 +144,6 @@ def srvuri
default_srvuri
end

def srvname
datastore['FetchServerName']
end

def windows?
return @windows unless @windows.nil?
@windows = platform.platforms.first == Msf::Module::Platform::Windows
Expand Down Expand Up @@ -243,7 +235,6 @@ def _generate_curl_command
cmd + _execute_add
end


def _generate_ftp_command
case fetch_protocol
when 'FTP'
Expand Down
7 changes: 5 additions & 2 deletions lib/msf/core/payload/adapter/fetch/http.rb
Expand Up @@ -10,7 +10,11 @@ def initialize(*args)
end

def cleanup_handler
cleanup_http_fetch_service(@fetch_service, @delete_resource)
if @fetch_service
cleanup_http_fetch_service(@fetch_service, @delete_resource)
@fetch_service = nil
end

super
end

Expand All @@ -20,4 +24,3 @@ def setup_handler
end

end

8 changes: 6 additions & 2 deletions lib/msf/core/payload/adapter/fetch/https.rb
Expand Up @@ -10,7 +10,11 @@ def initialize(*args)
end

def cleanup_handler
cleanup_http_fetch_service(@fetch_service, @delete_resource)
if @fetch_service
cleanup_http_fetch_service(@fetch_service, @delete_resource)
@fetch_service = nil
end

super
end

Expand All @@ -19,4 +23,4 @@ def setup_handler
super
end

end
end
108 changes: 97 additions & 11 deletions lib/msf/core/payload/adapter/fetch/server/http.rb
@@ -1,24 +1,110 @@
module Msf::Payload::Adapter::Fetch::Server::HTTP
include Msf::Payload::Adapter::Fetch::Server::Https

# This mixin supports only HTTP fetch handlers but still imports the HTTPS mixin.
# We just remove the HTTPS Options so the user does not see them.
#
# This mixin supports only HTTP fetch handlers.

def initialize(*args)
super
deregister_options('FETCH_SSL',
'FETCH_CHECK_CERT',
'FetchSSLCert',
'FetchSSLCompression',
'FetchSSLCipher',
'FetchSSLCipher',
'FetchSSLVersion'
register_advanced_options(
[
Msf::OptString.new('FetchHttpServerName', [true, 'Fetch HTTP server name', 'Apache'])
]
)
end

def fetch_protocol
'HTTP'
end

def srvname
datastore['FetchHttpServerName']
end

def add_resource(fetch_service, uri, srvexe)
vprint_status("Adding resource #{uri}")
if fetch_service.resources.include?(uri)
# When we clean up, we need to leave resources alone, because we never added one.
@delete_resource = false
fail_with(Msf::Exploit::Failure::BadConfig, "Resource collision detected. Set FETCH_URIPATH to a different value to continue.")
end
fetch_service.add_resource(uri,
'Proc' => proc do |cli, req|
on_request_uri(cli, req, srvexe)
end,
'VirtualDirectory' => true)
rescue ::Exception => e
# When we clean up, we need to leave resources alone, because we never added one.
@delete_resource = false
fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n #{e}")
end

def cleanup_http_fetch_service(fetch_service, delete_resource)
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
if fetch_service.resources.include?(escaped_srvuri) && delete_resource
fetch_service.remove_resource(escaped_srvuri)
end
fetch_service.deref
if fetch_service.resources.empty?
# if we don't call deref, we cannot start another httpserver
# this is a reimplementation of the cleanup_service method
# in Exploit::Remote::SocketServer
temp_service = fetch_service
temp_service.cleanup
temp_service.deref
end
end

def start_http_fetch_handler(srvname, srvexe, ssl=false, ssl_cert=nil, ssl_compression=nil, ssl_cipher=nil, ssl_version=nil)
# this looks a bit funny because I converted it to use an instance variable so that if we crash in the
# middle and don't return a value, we still have the right fetch_service to clean up.
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
fetch_service = start_http_server(ssl, ssl_cert, ssl_compression, ssl_cipher, ssl_version)
if fetch_service.nil?
cleanup_handler
fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}")
end
vprint_status("#{fetch_protocol} server started")
fetch_service.server_name = srvname
add_resource(fetch_service, escaped_srvuri, srvexe)
fetch_service
end

def on_request_uri(cli, request, srvexe)
client = cli.peerhost
vprint_status("Client #{client} requested #{request.uri}")
if (user_agent = request.headers['User-Agent'])
client += " (#{user_agent})"
end
vprint_status("Sending payload to #{client}")
cli.send_response(payload_response(srvexe))
end

def payload_response(srvexe)
res = Rex::Proto::Http::Response.new(200, 'OK', Rex::Proto::Http::DefaultProtocol)
res['Content-Type'] = 'text/html'
res.body = srvexe.to_s.unpack('C*').pack('C*')
res
end

def start_http_server(ssl=false, ssl_cert=nil, ssl_compression=nil, ssl_cipher=nil, ssl_version=nil)
begin
fetch_service = Rex::ServiceManager.start(
Rex::Proto::Http::Server,
fetch_bindport, fetch_bindhost, ssl,
{
'Msf' => framework,
'MsfExploit' => self
},
_determine_server_comm(fetch_bindhost),
ssl_cert,
ssl_compression,
ssl_cipher,
ssl_version
)
rescue Exception => e
cleanup_handler
fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}\n#{e}")
end
vprint_status("Fetch handler listening on #{fetch_bindnetloc}")
fetch_service
end
end
111 changes: 3 additions & 108 deletions lib/msf/core/payload/adapter/fetch/server/https.rb
@@ -1,18 +1,17 @@
module Msf::Payload::Adapter::Fetch::Server::Https
include Msf::Payload::Adapter::Fetch::Server::HTTP

# This mixin supports both HTTP and HTTPS fetch handlers. If you only want
# HTTP, use the HTTP mixin that imports this, but removes the HTTPS options
def initialize(*args)
super
register_options(
[
Msf::OptBool.new('FETCH_CHECK_CERT', [true,"Check SSL certificate", false])

Msf::OptBool.new('FETCH_CHECK_CERT', [true, 'Check SSL certificate', false])
]
)
register_advanced_options(
[
Msf::OptString.new('FetchHttpServerName', [true, 'Http Server Name', 'Apache']),
Msf::OptPath.new('FetchSSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)', '']),
Msf::OptBool.new('FetchSSLCompression', [ false, 'Enable SSL/TLS-level compression', false ]),
Msf::OptString.new('FetchSSLCipher', [ false, 'String for SSL cipher spec - "DHE-RSA-AES256-SHA" or "ADH"']),
Expand All @@ -23,64 +22,10 @@ def initialize(*args)
)
end

def add_resource(fetch_service, uri, srvexe)
vprint_status("Adding resource #{uri}")
if fetch_service.resources.include?(uri)
# When we clean up, we need to leave resources alone, because we never added one.
@delete_resource = false
fail_with(Msf::Exploit::Failure::BadConfig, "Resource collision detected. Set FETCH_URI to a different value to continue.")
end
fetch_service.add_resource(uri,
'Proc' => proc do |cli, req|
on_request_uri(cli, req, srvexe)
end,
'VirtualDirectory' => true)
rescue ::Exception => e
# When we clean up, we need to leave resources alone, because we never added one.
@delete_resource = false
fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n #{e}")
end

def cleanup_http_fetch_service(fetch_service, delete_resource)
unless fetch_service.nil?
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
if fetch_service.resources.include?(escaped_srvuri) && delete_resource
fetch_service.remove_resource(escaped_srvuri)
end
fetch_service.deref
if fetch_service.resources.empty?
# if we don't call deref, we cannot start another httpserver
# this is a reimplementation of the cleanup_service method
# in Exploit::Remote::SocketServer
temp_service = fetch_service
fetch_service = nil
temp_service.cleanup
temp_service.deref
end
end
end

def fetch_protocol
'HTTPS'
end

def on_request_uri(cli, request, srvexe)
client = cli.peerhost
vprint_status("Client #{client} requested #{request.uri}")
if (user_agent = request.headers['User-Agent'])
client += " (#{user_agent})"
end
vprint_status("Sending payload to #{client}")
cli.send_response(payload_response(srvexe))
end

def payload_response(srvexe)
res = Rex::Proto::Http::Response.new(200, 'OK', Rex::Proto::Http::DefaultProtocol)
res['Content-Type'] = 'text/html'
res.body = srvexe.to_s.unpack('C*').pack('C*')
res
end

def ssl_cert
datastore['FetchSSLCert']
end
Expand All @@ -97,57 +42,7 @@ def ssl_version
datastore['FetchSSLVersion']
end

def start_http_fetch_handler(srvname, srvexe)
# this looks a bit funny because I converted it to use an instance variable so that if we crash in the
# middle and don't return a value, we still have the right fetch_service to clean up.
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
@fetch_service = start_https_server(false, nil, nil, nil, nil) if @fetch_service.nil?
if @fetch_service.nil?
cleanup_handler
fail_with(Msf::Exploit::Failure::BadConfig, "Fetch Handler failed to start on #{fetch_bindhost}:#{fetch_bindport}")
end
vprint_status('HTTP server started')
@fetch_service.server_name = srvname
add_resource(@fetch_service, escaped_srvuri, srvexe)
@fetch_service
end

def start_https_fetch_handler(srvname, srvexe)
# this looks a bit funny because I converted it to use an instance variable so that if we crash in the
# middle and don't return a value, we still have the right fetch_service to clean up.
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
@fetch_service = start_https_server(true, ssl_cert, ssl_compression, ssl_cipher, ssl_version) if @fetch_service.nil?
if @fetch_service.nil?
cleanup_handler
fail_with(Msf::Exploit::Failure::BadConfig, "Fetch Handler failed to start on #{fetch_bindhost}:#{fetch_bindport}\n #{e}")
end
vprint_status('HTTPS server started')
@fetch_service.server_name = srvname
add_resource(@fetch_service, escaped_srvuri, srvexe)
@fetch_service
start_http_fetch_handler(srvname, srvexe, true, ssl_cert, ssl_compression, ssl_cipher, ssl_version)
end

def start_https_server(ssl, ssl_cert, ssl_compression, ssl_cipher, ssl_version)
begin
fetch_service = Rex::ServiceManager.start(
Rex::Proto::Http::Server,
fetch_bindport, fetch_bindhost, ssl,
{
'Msf' => framework,
'MsfExploit' => self
},
_determine_server_comm(fetch_bindhost),
ssl_cert,
ssl_compression,
ssl_cipher,
ssl_version
)
rescue Exception => e
cleanup_handler
fail_with(Msf::Exploit::Failure::BadConfig, "Fetch Handler failed to start on #{fetch_bindhost}:#{fetch_bindport}\n #{e}")
end
vprint_status("Fetch Handler listening on #{fetch_bindhost}:#{fetch_bindport}")
fetch_service
end

end

0 comments on commit 0fd5517

Please sign in to comment.