diff --git a/documentation/modules/auxiliary/client/sms/send_text.md b/documentation/modules/auxiliary/client/sms/send_text.md index dcdd5e419b..bbc4dc7bae 100644 --- a/documentation/modules/auxiliary/client/sms/send_text.md +++ b/documentation/modules/auxiliary/client/sms/send_text.md @@ -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: diff --git a/lib/msf/core/auxiliary/sms.rb b/lib/msf/core/auxiliary/sms.rb index 7c15ac319c..a98d1983f0 100644 --- a/lib/msf/core/auxiliary/sms.rb +++ b/lib/msf/core/auxiliary/sms.rb @@ -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 [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 diff --git a/lib/rex/proto/sms/client.rb b/lib/rex/proto/sms/client.rb index 271dafdec1..cede056600 100644 --- a/lib/rex/proto/sms/client.rb +++ b/lib/rex/proto/sms/client.rb @@ -32,10 +32,11 @@ module Rex # Sends a text to multiple recipients. # # @param phone_numbers [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 diff --git a/lib/rex/proto/sms/model.rb b/lib/rex/proto/sms/model.rb index c98ecf6dca..d95d50a35e 100644 --- a/lib/rex/proto/sms/model.rb +++ b/lib/rex/proto/sms/model.rb @@ -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' diff --git a/lib/rex/proto/sms/model/message.rb b/lib/rex/proto/sms/model/message.rb new file mode 100644 index 0000000000..8110c361dc --- /dev/null +++ b/lib/rex/proto/sms/model/message.rb @@ -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 diff --git a/modules/auxiliary/client/sms/send_text.rb b/modules/auxiliary/client/sms/send_text.rb index 8bc0382db4..9b8bf1ddd9 100644 --- a/modules/auxiliary/client/sms/send_text.rb +++ b/modules/auxiliary/client/sms/send_text.rb @@ -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) diff --git a/plugins/session_notifier.rb b/plugins/session_notifier.rb new file mode 100644 index 0000000000..1e26a8f63c --- /dev/null +++ b/plugins/session_notifier.rb @@ -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 diff --git a/spec/lib/rex/proto/sms/client_spec.rb b/spec/lib/rex/proto/sms/client_spec.rb index 688435497f..8ba71b0ed9 100644 --- a/spec/lib/rex/proto/sms/client_spec.rb +++ b/spec/lib/rex/proto/sms/client_spec.rb @@ -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 diff --git a/spec/lib/rex/proto/sms/model/message_spec.rb b/spec/lib/rex/proto/sms/model/message_spec.rb new file mode 100644 index 0000000000..c5ded81907 --- /dev/null +++ b/spec/lib/rex/proto/sms/model/message_spec.rb @@ -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