├── LICENSE ├── .gitignore ├── README.md └── ShellHerder.rb /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chris Maddalena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | ## Specific to RubyMotion: 17 | .dat* 18 | .repl_history 19 | build/ 20 | *.bridgesupport 21 | build-iPhoneOS/ 22 | build-iPhoneSimulator/ 23 | 24 | ## Specific to RubyMotion (use of CocoaPods): 25 | # 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 29 | # 30 | # vendor/Pods/ 31 | 32 | ## Documentation cache and generated files: 33 | /.yardoc/ 34 | /_yardoc/ 35 | /doc/ 36 | /rdoc/ 37 | 38 | ## Environment normalization: 39 | /.bundle/ 40 | /vendor/bundle 41 | /lib/bundler/man/ 42 | 43 | # for a library or gem, you might want to ignore these files since the code is 44 | # intended to run in multiple environments; otherwise, check them in: 45 | # Gemfile.lock 46 | # .ruby-version 47 | # .ruby-gemset 48 | 49 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 50 | .rvmrc 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shell Herder 2 | This Metasploit plugin was created to monitor sessions, new ones and those closing. The idea is to provide a way for consultants to see new sessions coming in when they might not have access to their listener(s). Perhaps you have stepped away from your computer with an active phishing campaign? Maybe you're using a Rubber Ducky in an office and want to get a mobile notification if your payload succeeds? 3 | 4 | A future version use the Metasploit remote API to monitor sessions and Empire REST API to support both Meterpreter sessions and Empire agents. 5 | 6 | ShellHerder uses session subscriptions to monitor activity and then sends an alert to Slack using Slack's Incoming WebHooks. The alert is sent using the WebHook URL and a POST request and will tag a specified username (you could also use @channel or @here) and provide the computer name of the server with the session (if set). 7 | 8 | Ruby gems like slack-notifier are not used because that would require installing a dependency and telling Metasploit to use it. Unfortunately, this can be an annoying process (getting msfconsole to recognize the gem) and your changes can be wiped out when msfconsole is updated. The HTTP requests work just as well and make all of this simpler. 9 | 10 | ## Setup 11 | Place the ShellHerder.rb file inside "/usr/share/metasploit-framework/plugins/" or a folder you have linked to this primary plugins folder. 12 | 13 | Then create a new Incoming WebHook for Slack. You may also want to create a new channel for the alerts, like #shell-alerts. 14 | 15 | ## Sample Usage 16 | The ShellHerder plugin can be used like any other Metasploit plugin. Begin by loading ShellHerder and setting your options. Then you will need to run notify_start to subscribe to session events. See the following example: 17 | 18 | msf exploit(handler) > load notify 19 | 20 | [\*] Successfully loaded plugin: notify 21 | 22 | msf exploit(handler) > notify_set_user @chrismaddalena 23 | 24 | [\*] Setting the Slack handle to @chrismaddalena 25 | 26 | msf exploit(handler) > notify_set_webhook 27 | 28 | [\*] Setting the Webhook URL to 29 | 30 | msf exploit(handler) > notify_set_source Test_VM 31 | 32 | [\*] Setting the Source to Test_VM 33 | 34 | msf exploit(handler) > notify_test 35 | 36 | [\*] Sending tests message 37 | 38 | msf exploit(handler) > 39 | 40 | [\*] Encoded stage with x86/shikata_ga_naiv 41 | 42 | [\*] Sending encoded stage (958029 bytes) to 10.10.1.10 43 | 44 | [\*] Meterpreter session 1 opened (10.10.1.11:4444 -> 10.10.1.10:49713) at 2016-11-15 10:54:23 -0500 45 | 46 | msf exploit(handler) > sessions -k 1 47 | 48 | [\*] Killing the following session(s): 1 49 | 50 | [\*] Killing session 1 51 | 52 | [\*] 10.10.1.10 - Meterpreter session 1 closed. 53 | 54 | This will result in three Slack messages, one confirming setup (notify_test) and one each for the new session and the session being killed. 55 | 56 | <@username> You did it! New session... Source: Test_VM; Session: 1; Platform: Windows; Type: Meterpreter" 57 | <@username> You have made a huge mistake... Source: Test_VM; Session: 1; Reason: Meterpreter is shutting down 58 | -------------------------------------------------------------------------------- /ShellHerder.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Author: Chris Maddalena 3 | # 4 | # Credit to Carlos Perez for the foundation of this - it's a Frankenstein of an 5 | # old, similar plugin of his. However, his is no longer functional or maintained. 6 | ## 7 | 8 | require 'open-uri' 9 | require 'net/http' 10 | require 'net/https' 11 | 12 | module Msf 13 | 14 | class Plugin::Notify < Msf::Plugin 15 | include Msf::SessionEvent 16 | 17 | # Checks if the constant is already set, if not it is set 18 | if not defined?(Notify_yaml) 19 | Notify_yaml = "#{Msf::Config.get_config_root}/Notify.yaml" 20 | end 21 | 22 | 23 | # Initialize the Class 24 | def initialize(framework, opts) 25 | super 26 | add_console_dispatcher(NotifyDispatcher) 27 | end 28 | 29 | 30 | # Cleans up the event subscriber on unload 31 | def cleanup 32 | self.framework.events.remove_session_subscriber(self) 33 | remove_console_dispatcher('notify') 34 | end 35 | 36 | 37 | # Sets the name of the plguin 38 | def name 39 | "notify" 40 | end 41 | 42 | 43 | # Sets the description of the plugin 44 | def desc 45 | "Automatically send Slack notifications when sessions are created and closed." 46 | end 47 | 48 | 49 | # Notify Dispatcher Class 50 | class NotifyDispatcher 51 | include Msf::Ui::Console::CommandDispatcher 52 | 53 | @webhook_url = nil 54 | @user_name = nil 55 | @channel = "#" # An existing channel or one setup for the alerts 56 | @bot_name = "Shell Herder" # Whatever you want the bot's name to be 57 | $source = nil 58 | $opened = Array.new 59 | $closed = Array.new 60 | 61 | 62 | # Actions for when a session is created 63 | def on_session_open(session) 64 | #print_status("Session received, sending push notification") 65 | sendslack("#{@user_name} You did it! New session... Source: #{$source}; Session: #{session.sid}; Platform: #{session.platform}; Type: #{session.type}", "http://emojipedia-us.s3.amazonaws.com/cache/a3/d8/a3d8a52b21c3e628e87001c9d5a2d25d.png", session.sid, "open") 66 | return 67 | end 68 | 69 | 70 | # Actions for when the session is closed 71 | def on_session_close(session,reason = "") 72 | begin 73 | #print_status("Session:#{session.sid} Type:#{session.type} is shutting down") 74 | if reason == "" 75 | reason = "unknown, may have been killed with sessions -k" 76 | end 77 | sendslack("#{@user_name} You have made a huge mistake... Source: #{$source}; Session: #{session.sid}; Reason: #{session.type} is shutting down - #{reason}", "http://emojipedia-us.s3.amazonaws.com/cache/03/e4/03e423c7d30403af03aecbf20276364b.png", session.sid, "close") 78 | rescue 79 | return 80 | end 81 | return 82 | end 83 | 84 | 85 | # Sets the name of the plguin 86 | def name 87 | "notify" 88 | end 89 | 90 | 91 | # Primary function for sending Slack notifications - creates the "notifier" and sends the "ping" 92 | # The arrays and "exclude?" checks prevent spam messages as a result of on_session_* triggering many times 93 | # This is an issue with Metasploit triggering events multiple times very quickly when a session opens or closes 94 | def sendslack(message, icon, session_id, event) 95 | if event == "open" and $opened.exclude?(session_id) 96 | print_status(message) 97 | data = "{'text': '#{message}', 'channel': '#{@channel}', 'username': '#{@bot_name}', 'icon_emoji': '#{icon}'}" 98 | url = URI.parse(@webhook_url) 99 | http = Net::HTTP.new(url.host, url.port) 100 | http.use_ssl = true 101 | resp = http.post(url.path, data) 102 | $opened.push(session_id) 103 | elsif event == "close" and $closed.exclude?(session_id) 104 | print_status(message) 105 | data = "{'text': '#{message}', 'channel': '#{@channel}', 'username': '#{@bot_name}', 'icon_emoji': '#{icon}'}" 106 | url = URI.parse(@webhook_url) 107 | http = Net::HTTP.new(url.host, url.port) 108 | http.use_ssl = true 109 | resp = http.post(url.path, data) 110 | $closed.push(session_id) 111 | end 112 | end 113 | 114 | 115 | # Reads and set the valued from the YAML settings file 116 | def read_settings 117 | read = nil 118 | if File.exist?("#{Notify_yaml}") 119 | ldconfig = YAML.load_file("#{Notify_yaml}") 120 | @webhook_url = ldconfig['webhook_url'] 121 | @user_name = ldconfig['user_name'] 122 | $source = ldconfig['source'] 123 | read = true 124 | else 125 | print_error("You must create a YAML File with the options") 126 | print_error("as: #{Notify_yaml}") 127 | return read 128 | end 129 | return read 130 | end 131 | 132 | 133 | # Sets the commands for the Metasploit plugin 134 | def commands 135 | { 136 | 'notify_help' => "Displays help", 137 | 'notify_start' => "Start Notify Plugin after saving settings.", 138 | 'notify_stop' => "Stop monitoring for new sessions.", 139 | 'notify_test' => "Send test message to make sure confoguration is working.", 140 | 'notify_save' => "Save Settings to YAML File #{Notify_yaml}.", 141 | 'notify_set_webhook' => "Sets Slack Webhook URL.", 142 | 'notify_set_user' => "Set Slack username for messages.", 143 | 'notify_set_source' => "Set source for identifying the souce of the message.", 144 | 'notify_show_options' => "Shows currently set parameters.", 145 | 146 | } 147 | end 148 | 149 | 150 | # Help command to help you help yourself 151 | def cmd_notify_help 152 | puts "Run notify_set_user, notify_set_webhook, and notify_set_source to setup Slack config. Then run notify_save to save them for later. Use notify_test to test your config and load it from the YAML file in the future. Finally, run notify_start when you have your listener setup." 153 | end 154 | 155 | 156 | # Re-Read YAML file and set Slack Webhook API configuration 157 | def cmd_notify_start 158 | print_status "Session activity will be sent to you via Slack Webhooks, channel: #{@channel}" 159 | if read_settings() 160 | self.framework.events.add_session_subscriber(self) 161 | print_good("Notify Plugin Started, Monitoring Sessions") 162 | else 163 | print_error("Could not set Slack Web API settings.") 164 | end 165 | end 166 | 167 | 168 | # Stop the module and unsubscribe from the session events 169 | def cmd_notify_stop 170 | print_status("Stopping the monitoring of sessions to Slack") 171 | self.framework.events.remove_session_subscriber(self) 172 | end 173 | 174 | 175 | # Send a test notification to Slack 176 | def cmd_notify_test 177 | print_status("Sending tests message") 178 | if read_settings() 179 | self.framework.events.add_session_subscriber(self) 180 | data = "{'text': '#{@user_name} Metasploit is online on #{$source}! Hack the Planet!', 'channel': '#{@channel}', 'username': '#{@bot_name}', 'icon_emoji': 'http://emojipedia-us.s3.amazonaws.com/cache/46/2e/462e369e465fd7b52537f6370227b52b.png'}" 181 | url = URI.parse(@webhook_url) 182 | http = Net::HTTP.new(url.host, url.port) 183 | http.use_ssl = true 184 | resp = http.post(url.path, data) 185 | else 186 | print_error("Could not set Slack Web API settings.") 187 | end 188 | end 189 | 190 | 191 | # Save settings to text file for later use 192 | def cmd_notify_save 193 | print_status("Saving options to config file") 194 | if @user_name and @webhook_url and $source 195 | config = {'user_name' => @user_name, 'webhook_url' => @webhook_url, 'source' => $source} 196 | File.open(Notify_yaml, 'w') do |out| 197 | YAML.dump(config, out) 198 | end 199 | print_good("All settings saved to #{Notify_yaml}") 200 | else 201 | print_error("You have not provided all the parameters!") 202 | end 203 | end 204 | 205 | 206 | # Set the username for Slack alerts 207 | def cmd_notify_set_user(*args) 208 | if args.length > 0 209 | print_status("Setting the Slack handle to #{args[0]}") 210 | @user_name = args[0] 211 | else 212 | print_error("Please provide a value") 213 | end 214 | end 215 | 216 | 217 | # Set the Slack Webhook URL - it's hard-coded above 218 | def cmd_notify_set_webhook(*args) 219 | if args.length > 0 220 | print_status("Setting the Webhook URL to #{args[0]}") 221 | @webhook_url = args[0] 222 | else 223 | print_error("Please provide a value") 224 | end 225 | end 226 | 227 | 228 | # Set the message source, e.g. Phish5 229 | def cmd_notify_set_source(*args) 230 | if args.length > 0 231 | print_status("Setting the Source to #{args[0]}") 232 | $source = args[0] 233 | else 234 | print_error("Please provide a value") 235 | end 236 | end 237 | 238 | 239 | # Show the parameters set on the Plug-In 240 | def cmd_notify_show_options 241 | print_status("Parameters:") 242 | print_good("Webhook URL: #{@webhook_url}") 243 | print_good("Slack User: #{@user_name}") 244 | print_good("Source: #{$source}") 245 | end 246 | end 247 | end 248 | end 249 | --------------------------------------------------------------------------------