metasploit-aggregator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +52 -0
- data/Gemfile +4 -0
- data/LICENSE +748 -0
- data/README.md +28 -0
- data/Rakefile +6 -0
- data/bin/msfaggregator +37 -0
- data/lib/msf/aggregator.rb +256 -0
- data/lib/msf/aggregator/cable.rb +18 -0
- data/lib/msf/aggregator/connection_manager.rb +184 -0
- data/lib/msf/aggregator/forwarder.rb +55 -0
- data/lib/msf/aggregator/http/request.rb +28 -0
- data/lib/msf/aggregator/http/responder.rb +151 -0
- data/lib/msf/aggregator/http/ssl_responder.rb +28 -0
- data/lib/msf/aggregator/http_forwarder.rb +40 -0
- data/lib/msf/aggregator/https_forwarder.rb +39 -0
- data/lib/msf/aggregator/logger.rb +5 -0
- data/lib/msf/aggregator/router.rb +41 -0
- data/lib/msf/aggregator/version.rb +5 -0
- data/metasploit-aggregator.gemspec +28 -0
- metadata +218 -0
- metadata.gz.sig +2 -0
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Metasploit [](https://travis-ci.org/rapid7/metasploit-aggregator) [](https://codeclimate.com/github/rapid7/metasploit-aggregator)
|
2
|
+
==
|
3
|
+
The Metasploit Aggregator is released under a BSD-style license. See
|
4
|
+
COPYING for more details.
|
5
|
+
|
6
|
+
Bug tracking and development information can be found at:
|
7
|
+
https://github.com/rapid7/metasploit-aggregator
|
8
|
+
|
9
|
+
New bugs and feature requests should be directed to:
|
10
|
+
http://r-7.co/MSF-BUGv1
|
11
|
+
|
12
|
+
API documentation for writing modules can be found at:
|
13
|
+
https://rapid7.github.io/metasploit-aggregator/api
|
14
|
+
|
15
|
+
Questions and suggestions can be sent to:
|
16
|
+
https://lists.sourceforge.net/lists/listinfo/metasploit-hackers
|
17
|
+
|
18
|
+
Installing
|
19
|
+
--
|
20
|
+
|
21
|
+
Using Metasploit Aggregator
|
22
|
+
--
|
23
|
+
|
24
|
+
Contributing
|
25
|
+
--
|
26
|
+
[Contributing](https://github.com/rapid7/metasploit-aggregator/blob/master/CONTRIBUTING.md).
|
27
|
+
|
28
|
+
|
data/Rakefile
ADDED
data/bin/msfaggregator
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'msf/aggregator'
|
5
|
+
require 'msf/aggregator/cable'
|
6
|
+
require 'msf/aggregator/logger'
|
7
|
+
|
8
|
+
admin_host = '127.0.0.1'
|
9
|
+
admin_port = 2447
|
10
|
+
listener = '127.0.0.1'
|
11
|
+
remote_console = '127.0.0.1'
|
12
|
+
# cert_file = './cert.pem'
|
13
|
+
# cert_string = File.new(cert_file).read
|
14
|
+
cert_string = nil
|
15
|
+
|
16
|
+
# server = Msf::Aggregator::Server.new('127.0.0.1', 1337)
|
17
|
+
server = Msf::Aggregator::MsgPackServer.new(admin_host, admin_port)
|
18
|
+
server.start
|
19
|
+
Logger.log "Starting administration service on #{admin_host}:#{admin_port}"
|
20
|
+
|
21
|
+
loop do
|
22
|
+
command = $stdin.gets
|
23
|
+
if command.chomp == 'exit'
|
24
|
+
exit
|
25
|
+
elsif command.chomp == 'clear'
|
26
|
+
forwarder.requests = []
|
27
|
+
forwarder.responses = []
|
28
|
+
elsif command.chomp == 'pause'
|
29
|
+
Logger.log "paused"
|
30
|
+
elsif command.chomp == 'start'
|
31
|
+
server.start
|
32
|
+
elsif command.chomp == 'stop'
|
33
|
+
server.stop
|
34
|
+
elsif command.chomp == 'park'
|
35
|
+
client.release_session($stdin.gets.chomp)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
require 'thread'
|
4
|
+
require 'msgpack'
|
5
|
+
require 'msgpack/rpc'
|
6
|
+
|
7
|
+
require 'msf/aggregator/version'
|
8
|
+
require 'msf/aggregator/cable'
|
9
|
+
require 'msf/aggregator/connection_manager'
|
10
|
+
require 'msf/aggregator/https_forwarder'
|
11
|
+
require 'msf/aggregator/logger'
|
12
|
+
|
13
|
+
module Msf
|
14
|
+
module Aggregator
|
15
|
+
|
16
|
+
class Service
|
17
|
+
# return availability status of the service
|
18
|
+
def available?
|
19
|
+
# index for impl
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns map of sessions available from the service
|
23
|
+
def sessions
|
24
|
+
# index for impl
|
25
|
+
end
|
26
|
+
|
27
|
+
def cables
|
28
|
+
# index for impl
|
29
|
+
end
|
30
|
+
|
31
|
+
# sets forwarding for a specific session to promote
|
32
|
+
# that session for local use, obtained sessions are
|
33
|
+
# not reported in getSessions
|
34
|
+
def obtain_session(payload, lhost, lport)
|
35
|
+
# index for impl
|
36
|
+
end
|
37
|
+
|
38
|
+
# parks a session and makes it available in the getSessions
|
39
|
+
def release_session(payload)
|
40
|
+
# index for impl
|
41
|
+
end
|
42
|
+
|
43
|
+
# start a listening port maintained on the service
|
44
|
+
# connections are forwarded to any registered default
|
45
|
+
# TODO: may want to require a type here for future proof of api
|
46
|
+
def add_cable(type, host, port, certificate = nil)
|
47
|
+
# index for impl
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_cable(host, port)
|
51
|
+
# index for impl
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_default(lhost, lport, payload_list)
|
55
|
+
# index for impl
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns list of IP addressed available to the service
|
59
|
+
# TODO: consider also reporting "used" ports (may not be needed)
|
60
|
+
def available_addresses
|
61
|
+
# index for impl
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ServerProxy < Service
|
66
|
+
@host = @port = @socket = nil
|
67
|
+
@response_queue = []
|
68
|
+
|
69
|
+
def initialize(host, port)
|
70
|
+
@host = host
|
71
|
+
@port = port
|
72
|
+
@client = MessagePack::RPC::Client.new(@host, @port)
|
73
|
+
end
|
74
|
+
|
75
|
+
def available?
|
76
|
+
@client.call(:available?)
|
77
|
+
rescue MessagePack::RPC::ConnectionTimeoutError => e
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def sessions
|
82
|
+
@client.call(:sessions)
|
83
|
+
rescue MessagePack::RPC::TimeoutError => e
|
84
|
+
Logger.log(e.to_s)
|
85
|
+
end
|
86
|
+
|
87
|
+
def cables
|
88
|
+
@client.call(:cables)
|
89
|
+
rescue MessagePack::RPC::TimeoutError => e
|
90
|
+
Logger.log(e.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def obtain_session(payload, lhost, lport)
|
95
|
+
@client.call(:obtain_session, payload, lhost, lport)
|
96
|
+
rescue MessagePack::RPC::TimeoutError => e
|
97
|
+
Logger.log(e.to_s)
|
98
|
+
end
|
99
|
+
|
100
|
+
def release_session(payload)
|
101
|
+
@client.call(:release_session, payload)
|
102
|
+
rescue MessagePack::RPC::TimeoutError => e
|
103
|
+
Logger.log(e.to_s)
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_cable(type, host, port, certificate = nil)
|
107
|
+
@client.call(:add_cable, type, host, port, certificate)
|
108
|
+
rescue MessagePack::RPC::TimeoutError => e
|
109
|
+
Logger.log(e.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_cable(host, port)
|
113
|
+
@client.call(:remove_cable, host, port)
|
114
|
+
rescue MessagePack::RPC::TimeoutError => e
|
115
|
+
Logger.log(e.to_s)
|
116
|
+
end
|
117
|
+
|
118
|
+
def register_default(lhost, lport, payload_list)
|
119
|
+
@client.call(:register_default, lhost, lport, payload_list)
|
120
|
+
rescue MessagePack::RPC::TimeoutError => e
|
121
|
+
Logger.log(e.to_s)
|
122
|
+
end
|
123
|
+
|
124
|
+
def available_addresses
|
125
|
+
@client.call(:available_addresses)
|
126
|
+
rescue MessagePack::RPC::TimeoutError => e
|
127
|
+
Logger.log(e.to_s)
|
128
|
+
end
|
129
|
+
|
130
|
+
def stop
|
131
|
+
@client.close
|
132
|
+
end
|
133
|
+
end # ServerProxy
|
134
|
+
|
135
|
+
class Server < Service
|
136
|
+
# include Metasploit::Aggregator::ConnectionManager
|
137
|
+
|
138
|
+
def initialize
|
139
|
+
@manager = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def start
|
143
|
+
@manager = Msf::Aggregator::ConnectionManager.new
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def available?
|
148
|
+
!@manager.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
def sessions
|
152
|
+
@manager.connections
|
153
|
+
end
|
154
|
+
|
155
|
+
def cables
|
156
|
+
@manager.cables
|
157
|
+
end
|
158
|
+
|
159
|
+
def obtain_session(payload, rhost, rport)
|
160
|
+
# return session object details or UUID/uri
|
161
|
+
# forwarding will cause new session creation on the console
|
162
|
+
# TODO: check and set lock on payload requested see note below in register_default
|
163
|
+
@manager.register_forward(rhost, rport, [ payload ])
|
164
|
+
true # update later to return if lock obtained
|
165
|
+
end
|
166
|
+
|
167
|
+
def release_session(payload)
|
168
|
+
@manager.park(payload)
|
169
|
+
true # return always return success for now
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_cable(type, host, port, certificate = nil)
|
173
|
+
unless @manager.nil?
|
174
|
+
case type
|
175
|
+
when Cable::HTTPS
|
176
|
+
# TODO: check if already listening on that port
|
177
|
+
@manager.add_cable_https(host, port, certificate)
|
178
|
+
when Cable::HTTP
|
179
|
+
@manager.add_cable_http(host, port)
|
180
|
+
else
|
181
|
+
Logger.log("#{type} cables are not supported.")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
true
|
185
|
+
end
|
186
|
+
|
187
|
+
def remove_cable(host, port)
|
188
|
+
unless @manager.nil?
|
189
|
+
@manager.remove_cable(host, port)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def register_default(lhost, lport, payload_list)
|
194
|
+
# add this payload list to each forwarder for this remote console
|
195
|
+
# TODO: consider adding boolean param to ConnectionManager.register_forward to 'lock'
|
196
|
+
@manager.register_forward(lhost, lport, payload_list)
|
197
|
+
true
|
198
|
+
end
|
199
|
+
|
200
|
+
def available_addresses
|
201
|
+
addr_list = Socket.ip_address_list
|
202
|
+
addresses = []
|
203
|
+
addr_list.each do |addr|
|
204
|
+
addresses << addr.ip_address
|
205
|
+
end
|
206
|
+
addresses
|
207
|
+
end
|
208
|
+
|
209
|
+
def stop
|
210
|
+
unless @manager.nil?
|
211
|
+
@manager.stop
|
212
|
+
end
|
213
|
+
@manager = nil
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
def release_session(host)
|
218
|
+
@manager.park(host)
|
219
|
+
end
|
220
|
+
end # class Server
|
221
|
+
|
222
|
+
class MsgPackServer
|
223
|
+
|
224
|
+
def initialize(host, port)
|
225
|
+
@host = host
|
226
|
+
@port = port
|
227
|
+
|
228
|
+
# server = TCPServer.new(@host, @port)
|
229
|
+
# sslContext = OpenSSL::SSL::SSLContext.new
|
230
|
+
# sslContext.key, sslContext.cert = Msf::Aggregator::ConnectionManager.ssl_generate_certificate
|
231
|
+
# sslServer = OpenSSL::SSL::SSLServer.new(server, sslContext)
|
232
|
+
#
|
233
|
+
@svr = MessagePack::RPC::Server.new # need to initialize this as ssl server
|
234
|
+
# @svr.listen(sslServer, Server.new)
|
235
|
+
@svr.listen(@host, @port, Server.new)
|
236
|
+
|
237
|
+
Thread.new { @svr.run }
|
238
|
+
end
|
239
|
+
|
240
|
+
def start
|
241
|
+
c = MessagePack::RPC::Client.new(@host,@port)
|
242
|
+
c.call(:start)
|
243
|
+
c.close
|
244
|
+
rescue MessagePack::RPC::TimeoutError => e
|
245
|
+
Logger.log(e.to_s)
|
246
|
+
end
|
247
|
+
|
248
|
+
def stop
|
249
|
+
c = MessagePack::RPC::Client.new(@host,@port)
|
250
|
+
c.call(:stop)
|
251
|
+
c.close
|
252
|
+
@svr.close
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Msf
|
2
|
+
module Aggregator
|
3
|
+
class Cable
|
4
|
+
HTTPS = 'https'
|
5
|
+
HTTP = 'http'
|
6
|
+
|
7
|
+
attr_reader :forwarder
|
8
|
+
attr_reader :server
|
9
|
+
attr_reader :thread
|
10
|
+
|
11
|
+
def initialize(thread, server, forwarder)
|
12
|
+
@thread = thread
|
13
|
+
@forwarder = forwarder
|
14
|
+
@server = server
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'msf/aggregator/logger'
|
5
|
+
require 'msf/aggregator/http_forwarder'
|
6
|
+
require 'msf/aggregator/https_forwarder'
|
7
|
+
require 'msf/aggregator/cable'
|
8
|
+
|
9
|
+
module Msf
|
10
|
+
module Aggregator
|
11
|
+
|
12
|
+
class ConnectionManager
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@cables = []
|
16
|
+
@manager_mutex = Mutex.new
|
17
|
+
@default_route = []
|
18
|
+
@router = Router.instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.ssl_generate_certificate
|
22
|
+
yr = 24*3600*365
|
23
|
+
vf = Time.at(Time.now.to_i - rand(yr * 3) - yr)
|
24
|
+
vt = Time.at(vf.to_i + (10 * yr))
|
25
|
+
cn = 'localhost'
|
26
|
+
key = OpenSSL::PKey::RSA.new(2048){ }
|
27
|
+
cert = OpenSSL::X509::Certificate.new
|
28
|
+
cert.version = 2
|
29
|
+
cert.serial = (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF)
|
30
|
+
cert.subject = OpenSSL::X509::Name.new([["CN", cn]])
|
31
|
+
cert.issuer = OpenSSL::X509::Name.new([["CN", cn]])
|
32
|
+
cert.not_before = vf
|
33
|
+
cert.not_after = vt
|
34
|
+
cert.public_key = key.public_key
|
35
|
+
|
36
|
+
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
37
|
+
cert.extensions = [
|
38
|
+
ef.create_extension("basicConstraints","CA:FALSE")
|
39
|
+
]
|
40
|
+
ef.issuer_certificate = cert
|
41
|
+
|
42
|
+
cert.sign(key, OpenSSL::Digest::SHA256.new)
|
43
|
+
|
44
|
+
[key, cert, nil]
|
45
|
+
end
|
46
|
+
|
47
|
+
def ssl_parse_certificate(certificate)
|
48
|
+
key, cert, chain = nil
|
49
|
+
unless certificate.nil?
|
50
|
+
begin
|
51
|
+
# parse the cert
|
52
|
+
key = OpenSSL::PKey::RSA.new(certificate, "")
|
53
|
+
cert = OpenSSL::X509::Certificate.new(certificate)
|
54
|
+
# TODO: ensure this parses all certificate in object provided
|
55
|
+
rescue OpenSSL::PKey::RSAError => e
|
56
|
+
Logger.log(e.message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return key, cert, chain
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_cable_https(host, port, certificate)
|
63
|
+
@manager_mutex.synchronize do
|
64
|
+
forwarder = Msf::Aggregator::HttpsForwarder.new
|
65
|
+
forwarder.log_messages = true
|
66
|
+
server = TCPServer.new(host, port)
|
67
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
68
|
+
unless certificate.nil?
|
69
|
+
ssl_context.key, ssl_context.cert = ssl_parse_certificate(certificate)
|
70
|
+
else
|
71
|
+
ssl_context.key, ssl_context.cert = Msf::Aggregator::ConnectionManager.ssl_generate_certificate
|
72
|
+
end
|
73
|
+
ssl_server = OpenSSL::SSL::SSLServer.new(server, ssl_context)
|
74
|
+
|
75
|
+
handler = connect_cable(ssl_server, host, port, forwarder)
|
76
|
+
@cables << Cable.new(handler, server, forwarder)
|
77
|
+
handler
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_cable_http(host, port)
|
82
|
+
@manager_mutex.synchronize do
|
83
|
+
forwarder = Msf::Aggregator::HttpForwarder.new
|
84
|
+
forwarder.log_messages = true
|
85
|
+
server = TCPServer.new(host, port)
|
86
|
+
|
87
|
+
handler = connect_cable(server, host, port, forwarder)
|
88
|
+
@cables << Cable.new(handler, server, forwarder)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def register_forward(rhost, rport, payload_list = nil)
|
93
|
+
@cables.each do |cable|
|
94
|
+
addr = cable.server.local_address
|
95
|
+
if addr.ip_address == rhost && addr.ip_port == rport.to_i
|
96
|
+
raise ArgumentError.new("#{rhost}:#{rport} is not a valid forward")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
if payload_list.nil?
|
100
|
+
# add the this host and port as the new default route
|
101
|
+
@default_route = [rhost, rport]
|
102
|
+
@router.add_route(rhost, rport, nil)
|
103
|
+
else
|
104
|
+
payload_list.each do |payload|
|
105
|
+
@router.add_route(rhost, rport, payload)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def connections
|
111
|
+
connections = {}
|
112
|
+
@cables.each do |cable|
|
113
|
+
connections = connections.merge cable.forwarder.connections
|
114
|
+
end
|
115
|
+
connections
|
116
|
+
end
|
117
|
+
|
118
|
+
def cables
|
119
|
+
local_cables = []
|
120
|
+
@cables.each do |cable|
|
121
|
+
addr = cable.server.local_address
|
122
|
+
local_cables << addr.ip_address + ':' + addr.ip_port.to_s
|
123
|
+
end
|
124
|
+
local_cables
|
125
|
+
end
|
126
|
+
|
127
|
+
def connect_cable(server, host, port, forwarder)
|
128
|
+
Logger.log "Listening on port #{host}:#{port}"
|
129
|
+
|
130
|
+
handler = Thread.new do
|
131
|
+
begin
|
132
|
+
loop do
|
133
|
+
Logger.log "waiting for connection on #{host}:#{port}"
|
134
|
+
connection = server.accept
|
135
|
+
Logger.log "got connection on #{host}:#{port}"
|
136
|
+
Thread.new do
|
137
|
+
begin
|
138
|
+
forwarder.forward(connection)
|
139
|
+
rescue
|
140
|
+
Logger.log $!
|
141
|
+
end
|
142
|
+
Logger.log "completed connection on #{host}:#{port}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
handler
|
148
|
+
end
|
149
|
+
|
150
|
+
def remove_cable(host, port)
|
151
|
+
@manager_mutex.synchronize do
|
152
|
+
closed_servers = []
|
153
|
+
@cables.each do |cable|
|
154
|
+
addr = cable.server.local_address
|
155
|
+
if addr.ip_address == host && addr.ip_port == port.to_i
|
156
|
+
cable.server.close
|
157
|
+
cable.thread.exit
|
158
|
+
closed_servers << cable
|
159
|
+
end
|
160
|
+
end
|
161
|
+
@cables -= closed_servers
|
162
|
+
end
|
163
|
+
return true
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
def stop
|
168
|
+
@manager_mutex.synchronize do
|
169
|
+
@cables.each do |listener|
|
170
|
+
listener.server.close
|
171
|
+
listener.thread.exit
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def park(payload)
|
177
|
+
@router.add_route(nil, nil, payload)
|
178
|
+
Logger.log "parking #{payload}"
|
179
|
+
end
|
180
|
+
|
181
|
+
private :ssl_parse_certificate
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|