diff --git a/plugins/session_notifier.rb b/plugins/session_notifier.rb index 63977fe0d0..d6794ec8ad 100644 --- a/plugins/session_notifier.rb +++ b/plugins/session_notifier.rb @@ -1,9 +1,12 @@ +require 'net/https' +require 'net/http' +require 'uri' module Msf class Plugin::SessionNotifier < Msf::Plugin include Msf::SessionEvent - class Exception < ::RuntimeError ; end + class Exception < ::RuntimeError; end class SessionNotifierCommandDispatcher @@ -19,6 +22,7 @@ module Msf attr_reader :smtp_from attr_reader :minimum_ip attr_reader :maximum_ip + attr_reader :dingtalk_webhook def name 'SessionNotifier' @@ -35,6 +39,7 @@ module Msf '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', + 'set_session_dingtalk_webhook' => 'Set the DingTalk webhook for the session notifier (keyword: session).', 'save_session_notifier_settings' => 'Save all the session notifier settings to framework', 'start_session_notifier' => 'Start notifying sessions', 'stop_session_notifier' => 'Stop notifying sessions', @@ -107,38 +112,52 @@ module Msf end end - def cmd_save_session_notifier_settings(*args) - save_settings_to_config - print_status("Session Notifier settings saved in config file.") + def cmd_set_session_dingtalk_webhook(*args) + webhook_url = args[0] + if webhook_url.blank? + @dingtalk_webhook = nil + elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil? + @dingtalk_webhook = webhook_url + else + print_error('Invalid webhook_url') + end end - def cmd_start_session_notifier(*args) - if is_session_notifier_subscribed? + 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 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.") + framework.events.add_session_subscriber(self) + if validate_sms_settings? + smtp = Rex::Proto::Sms::Model::Smtp.new( + address: smtp_address, + port: smtp_port, + username: smtp_username, + password: smtp_password, + login_type: :login, + from: smtp_from + ) + @sms_client = Rex::Proto::Sms::Client.new(carrier: sms_carrier, smtp_server: smtp) + print_status('Session notification started.') + elsif !dingtalk_webhook.nil? + print_status('DingTalk notification started.') + end 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.") + def cmd_stop_session_notifier(*_args) + framework.events.remove_session_subscriber(self) + print_status('Session notification stopped.') end def cmd_restart_session_notifier(*args) @@ -148,7 +167,7 @@ module Msf 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}" + msg = "#{session.tunnel_peer} (#{session.session_host}) #{session.info ? "\"#{session.info}\"" : nil}" notify_session(session, subject, msg) end @@ -158,15 +177,16 @@ module Msf 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 unless self.minimum_ip.blank? - ini[name]['maximum_ip'] = self.maximum_ip.to_s unless self.maximum_ip.blank? + ini[name]['smtp_address'] = smtp_address + ini[name]['smtp_port'] = smtp_port + ini[name]['smtp_username'] = smtp_username + ini[name]['smtp_password'] = smtp_password + ini[name]['smtp_from'] = smtp_from + ini[name]['sms_number'] = sms_number + ini[name]['sms_carrier'] = sms_carrier + ini[name]['minimum_ip'] = minimum_ip.to_s unless minimum_ip.blank? + ini[name]['maximum_ip'] = maximum_ip.to_s unless maximum_ip.blank? + ini[name]['dingtalk_webhook'] = dingtalk_webhook.to_s unless dingtalk_webhook.blank? ini.to_file(config_file) end @@ -175,57 +195,81 @@ module Msf ini = Rex::Parser::Ini.new(config_file) group = ini[name] if group - @sms_carrier = group['sms_carrier'].to_sym 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'] + @sms_carrier = group['sms_carrier'].to_sym 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'] + @dingtalk_webhook = group['dingtalk_webhook'] if group['dingtalk_webhook'] 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 } + def session_notifier_subscribed? + subscribers = framework.events.instance_variable_get(:@session_event_subscribers).collect(&:class) subscribers.include?(self.class) end + def send_text_to_dingtalk(session) + # https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq/9e91d73c + uri_parser = URI.parse(dingtalk_webhook) + markdown_text = "## You have a new #{session.type} session!\n\n" \ + "**platform** : #{session.platform}\n\n" \ + "**tunnel** : #{session.tunnel_to_s}\n\n" \ + "**arch** : #{session.arch}\n\n" \ + "**info** : > #{session.info ? session.info.to_s : nil}" + json_post_data = JSON.pretty_generate({ + msgtype: 'markdown', + markdown: { title: 'Session Notifier', text: markdown_text } + }) + http = Net::HTTP.new(uri_parser.host, uri_parser.port) + http.use_ssl = true + request = Net::HTTP::Post.new(uri_parser.request_uri) + request.content_type = 'application/json' + request.body = json_post_data + res = http.request(request) + body = JSON.parse(res.body) + print_status((body['errcode'] == 0) ? 'Session notified to DingTalk.' : 'Failed to send notification.') + 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}") + if in_range?(session) && validate_sms_settings? + @sms_client.send_text_to_phones([sms_number], subject, msg) + print_status("Session notified to: #{sms_number}") + end + if in_range?(session) && !dingtalk_webhook.nil? + send_text_to_dingtalk(session) end end - def is_in_range?(session) + def 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? + return true if minimum_ip.blank? && maximum_ip.blank? ip = IPAddr.new(session.session_host) - if self.minimum_ip && !self.maximum_ip + if minimum_ip && !maximum_ip # There is only a minimum IP - self.minimum_ip < ip - elsif !self.minimum_ip && self.maximum_ip + minimum_ip < ip + elsif !minimum_ip && maximum_ip # There is only a max IP - self.maximum_ip > ip + maximum_ip > ip else # Both ends are set - range = self.minimum_ip..self.maximum_ip + range = minimum_ip..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 + def validate_sms_settings? + !(smtp_address.nil? || smtp_port.nil? || + smtp_username.nil? || smtp_password.nil? || + smtp_from.nil?) end end @@ -243,10 +287,6 @@ module Msf remove_console_dispatcher(name) end - def name - 'SessionNotifier' - end - def desc 'This plugin notifies you a new session via SMS.' end