CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/plugins/session_notifier.rb
Views: 11705
1
require 'net/https'
2
require 'net/http'
3
require 'uri'
4
module Msf
5
class Plugin::SessionNotifier < Msf::Plugin
6
7
include Msf::SessionEvent
8
9
class Exception < ::RuntimeError; end
10
11
class SessionNotifierCommandDispatcher
12
13
include Msf::Ui::Console::CommandDispatcher
14
15
attr_reader :sms_client, :sms_carrier, :sms_number, :smtp_address, :smtp_port, :smtp_username, :smtp_password, :smtp_from, :minimum_ip, :maximum_ip, :dingtalk_webhook, :gotify_address, :gotify_sslcert_path, :serverjang_webhook
16
17
def name
18
'SessionNotifier'
19
end
20
21
def commands
22
{
23
'set_session_smtp_address' => 'Set the SMTP address for the session notifier',
24
'set_session_smtp_port' => 'Set the SMTP port for the session notifier',
25
'set_session_smtp_username' => 'Set the SMTP username',
26
'set_session_smtp_password' => 'Set the SMTP password',
27
'set_session_smtp_from' => 'Set the from field of SMTP',
28
'set_session_mobile_number' => 'Set the 10-digit mobile number you want to notify',
29
'set_session_mobile_carrier' => 'Set the mobile carrier of the phone',
30
'set_session_minimum_ip' => 'Set the minimum session IP range you want to be notified for',
31
'set_session_maximum_ip' => 'Set the maximum session IP range you want to be notified for',
32
'set_session_dingtalk_webhook' => 'Set the DingTalk webhook for the session notifier (keyword: session).',
33
'set_session_gotify_address' => 'Set the Gotify address for the session notifier',
34
'set_session_gotify_sslcert_path' => 'Set the path to load your Gotify SSL cert (if you want to use HTTPS)',
35
'set_session_serverjang_webhook' => 'Set the ServerJiang webhook for the session notifier (keyword: session).',
36
'save_session_notifier_settings' => 'Save all the session notifier settings to framework',
37
'start_session_notifier' => 'Start notifying sessions',
38
'stop_session_notifier' => 'Stop notifying sessions',
39
'restart_session_notifier' => 'Restart notifying sessions'
40
}
41
end
42
43
def initialize(driver)
44
super(driver)
45
load_settings_from_config
46
end
47
48
def cmd_set_session_smtp_address(*args)
49
@smtp_address = args[0]
50
end
51
52
def cmd_set_session_smtp_port(*args)
53
port = args[0]
54
if port =~ /^\d+$/
55
@smtp_port = args[0]
56
else
57
print_error('Invalid port setting. Must be a number.')
58
end
59
end
60
61
def cmd_set_session_smtp_username(*args)
62
@smtp_username = args[0]
63
end
64
65
def cmd_set_session_smtp_password(*args)
66
@smtp_password = args[0]
67
end
68
69
def cmd_set_session_smtp_from(*args)
70
@smtp_from = args[0]
71
end
72
73
def cmd_set_session_mobile_number(*args)
74
num = args[0]
75
if num =~ /^\d{10}$/
76
@sms_number = args[0]
77
else
78
print_error('Invalid phone format. It should be a 10-digit number that looks like: XXXXXXXXXX')
79
end
80
end
81
82
def cmd_set_session_mobile_carrier(*args)
83
@sms_carrier = args[0].to_sym
84
end
85
86
def cmd_set_session_minimum_ip(*args)
87
ip = args[0]
88
if ip.blank?
89
@minimum_ip = nil
90
elsif Rex::Socket.dotted_ip?(ip)
91
@minimum_ip = IPAddr.new(ip)
92
else
93
print_error('Invalid IP format')
94
end
95
end
96
97
def cmd_set_session_maximum_ip(*args)
98
ip = args[0]
99
if ip.blank?
100
@maximum_ip = nil
101
elsif Rex::Socket.self.dotted_ip?(ip)
102
@maximum_ip = IPAddr.new(ip)
103
else
104
print_error('Invalid IP format')
105
end
106
end
107
108
def cmd_set_session_gotify_address(*args)
109
webhook_url = args[0]
110
if webhook_url.blank?
111
@gotify_address = nil
112
elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?
113
@gotify_address = webhook_url
114
else
115
@gotify_address = nil
116
print_error('Invalid gotify_address')
117
end
118
end
119
120
def cmd_set_session_gotify_sslcert_path(*args)
121
cert_path = args[0]
122
if !cert_path.blank? && ::File.file?(cert_path) && ::File.readable?(cert_path)
123
@gotify_sslcert_path = cert_path
124
print_status("Set Gotify ssl_mode ON! Your cert path is #{gotify_sslcert_path}")
125
else
126
@gotify_sslcert_path = nil
127
print_status('Set Gotify ssl_mode OFF!')
128
end
129
end
130
131
def cmd_set_session_dingtalk_webhook(*args)
132
webhook_url = args[0]
133
if webhook_url.blank?
134
@dingtalk_webhook = nil
135
elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?
136
@dingtalk_webhook = webhook_url
137
else
138
print_error('Invalid webhook_url')
139
end
140
end
141
142
def cmd_set_session_serverjang_webhook(*args)
143
webhook_url = args[0]
144
if webhook_url.blank?
145
@serverjang_webhook = nil
146
elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?
147
@serverjang_webhook = webhook_url
148
else
149
print_error('Invalid webhook_url')
150
end
151
end
152
153
def cmd_save_session_notifier_settings(*_args)
154
save_settings_to_config
155
print_status('Session Notifier settings saved in config file.')
156
end
157
158
def cmd_start_session_notifier(*_args)
159
if session_notifier_subscribed?
160
print_status('You already have an active session notifier.')
161
return
162
end
163
164
begin
165
framework.events.add_session_subscriber(self)
166
if validate_sms_settings?
167
smtp = Rex::Proto::Sms::Model::Smtp.new(
168
address: smtp_address,
169
port: smtp_port,
170
username: smtp_username,
171
password: smtp_password,
172
login_type: :login,
173
from: smtp_from
174
)
175
@sms_client = Rex::Proto::Sms::Client.new(carrier: sms_carrier, smtp_server: smtp)
176
print_status('Session notification started.')
177
end
178
if !dingtalk_webhook.nil?
179
print_status('DingTalk notification started.')
180
end
181
if !gotify_address.nil?
182
print_status('Gotify notification started.')
183
end
184
if !serverjang_webhook.nil?
185
print_status('ServerJang notification started.')
186
end
187
rescue Msf::Plugin::SessionNotifier::Exception, Rex::Proto::Sms::Exception => e
188
print_error(e.message)
189
end
190
end
191
192
def cmd_stop_session_notifier(*_args)
193
framework.events.remove_session_subscriber(self)
194
print_status('Session notification stopped.')
195
end
196
197
def cmd_restart_session_notifier(*args)
198
cmd_stop_session_notifier(args)
199
cmd_start_session_notifier(args)
200
end
201
202
def on_session_open(session)
203
subject = "You have a new #{session.type} session!"
204
msg = "#{session.tunnel_peer} (#{session.session_host}) #{session.info ? "\"#{session.info}\"" : nil}"
205
notify_session(session, subject, msg)
206
end
207
208
private
209
210
def save_settings_to_config
211
config_file = Msf::Config.config_file
212
ini = Rex::Parser::Ini.new(config_file)
213
ini.add_group(name) unless ini[name]
214
ini[name]['smtp_address'] = smtp_address
215
ini[name]['smtp_port'] = smtp_port
216
ini[name]['smtp_username'] = smtp_username
217
ini[name]['smtp_password'] = smtp_password
218
ini[name]['smtp_from'] = smtp_from
219
ini[name]['sms_number'] = sms_number
220
ini[name]['sms_carrier'] = sms_carrier
221
ini[name]['minimum_ip'] = minimum_ip.to_s unless minimum_ip.blank?
222
ini[name]['maximum_ip'] = maximum_ip.to_s unless maximum_ip.blank?
223
ini[name]['dingtalk_webhook'] = dingtalk_webhook.to_s unless dingtalk_webhook.blank?
224
ini[name]['gotify_address'] = gotify_address.to_s unless gotify_address.blank?
225
ini[name]['gotify_sslcert_path'] = gotify_sslcert_path.to_s unless gotify_sslcert_path.blank?
226
ini[name]['serverjang_webhook'] = serverjang_webhook.to_s unless serverjang_webhook.blank?
227
ini.to_file(config_file)
228
end
229
230
def load_settings_from_config
231
config_file = Msf::Config.config_file
232
ini = Rex::Parser::Ini.new(config_file)
233
group = ini[name]
234
if group
235
@sms_carrier = group['sms_carrier'].to_sym if group['sms_carrier']
236
@sms_number = group['sms_number'] if group['sms_number']
237
@smtp_address = group['smtp_address'] if group['smtp_address']
238
@smtp_port = group['smtp_port'] if group['smtp_port']
239
@smtp_username = group['smtp_username'] if group['smtp_username']
240
@smtp_password = group['smtp_password'] if group['smtp_password']
241
@smtp_from = group['smtp_from'] if group['smtp_from']
242
@minimum_ip = IPAddr.new(group['minimum_ip']) if group['minimum_ip']
243
@maximum_ip = IPAddr.new(group['maximum_ip']) if group['maximum_ip']
244
@dingtalk_webhook = group['dingtalk_webhook'] if group['dingtalk_webhook']
245
@gotify_address = group['gotify_address'] if group['gotify_address']
246
@gotify_sslcert_path = group['gotify_sslcert_path'] if group['gotify_sslcert_path']
247
@serverjang_webhook = group['serverjang_webhook'] if group['serverjang_webhook']
248
print_status('Session Notifier settings loaded from config file.')
249
end
250
end
251
252
def session_notifier_subscribed?
253
subscribers = framework.events.instance_variable_get(:@session_event_subscribers).collect(&:class)
254
subscribers.include?(self.class)
255
end
256
257
def send_text_to_dingtalk(session)
258
# https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq/9e91d73c
259
uri_parser = URI.parse(dingtalk_webhook)
260
markdown_text = "## You have a new #{session.type} session!\n\n" \
261
"**platform** : #{session.platform}\n\n" \
262
"**tunnel** : #{session.tunnel_to_s}\n\n" \
263
"**arch** : #{session.arch}\n\n" \
264
"**info** : > #{session.info ? session.info.to_s : nil}"
265
json_post_data = JSON.pretty_generate({
266
msgtype: 'markdown',
267
markdown: { title: 'Session Notifier', text: markdown_text }
268
})
269
http = Net::HTTP.new(uri_parser.host, uri_parser.port)
270
http.use_ssl = true
271
request = Net::HTTP::Post.new(uri_parser.request_uri)
272
request.content_type = 'application/json'
273
request.body = json_post_data
274
res = http.request(request)
275
if res.nil? || res.body.blank?
276
print_error('No response received from the DingTalk server!')
277
return nil
278
end
279
begin
280
body = JSON.parse(res.body)
281
print_status((body['errcode'] == 0) ? 'Session notified to DingTalk.' : 'Failed to send notification.')
282
rescue JSON::ParserError
283
print_error("Couldn't parse the JSON returned from the DingTalk server!")
284
end
285
end
286
287
def send_text_to_gotify(session)
288
# https://gotify.net/docs/more-pushmsg
289
uri_parser = URI.parse(gotify_address)
290
message_text =
291
"Platform : #{session.platform}\n" \
292
"Tunnel : #{session.tunnel_to_s}\n" \
293
"Arch : #{session.arch}\n" \
294
"Info : > #{session.info ? session.info.to_s : nil}"
295
json_post_data = JSON.pretty_generate({
296
title: "A #{session.platform}/#{session.type} Session is On!",
297
message: message_text,
298
priority: 10
299
})
300
http = Net::HTTP.new(uri_parser.host, uri_parser.port)
301
if !gotify_sslcert_path.nil? && ::File.file?(gotify_sslcert_path) && ::File.readable?(gotify_sslcert_path)
302
http.use_ssl = true
303
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
304
store = OpenSSL::X509::Store.new
305
store.add_file(gotify_sslcert_path)
306
end
307
request = Net::HTTP::Post.new(uri_parser.request_uri)
308
request.content_type = 'application/json'
309
request.body = json_post_data
310
res = http.request(request)
311
if res.nil? || res.body.blank?
312
print_error('No response received from the Gotify server!')
313
return nil
314
end
315
begin
316
body = JSON.parse(res.body)
317
print_status((body['priority'] == 10) ? 'Session notified to Gotify.' : 'Failed to send notification.')
318
rescue JSON::ParserError
319
print_error("Couldn't parse the JSON returned from the Gotify server!")
320
end
321
end
322
323
def send_text_to_serverjang(session)
324
# https://sct.ftqq.com/sendkey
325
uri_parser = URI.parse(serverjang_webhook)
326
params = {}
327
params['title'] = "You have new #{session.type} session"
328
params['desp'] = "OS:#{session.platform}, tunnel:#{session.tunnel_to_s}, Arch:#{session.arch}"
329
http = Net::HTTP.new(uri_parser.host, uri_parser.port)
330
http.use_ssl = true
331
332
res = Net::HTTP.post_form(uri_parser, params)
333
if res.nil? || res.body.blank?
334
print_error('No response received from the ServerJang server!')
335
return nil
336
end
337
338
begin
339
body = JSON.parse(res.body)
340
print_status((body['code'] == 20001) ? 'Failed to send notification.' : 'Session notified to ServerJang.')
341
rescue JSON::ParserError
342
print_error("Couldn't parse the JSON returned from the ServerJang server!")
343
end
344
end
345
346
def notify_session(session, subject, msg)
347
if in_range?(session) && validate_sms_settings?
348
@sms_client.send_text_to_phones([sms_number], subject, msg)
349
print_status("Session notified to: #{sms_number}")
350
end
351
if in_range?(session) && !dingtalk_webhook.nil?
352
send_text_to_dingtalk(session)
353
end
354
if in_range?(session) && !gotify_address.nil?
355
send_text_to_gotify(session)
356
end
357
if in_range?(session) && !serverjang_webhook.nil?
358
send_text_to_serverjang(session)
359
end
360
end
361
362
def in_range?(session)
363
# If both blank, it means we're not setting a range.
364
return true if minimum_ip.blank? && maximum_ip.blank?
365
366
ip = IPAddr.new(session.session_host)
367
368
if minimum_ip && !maximum_ip
369
# There is only a minimum IP
370
minimum_ip < ip
371
elsif !minimum_ip && maximum_ip
372
# There is only a max IP
373
maximum_ip > ip
374
else
375
# Both ends are set
376
range = minimum_ip..maximum_ip
377
range.include?(ip)
378
end
379
end
380
381
def validate_sms_settings?
382
!(smtp_address.nil? || smtp_port.nil? ||
383
smtp_username.nil? || smtp_password.nil? ||
384
smtp_from.nil?)
385
end
386
387
end
388
389
def name
390
'SessionNotifier'
391
end
392
393
def initialize(framework, opts)
394
super
395
add_console_dispatcher(SessionNotifierCommandDispatcher)
396
end
397
398
def cleanup
399
remove_console_dispatcher(name)
400
end
401
402
def desc
403
'This plugin notifies you of a new session via SMS'
404
end
405
406
end
407
end
408
409