Resolve #8026, Add a plugin to notify new sessions via SMS
This plugin will notify you of a new session via SMS. It also changes the SMS text format to MIME. Resolve #8026bug/bundler_fix
parent
a712688895
commit
bb4d6e17c8
|
@ -23,6 +23,10 @@ Remember that these phone numbers must be the same carrier.
|
|||
The carrier that the targeted numbers use. See **Supported Carrier Gateways** to learn more about
|
||||
supported carriers.
|
||||
|
||||
**SMSSUBJECT**
|
||||
|
||||
The text subject.
|
||||
|
||||
**SMSMESSAGE**
|
||||
|
||||
The text message you want to send. For example, this will send a text with a link to google:
|
||||
|
|
|
@ -22,7 +22,8 @@ module Msf
|
|||
OptString.new('SMTPPASSWORD', [true, 'The SMTP password to use to send the text messages']),
|
||||
OptEnum.new('SMSCARRIER', [true, 'The targeted SMS service provider', nil,Rex::Proto::Sms::Model::GATEWAYS.keys.collect { |k| k.to_s }]),
|
||||
OptString.new('CELLNUMBERS', [true, 'The phone numbers to send to']),
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send'])
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send']),
|
||||
OptString.new('SMSSUBJECT', [false, 'The text subject', ''])
|
||||
], Auxiliary::Sms)
|
||||
|
||||
register_advanced_options(
|
||||
|
@ -42,10 +43,11 @@ module Msf
|
|||
# sms.send_text_to_phones(numbers, 'Hello from Gmail')
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of numbers of try (of the same carrier)
|
||||
# @param subject [String] The text subject
|
||||
# @param message [String] The text to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text(phone_numbers, message)
|
||||
def send_text(phone_numbers, subject, message)
|
||||
smtp = Rex::Proto::Sms::Model::Smtp.new(
|
||||
address: datastore['SMTPADDRESS'],
|
||||
port: datastore['SMTPPORT'],
|
||||
|
@ -57,7 +59,7 @@ module Msf
|
|||
|
||||
carrier = datastore['SMSCARRIER'].to_sym
|
||||
sms = Rex::Proto::Sms::Client.new(carrier: carrier, smtp_server: smtp)
|
||||
sms.send_text_to_phones(phone_numbers, message)
|
||||
sms.send_text_to_phones(phone_numbers, subject, message)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -32,10 +32,11 @@ module Rex
|
|||
# Sends a text to multiple recipients.
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of phone numbers.
|
||||
# @param subject [String] Subject of the message
|
||||
# @param message [String] The text message to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text_to_phones(phone_numbers, message)
|
||||
def send_text_to_phones(phone_numbers, subject, message)
|
||||
carrier = Rex::Proto::Sms::Model::GATEWAYS[self.carrier]
|
||||
recipients = phone_numbers.collect { |p| "#{p}@#{carrier}" }
|
||||
address = self.smtp_server.address
|
||||
|
@ -52,7 +53,13 @@ module Rex
|
|||
smtp.enable_starttls_auto
|
||||
smtp.start(helo_domain, username, password, login_type) do
|
||||
recipients.each do |r|
|
||||
smtp.send_message(message, from, r)
|
||||
sms_message = Rex::Proto::Sms::Model::Message.new(
|
||||
from: from,
|
||||
to: r,
|
||||
subject: subject,
|
||||
message: message
|
||||
)
|
||||
smtp.send_message(sms_message.to_s, from, r)
|
||||
end
|
||||
end
|
||||
rescue Net::SMTPAuthenticationError => e
|
||||
|
|
|
@ -28,4 +28,5 @@ end
|
|||
|
||||
require 'net/smtp'
|
||||
require 'rex/proto/sms/model/smtp'
|
||||
require 'rex/proto/sms/model/message'
|
||||
require 'rex/proto/sms/client'
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Sms
|
||||
module Model
|
||||
class Message
|
||||
|
||||
# @!attribute message
|
||||
# @return [String] The text message
|
||||
attr_accessor :message
|
||||
|
||||
|
||||
# @!attribute from
|
||||
# @return [String] The from field in the email
|
||||
attr_accessor :from
|
||||
|
||||
# @!attribute to
|
||||
# @return [String] The to field in the email
|
||||
attr_accessor :to
|
||||
|
||||
# @!attribute subject
|
||||
# @return [String] The subject of the email
|
||||
attr_accessor :subject
|
||||
|
||||
|
||||
# Initializes the SMTP object.
|
||||
#
|
||||
# @param [Hash] opts
|
||||
# @option opts [String] :from
|
||||
# @option opts [String] :to
|
||||
# @option opts [String] :message
|
||||
#
|
||||
# @return [Rex::Proto::Sms::Model::Message]
|
||||
def initialize(opts={})
|
||||
self.from = opts[:from]
|
||||
self.to = opts[:to]
|
||||
self.message = opts[:message]
|
||||
self.subject = opts[:subject]
|
||||
end
|
||||
|
||||
|
||||
# Returns the raw SMS message
|
||||
#
|
||||
# @return [String]
|
||||
def to_s
|
||||
body = Rex::MIME::Message.new
|
||||
body.add_part(self.message, 'text/plain; charset=UTF-8', nil)
|
||||
|
||||
sms = "MIME-Version: 1.0\n"
|
||||
sms << "From: #{self.from}\n"
|
||||
sms << "To: #{self.to}\n"
|
||||
sms << "Subject: #{self.subject}\n"
|
||||
sms << "Content-Type: multipart/alternative; boundary=#{body.bound}\n"
|
||||
sms << "\n"
|
||||
sms << body.to_s
|
||||
|
||||
sms
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
phone_numbers = datastore['CELLNUMBERS'].split
|
||||
print_status("Sending text (#{datastore['SMSMESSAGE'].length} bytes) to #{phone_numbers.length} number(s)...")
|
||||
begin
|
||||
res = send_text(phone_numbers, datastore['SMSMESSAGE'])
|
||||
res = send_text(phone_numbers, datastore['SMSSUBJECT'], datastore['SMSMESSAGE'])
|
||||
print_status("Done.")
|
||||
rescue Rex::Proto::Sms::Exception => e
|
||||
print_error(e.message)
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
module Msf
|
||||
class Plugin::SessionNotifier < Msf::Plugin
|
||||
|
||||
include Msf::SessionEvent
|
||||
|
||||
class Exception < ::RuntimeError ; end
|
||||
|
||||
class SessionNotifierCommandDispatcher
|
||||
|
||||
include Msf::Ui::Console::CommandDispatcher
|
||||
|
||||
attr_reader :sms_client
|
||||
attr_reader :sms_carrier
|
||||
attr_reader :sms_number
|
||||
attr_reader :smtp_address
|
||||
attr_reader :smtp_port
|
||||
attr_reader :smtp_username
|
||||
attr_reader :smtp_password
|
||||
attr_reader :smtp_from
|
||||
attr_reader :minimum_ip
|
||||
attr_reader :maximum_ip
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def commands
|
||||
{
|
||||
'set_session_smtp_address' => 'Set the SMTP address for the session notifier',
|
||||
'set_session_smtp_port' => 'Set the SMTP port for the session notifier',
|
||||
'set_session_smtp_username' => 'Set the SMTP username',
|
||||
'set_session_smtp_password' => 'Set the SMTP password',
|
||||
'set_session_smtp_from' => 'Set the from field of SMTP',
|
||||
'set_session_mobile_number' => 'Set the 10-digit mobile number you want to notify',
|
||||
'set_session_mobile_carrier' => 'Set the mobile carrier of the phone',
|
||||
'set_session_minimum_ip' => 'Set the minimum session IP range you want to be notified for',
|
||||
'set_session_maximum_ip' => 'Set the maximum session IP range you want to be notified for',
|
||||
'save_session_notifier_settings' => 'Save all the session notifier settings to framework',
|
||||
'start_session_notifier' => 'Start notifying sessions',
|
||||
'stop_session_notifier' => 'Stop notifying sessions',
|
||||
'restart_session_notifier' => 'Restart notifying sessions'
|
||||
}
|
||||
end
|
||||
|
||||
def initialize(driver)
|
||||
super(driver)
|
||||
load_settings_from_config
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_address(*args)
|
||||
@smtp_address = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_port(*args)
|
||||
port = args[0]
|
||||
if port =~ /^\d+$/
|
||||
@smtp_port = args[0]
|
||||
else
|
||||
print_error('Invalid port setting. Must be a number.')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_username(*args)
|
||||
@smtp_username = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_password(*args)
|
||||
@smtp_password = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_from(*args)
|
||||
@smtp_from = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_mobile_number(*args)
|
||||
num = args[0]
|
||||
if num =~ /^\d{10}$/
|
||||
@sms_number = args[0]
|
||||
else
|
||||
print_error('Invalid phone format. It should be a 10-digit number that looks like: XXXXXXXXXX')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_mobile_carrier(*args)
|
||||
@sms_carrier = args[0].to_sym
|
||||
end
|
||||
|
||||
def cmd_set_session_minimum_ip(*args)
|
||||
ip = args[0]
|
||||
if ip.blank?
|
||||
@minimum_ip = nil
|
||||
elsif Rex::Socket.dotted_ip?(ip)
|
||||
@minimum_ip = IPAddr.new(ip)
|
||||
else
|
||||
print_error('Invalid IP format')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_maximum_ip(*args)
|
||||
ip = args[0]
|
||||
if ip.blank?
|
||||
@maximum_ip = nil
|
||||
elsif Rex::Socket.self.dotted_ip?(ip)
|
||||
@maximum_ip = IPAddr.new(ip)
|
||||
else
|
||||
print_error('Invalid IP format')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_save_session_notifier_settings(*args)
|
||||
save_settings_to_config
|
||||
print_status("Session Notifier settings saved in config file.")
|
||||
end
|
||||
|
||||
def cmd_start_session_notifier(*args)
|
||||
if is_session_notifier_subscribed?
|
||||
print_status('You already have an active session notifier.')
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
validate_settings!
|
||||
self.framework.events.add_session_subscriber(self)
|
||||
smtp = Rex::Proto::Sms::Model::Smtp.new(
|
||||
address: self.smtp_address,
|
||||
port: self.smtp_port,
|
||||
username: self.smtp_username,
|
||||
password: self.smtp_password,
|
||||
login_type: :login,
|
||||
from: self.smtp_from
|
||||
)
|
||||
@sms_client = Rex::Proto::Sms::Client.new(carrier: self.sms_carrier, smtp_server: smtp)
|
||||
print_status("Session notification started.")
|
||||
rescue Msf::Plugin::SessionNotifier::Exception, Rex::Proto::Sms::Exception => e
|
||||
print_error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_stop_session_notifier(*args)
|
||||
self.framework.events.remove_session_subscriber(self)
|
||||
print_status("Session notification stopped.")
|
||||
end
|
||||
|
||||
def cmd_restart_session_notifier(*args)
|
||||
cmd_stop_session_notifier(args)
|
||||
cmd_start_session_notifier(args)
|
||||
end
|
||||
|
||||
def on_session_open(session)
|
||||
subject = "You have a new #{session.type} session!"
|
||||
msg = "#{session.tunnel_peer} (#{session.session_host}) #{session.info ? "\"#{session.info.to_s}\"" : nil}"
|
||||
notify_session(session, subject, msg)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_settings_to_config
|
||||
config_file = Msf::Config.config_file
|
||||
ini = Rex::Parser::Ini.new(config_file)
|
||||
ini.add_group(name) unless ini[name]
|
||||
ini[name]['smtp_address'] = self.smtp_address
|
||||
ini[name]['smtp_port'] = self.smtp_port
|
||||
ini[name]['smtp_username'] = self.smtp_username
|
||||
ini[name]['smtp_password'] = self.smtp_password
|
||||
ini[name]['smtp_from'] = self.smtp_from
|
||||
ini[name]['sms_number'] = self.sms_number
|
||||
ini[name]['sms_carrier'] = self.sms_carrier
|
||||
ini[name]['minimum_ip'] = self.minimum_ip.to_s
|
||||
ini[name]['maximum_ip'] = self.maximum_ip.to_s
|
||||
ini.to_file(config_file)
|
||||
end
|
||||
|
||||
def load_settings_from_config
|
||||
config_file = Msf::Config.config_file
|
||||
ini = Rex::Parser::Ini.new(config_file)
|
||||
group = ini[name]
|
||||
if group
|
||||
@sms_carrier = group['sms_carrier'] if group['sms_carrier']
|
||||
@sms_number = group['sms_number'] if group['sms_number']
|
||||
@smtp_address = group['smtp_address'] if group['smtp_address']
|
||||
@smtp_port = group['smtp_port'] if group['smtp_port']
|
||||
@smtp_username = group['smtp_username'] if group['smtp_username']
|
||||
@smtp_password = group['smtp_password'] if group['smtp_password']
|
||||
@smtp_from = group['smtp_from'] if group['smtp_from']
|
||||
@minimum_ip = IPAddr.new(group['minimum_ip']) if group['minimum_ip']
|
||||
@maximum_ip = IPAddr.new(group['maximum_ip']) if group['maximum_ip']
|
||||
|
||||
print_status('Session Notifier settings loaded from config file.')
|
||||
end
|
||||
end
|
||||
|
||||
def is_session_notifier_subscribed?
|
||||
subscribers = framework.events.instance_variable_get(:@session_event_subscribers).collect { |s| s.class }
|
||||
subscribers.include?(self.class)
|
||||
end
|
||||
|
||||
def notify_session(session, subject, msg)
|
||||
if is_in_range?(session)
|
||||
@sms_client.send_text_to_phones([self.sms_number], subject, msg)
|
||||
print_status("Session notified to: #{self.sms_number}")
|
||||
end
|
||||
end
|
||||
|
||||
def is_in_range?(session)
|
||||
# If both blank, it means we're not setting a range.
|
||||
return true if self.minimum_ip.blank? && self.maximum_ip.blank?
|
||||
|
||||
ip = IPAddr.new(session.session_host)
|
||||
|
||||
if self.minimum_ip && !self.maximum_ip
|
||||
# There is only a minimum IP
|
||||
self.minimum_ip < ip
|
||||
elsif !self.minimum_ip && self.maximum_ip
|
||||
# There is only a max IP
|
||||
self.maximum_ip > ip
|
||||
else
|
||||
# Both ends are set
|
||||
range = self.minimum_ip..self.maximum_ip
|
||||
range.include?(ip)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_settings!
|
||||
if self.smtp_address.nil? || self.smtp_port.nil? ||
|
||||
self.smtp_username.nil? || self.smtp_password.nil? ||
|
||||
self.smtp_from.nil?
|
||||
raise Msf::Plugin::SessionNotifier::Exception, "All Session Notifier's settings must be configured."
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def initialize(framework, opts)
|
||||
super
|
||||
add_console_dispatcher(SessionNotifierCommandDispatcher)
|
||||
end
|
||||
|
||||
def cleanup
|
||||
remove_console_dispatcher(name)
|
||||
end
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def desc
|
||||
'This plugin notifies you a new session via SMS.'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -6,6 +6,8 @@ RSpec.describe Rex::Proto::Sms::Client do
|
|||
|
||||
let(:phone_numbers) { ['1112223333'] }
|
||||
|
||||
let(:sms_subject) { 'subject' }
|
||||
|
||||
let(:message) { 'message' }
|
||||
|
||||
let(:carrier) { :verizon }
|
||||
|
@ -45,8 +47,8 @@ RSpec.describe Rex::Proto::Sms::Client do
|
|||
end
|
||||
|
||||
it 'sends a text message' do
|
||||
subject.send_text_to_phones(phone_numbers, message)
|
||||
expect(@sent_message).to eq(message)
|
||||
subject.send_text_to_phones(phone_numbers, sms_subject, message)
|
||||
expect(@sent_message).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
require 'spec_helper'
|
||||
require 'rex/proto/sms/model'
|
||||
|
||||
RSpec.describe Rex::Proto::Sms::Model::Message do
|
||||
|
||||
let(:message) { 'message' }
|
||||
let(:from) { 'sender@example.com' }
|
||||
let(:to) { 'receiver@example.com' }
|
||||
let(:sms_subject) { 'subject' }
|
||||
|
||||
subject do
|
||||
described_class.new(
|
||||
from: from,
|
||||
to: to,
|
||||
subject: sms_subject,
|
||||
message: message,
|
||||
)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets message' do
|
||||
expect(subject.message).to eq(message)
|
||||
end
|
||||
|
||||
it 'sets from' do
|
||||
expect(subject.from).to eq(from)
|
||||
end
|
||||
|
||||
it 'sets to' do
|
||||
expect(subject.to).to eq(to)
|
||||
end
|
||||
|
||||
it 'sets subject' do
|
||||
expect(subject.subject).to eq(sms_subject)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_s' do
|
||||
it 'returns the sms message' do
|
||||
expect(subject.to_s).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue