Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/plugins/session_notifier.rb
Views: 11705
require 'net/https'1require 'net/http'2require 'uri'3module Msf4class Plugin::SessionNotifier < Msf::Plugin56include Msf::SessionEvent78class Exception < ::RuntimeError; end910class SessionNotifierCommandDispatcher1112include Msf::Ui::Console::CommandDispatcher1314attr_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_webhook1516def name17'SessionNotifier'18end1920def commands21{22'set_session_smtp_address' => 'Set the SMTP address for the session notifier',23'set_session_smtp_port' => 'Set the SMTP port for the session notifier',24'set_session_smtp_username' => 'Set the SMTP username',25'set_session_smtp_password' => 'Set the SMTP password',26'set_session_smtp_from' => 'Set the from field of SMTP',27'set_session_mobile_number' => 'Set the 10-digit mobile number you want to notify',28'set_session_mobile_carrier' => 'Set the mobile carrier of the phone',29'set_session_minimum_ip' => 'Set the minimum session IP range you want to be notified for',30'set_session_maximum_ip' => 'Set the maximum session IP range you want to be notified for',31'set_session_dingtalk_webhook' => 'Set the DingTalk webhook for the session notifier (keyword: session).',32'set_session_gotify_address' => 'Set the Gotify address for the session notifier',33'set_session_gotify_sslcert_path' => 'Set the path to load your Gotify SSL cert (if you want to use HTTPS)',34'set_session_serverjang_webhook' => 'Set the ServerJiang webhook for the session notifier (keyword: session).',35'save_session_notifier_settings' => 'Save all the session notifier settings to framework',36'start_session_notifier' => 'Start notifying sessions',37'stop_session_notifier' => 'Stop notifying sessions',38'restart_session_notifier' => 'Restart notifying sessions'39}40end4142def initialize(driver)43super(driver)44load_settings_from_config45end4647def cmd_set_session_smtp_address(*args)48@smtp_address = args[0]49end5051def cmd_set_session_smtp_port(*args)52port = args[0]53if port =~ /^\d+$/54@smtp_port = args[0]55else56print_error('Invalid port setting. Must be a number.')57end58end5960def cmd_set_session_smtp_username(*args)61@smtp_username = args[0]62end6364def cmd_set_session_smtp_password(*args)65@smtp_password = args[0]66end6768def cmd_set_session_smtp_from(*args)69@smtp_from = args[0]70end7172def cmd_set_session_mobile_number(*args)73num = args[0]74if num =~ /^\d{10}$/75@sms_number = args[0]76else77print_error('Invalid phone format. It should be a 10-digit number that looks like: XXXXXXXXXX')78end79end8081def cmd_set_session_mobile_carrier(*args)82@sms_carrier = args[0].to_sym83end8485def cmd_set_session_minimum_ip(*args)86ip = args[0]87if ip.blank?88@minimum_ip = nil89elsif Rex::Socket.dotted_ip?(ip)90@minimum_ip = IPAddr.new(ip)91else92print_error('Invalid IP format')93end94end9596def cmd_set_session_maximum_ip(*args)97ip = args[0]98if ip.blank?99@maximum_ip = nil100elsif Rex::Socket.self.dotted_ip?(ip)101@maximum_ip = IPAddr.new(ip)102else103print_error('Invalid IP format')104end105end106107def cmd_set_session_gotify_address(*args)108webhook_url = args[0]109if webhook_url.blank?110@gotify_address = nil111elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?112@gotify_address = webhook_url113else114@gotify_address = nil115print_error('Invalid gotify_address')116end117end118119def cmd_set_session_gotify_sslcert_path(*args)120cert_path = args[0]121if !cert_path.blank? && ::File.file?(cert_path) && ::File.readable?(cert_path)122@gotify_sslcert_path = cert_path123print_status("Set Gotify ssl_mode ON! Your cert path is #{gotify_sslcert_path}")124else125@gotify_sslcert_path = nil126print_status('Set Gotify ssl_mode OFF!')127end128end129130def cmd_set_session_dingtalk_webhook(*args)131webhook_url = args[0]132if webhook_url.blank?133@dingtalk_webhook = nil134elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?135@dingtalk_webhook = webhook_url136else137print_error('Invalid webhook_url')138end139end140141def cmd_set_session_serverjang_webhook(*args)142webhook_url = args[0]143if webhook_url.blank?144@serverjang_webhook = nil145elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil?146@serverjang_webhook = webhook_url147else148print_error('Invalid webhook_url')149end150end151152def cmd_save_session_notifier_settings(*_args)153save_settings_to_config154print_status('Session Notifier settings saved in config file.')155end156157def cmd_start_session_notifier(*_args)158if session_notifier_subscribed?159print_status('You already have an active session notifier.')160return161end162163begin164framework.events.add_session_subscriber(self)165if validate_sms_settings?166smtp = Rex::Proto::Sms::Model::Smtp.new(167address: smtp_address,168port: smtp_port,169username: smtp_username,170password: smtp_password,171login_type: :login,172from: smtp_from173)174@sms_client = Rex::Proto::Sms::Client.new(carrier: sms_carrier, smtp_server: smtp)175print_status('Session notification started.')176end177if !dingtalk_webhook.nil?178print_status('DingTalk notification started.')179end180if !gotify_address.nil?181print_status('Gotify notification started.')182end183if !serverjang_webhook.nil?184print_status('ServerJang notification started.')185end186rescue Msf::Plugin::SessionNotifier::Exception, Rex::Proto::Sms::Exception => e187print_error(e.message)188end189end190191def cmd_stop_session_notifier(*_args)192framework.events.remove_session_subscriber(self)193print_status('Session notification stopped.')194end195196def cmd_restart_session_notifier(*args)197cmd_stop_session_notifier(args)198cmd_start_session_notifier(args)199end200201def on_session_open(session)202subject = "You have a new #{session.type} session!"203msg = "#{session.tunnel_peer} (#{session.session_host}) #{session.info ? "\"#{session.info}\"" : nil}"204notify_session(session, subject, msg)205end206207private208209def save_settings_to_config210config_file = Msf::Config.config_file211ini = Rex::Parser::Ini.new(config_file)212ini.add_group(name) unless ini[name]213ini[name]['smtp_address'] = smtp_address214ini[name]['smtp_port'] = smtp_port215ini[name]['smtp_username'] = smtp_username216ini[name]['smtp_password'] = smtp_password217ini[name]['smtp_from'] = smtp_from218ini[name]['sms_number'] = sms_number219ini[name]['sms_carrier'] = sms_carrier220ini[name]['minimum_ip'] = minimum_ip.to_s unless minimum_ip.blank?221ini[name]['maximum_ip'] = maximum_ip.to_s unless maximum_ip.blank?222ini[name]['dingtalk_webhook'] = dingtalk_webhook.to_s unless dingtalk_webhook.blank?223ini[name]['gotify_address'] = gotify_address.to_s unless gotify_address.blank?224ini[name]['gotify_sslcert_path'] = gotify_sslcert_path.to_s unless gotify_sslcert_path.blank?225ini[name]['serverjang_webhook'] = serverjang_webhook.to_s unless serverjang_webhook.blank?226ini.to_file(config_file)227end228229def load_settings_from_config230config_file = Msf::Config.config_file231ini = Rex::Parser::Ini.new(config_file)232group = ini[name]233if group234@sms_carrier = group['sms_carrier'].to_sym if group['sms_carrier']235@sms_number = group['sms_number'] if group['sms_number']236@smtp_address = group['smtp_address'] if group['smtp_address']237@smtp_port = group['smtp_port'] if group['smtp_port']238@smtp_username = group['smtp_username'] if group['smtp_username']239@smtp_password = group['smtp_password'] if group['smtp_password']240@smtp_from = group['smtp_from'] if group['smtp_from']241@minimum_ip = IPAddr.new(group['minimum_ip']) if group['minimum_ip']242@maximum_ip = IPAddr.new(group['maximum_ip']) if group['maximum_ip']243@dingtalk_webhook = group['dingtalk_webhook'] if group['dingtalk_webhook']244@gotify_address = group['gotify_address'] if group['gotify_address']245@gotify_sslcert_path = group['gotify_sslcert_path'] if group['gotify_sslcert_path']246@serverjang_webhook = group['serverjang_webhook'] if group['serverjang_webhook']247print_status('Session Notifier settings loaded from config file.')248end249end250251def session_notifier_subscribed?252subscribers = framework.events.instance_variable_get(:@session_event_subscribers).collect(&:class)253subscribers.include?(self.class)254end255256def send_text_to_dingtalk(session)257# https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq/9e91d73c258uri_parser = URI.parse(dingtalk_webhook)259markdown_text = "## You have a new #{session.type} session!\n\n" \260"**platform** : #{session.platform}\n\n" \261"**tunnel** : #{session.tunnel_to_s}\n\n" \262"**arch** : #{session.arch}\n\n" \263"**info** : > #{session.info ? session.info.to_s : nil}"264json_post_data = JSON.pretty_generate({265msgtype: 'markdown',266markdown: { title: 'Session Notifier', text: markdown_text }267})268http = Net::HTTP.new(uri_parser.host, uri_parser.port)269http.use_ssl = true270request = Net::HTTP::Post.new(uri_parser.request_uri)271request.content_type = 'application/json'272request.body = json_post_data273res = http.request(request)274if res.nil? || res.body.blank?275print_error('No response received from the DingTalk server!')276return nil277end278begin279body = JSON.parse(res.body)280print_status((body['errcode'] == 0) ? 'Session notified to DingTalk.' : 'Failed to send notification.')281rescue JSON::ParserError282print_error("Couldn't parse the JSON returned from the DingTalk server!")283end284end285286def send_text_to_gotify(session)287# https://gotify.net/docs/more-pushmsg288uri_parser = URI.parse(gotify_address)289message_text =290"Platform : #{session.platform}\n" \291"Tunnel : #{session.tunnel_to_s}\n" \292"Arch : #{session.arch}\n" \293"Info : > #{session.info ? session.info.to_s : nil}"294json_post_data = JSON.pretty_generate({295title: "A #{session.platform}/#{session.type} Session is On!",296message: message_text,297priority: 10298})299http = Net::HTTP.new(uri_parser.host, uri_parser.port)300if !gotify_sslcert_path.nil? && ::File.file?(gotify_sslcert_path) && ::File.readable?(gotify_sslcert_path)301http.use_ssl = true302http.verify_mode = OpenSSL::SSL::VERIFY_PEER303store = OpenSSL::X509::Store.new304store.add_file(gotify_sslcert_path)305end306request = Net::HTTP::Post.new(uri_parser.request_uri)307request.content_type = 'application/json'308request.body = json_post_data309res = http.request(request)310if res.nil? || res.body.blank?311print_error('No response received from the Gotify server!')312return nil313end314begin315body = JSON.parse(res.body)316print_status((body['priority'] == 10) ? 'Session notified to Gotify.' : 'Failed to send notification.')317rescue JSON::ParserError318print_error("Couldn't parse the JSON returned from the Gotify server!")319end320end321322def send_text_to_serverjang(session)323# https://sct.ftqq.com/sendkey324uri_parser = URI.parse(serverjang_webhook)325params = {}326params['title'] = "You have new #{session.type} session"327params['desp'] = "OS:#{session.platform}, tunnel:#{session.tunnel_to_s}, Arch:#{session.arch}"328http = Net::HTTP.new(uri_parser.host, uri_parser.port)329http.use_ssl = true330331res = Net::HTTP.post_form(uri_parser, params)332if res.nil? || res.body.blank?333print_error('No response received from the ServerJang server!')334return nil335end336337begin338body = JSON.parse(res.body)339print_status((body['code'] == 20001) ? 'Failed to send notification.' : 'Session notified to ServerJang.')340rescue JSON::ParserError341print_error("Couldn't parse the JSON returned from the ServerJang server!")342end343end344345def notify_session(session, subject, msg)346if in_range?(session) && validate_sms_settings?347@sms_client.send_text_to_phones([sms_number], subject, msg)348print_status("Session notified to: #{sms_number}")349end350if in_range?(session) && !dingtalk_webhook.nil?351send_text_to_dingtalk(session)352end353if in_range?(session) && !gotify_address.nil?354send_text_to_gotify(session)355end356if in_range?(session) && !serverjang_webhook.nil?357send_text_to_serverjang(session)358end359end360361def in_range?(session)362# If both blank, it means we're not setting a range.363return true if minimum_ip.blank? && maximum_ip.blank?364365ip = IPAddr.new(session.session_host)366367if minimum_ip && !maximum_ip368# There is only a minimum IP369minimum_ip < ip370elsif !minimum_ip && maximum_ip371# There is only a max IP372maximum_ip > ip373else374# Both ends are set375range = minimum_ip..maximum_ip376range.include?(ip)377end378end379380def validate_sms_settings?381!(smtp_address.nil? || smtp_port.nil? ||382smtp_username.nil? || smtp_password.nil? ||383smtp_from.nil?)384end385386end387388def name389'SessionNotifier'390end391392def initialize(framework, opts)393super394add_console_dispatcher(SessionNotifierCommandDispatcher)395end396397def cleanup398remove_console_dispatcher(name)399end400401def desc402'This plugin notifies you of a new session via SMS'403end404405end406end407408409