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.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-aggregator.svg?branch=master)](https://travis-ci.org/rapid7/metasploit-aggregator) [![Code Climate](https://img.shields.io/codeclimate/github/rapid7/metasploit-aggregator.svg)](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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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