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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
|
local coroutine = require "coroutine"
local dns = require "dns"
local io = require "io"
local math = require "math"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local stringaux = require "stringaux"
local table = require "table"
local target = require "target"
local rand = require "rand"
description = [[
Attempts to enumerate DNS hostnames by brute force guessing of common
subdomains. With the <code>dns-brute.srv</code> argument, dns-brute will also
try to enumerate common DNS SRV records.
Wildcard records are listed as "*A" and "*AAAA" for IPv4 and IPv6 respectively.
]]
-- 2011-01-26
---
-- @usage
-- nmap --script dns-brute --script-args dns-brute.domain=foo.com,dns-brute.threads=6,dns-brute.hostlist=./hostfile.txt,newtargets -sS -p 80
-- nmap --script dns-brute www.foo.com
-- @args dns-brute.hostlist The filename of a list of host strings to try.
-- Defaults to "nselib/data/vhosts-default.lst"
-- @args dns-brute.threads Thread to use (default 5).
-- @args dns-brute.srv Perform lookup for SRV records
-- @args dns-brute.srvlist The filename of a list of SRV records to try.
-- Defaults to "nselib/data/dns-srv-names"
-- @args dns-brute.domain Domain name to brute force if no host is specified
--
-- @see dns-nsec3-enum.nse
-- @see dns-ip6-arpa-scan.nse
-- @see dns-nsec-enum.nse
-- @see dns-zone-transfer.nse
--
-- @output
-- Pre-scan script results:
-- | dns-brute:
-- | DNS Brute-force hostnames
-- | www.foo.com - 127.0.0.1
-- | mail.foo.com - 127.0.0.2
-- | blog.foo.com - 127.0.1.3
-- | ns1.foo.com - 127.0.0.4
-- | admin.foo.com - 127.0.0.5
-- |_ *A: 127.0.0.123
--
-- @xmloutput
-- <table key="DNS Brute-force hostnames">
-- <table>
-- <elem key="address">127.0.0.1</elem>
-- <elem key="hostname">www.foo.com</elem>
-- </table>
-- <table>
-- <elem key="address">127.0.0.2</elem>
-- <elem key="hostname">mail.foo.com</elem>
-- </table>
-- <table>
-- <elem key="address">127.0.1.3</elem>
-- <elem key="hostname">blog.foo.com</elem>
-- </table>
-- <table>
-- <elem key="address">127.0.0.4</elem>
-- <elem key="hostname">ns1.foo.com</elem>
-- </table>
-- <table>
-- <elem key="address">127.0.0.5</elem>
-- <elem key="hostname">admin.foo.com</elem>
-- </table>
-- <elem key="*A">127.0.0.123</elem>
-- </table>
-- <table key="SRV results"></table>
author = "Cirrus"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "discovery"}
prerule = function()
if not stdnse.get_script_args("dns-brute.domain") then
stdnse.debug1("Skipping '%s' %s, 'dns-brute.domain' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE)
return false
end
return true
end
hostrule = function(host)
return true
end
local function guess_domain(host)
local name
name = stdnse.get_hostname(host)
if name and name ~= host.ip then
return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$")
else
return nil
end
end
-- Single DNS lookup, returning all results. dtype should be e.g. "A", "AAAA".
local function resolve(host, dtype)
local status, result = dns.query(host, {dtype=dtype,retAll=true})
return status and result or false
end
local function array_iter(array, i, j)
return coroutine.wrap(function ()
while i <= j do
coroutine.yield(array[i])
i = i + 1
end
end)
end
local record_mt = {
__tostring = function(t)
return ("%s - %s"):format(t.hostname, t.address)
end
}
local function make_record(hostn, addr)
local record = { hostname=hostn, address=addr }
setmetatable(record, record_mt)
return record
end
local function thread_main(domainname, results, name_iter)
local condvar = nmap.condvar( results )
for name in name_iter do
for _, dtype in ipairs({"A", "AAAA"}) do
local res = resolve(name..'.'..domainname, dtype)
if(res) then
table.sort(res)
if results["*" .. dtype] ~= res[1] then
for _,addr in ipairs(res) do
local hostn = name..'.'..domainname
if target.ALLOW_NEW_TARGETS then
stdnse.debug1("Added target: "..hostn)
local status,err = target.add(hostn)
end
stdnse.debug2("Hostname: "..hostn.." IP: "..addr)
results[#results+1] = make_record(hostn, addr)
end
end
end
end
end
condvar("signal")
end
local function srv_main(domainname, srvresults, srv_iter)
local condvar = nmap.condvar( srvresults )
for name in srv_iter do
local res = resolve(name..'.'..domainname, "SRV")
if(res) then
for _,addr in ipairs(res) do
local hostn = name..'.'..domainname
addr = stringaux.strsplit(":",addr)
for _, dtype in ipairs({"A", "AAAA"}) do
local srvres = resolve(addr[4], dtype)
if(srvres) then
for srvhost,srvip in ipairs(srvres) do
if target.ALLOW_NEW_TARGETS then
stdnse.debug1("Added target: "..srvip)
local status,err = target.add(srvip)
end
stdnse.debug1("Hostname: "..hostn.." IP: "..srvip)
srvresults[#srvresults+1] = make_record(hostn, srvip)
end
end
end
end
end
end
condvar("signal")
end
local function detect_wildcard(domainname, record)
local rand_host1 = rand.random_alpha(24).."."..domainname
local rand_host2 = rand.random_alpha(24).."."..domainname
local res1 = resolve(rand_host1, record)
stdnse.debug1("Detecting wildcard for \"%s\" records using random hostname \"%s\".", record, rand_host1)
if res1 then
stdnse.debug1("Random hostname resolved. Comparing to second random hostname \"%s\".", rand_host2)
local res2 = resolve(rand_host2, record)
table.sort(res1)
table.sort(res2)
if (res1[1] == res2[1]) then
stdnse.debug1("Both random hostnames resolved to the same IP. Wildcard detected.")
return res1[1]
end
end
return nil
end
action = function(host)
local domainname = stdnse.get_script_args('dns-brute.domain')
if not domainname then
domainname = guess_domain(host)
end
if not domainname then
return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME)
end
if not nmap.registry.bruteddomains then
nmap.registry.bruteddomains = {}
end
if nmap.registry.bruteddomains[domainname] then
stdnse.debug1("Skipping already-bruted domain %s", domainname)
return nil
end
nmap.registry.bruteddomains[domainname] = true
stdnse.debug1("Starting dns-brute at: "..domainname)
local max_threads = tonumber( stdnse.get_script_args('dns-brute.threads') ) or 5
local dosrv = stdnse.get_script_args("dns-brute.srv") or false
stdnse.debug1("THREADS: "..max_threads)
-- First look for dns-brute.hostlist
local fileName = stdnse.get_script_args('dns-brute.hostlist')
-- Check fetchfile locations, then relative paths
local commFile = (fileName and nmap.fetchfile(fileName)) or fileName
-- Finally, fall back to vhosts-default.lst
commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst")
local hostlist = {}
if commFile then
for l in io.lines(commFile) do
if not l:match("#!comment:") then
table.insert(hostlist, l)
end
end
else
stdnse.debug1("Cannot find hostlist file, quitting")
return
end
local threads, results, srvresults = {}, {}, {}
for _, dtype in ipairs({"A", "AAAA"}) do
results["*" .. dtype] = detect_wildcard(domainname, dtype)
end
local condvar = nmap.condvar( results )
local i = 1
local howmany = math.floor(#hostlist/max_threads)+1
stdnse.debug1("Hosts per thread: "..howmany)
repeat
local j = math.min(i+howmany, #hostlist)
local name_iter = array_iter(hostlist, i, j)
threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true
i = j+1
until i > #hostlist
local done
-- wait for all threads to finish
while( not(done) ) do
done = true
for thread in pairs(threads) do
if (coroutine.status(thread) ~= "dead") then done = false end
end
if ( not(done) ) then
condvar("wait")
end
end
if(dosrv) then
-- First look for dns-brute.srvlist
fileName = stdnse.get_script_args('dns-brute.srvlist')
-- Check fetchfile locations, then relative paths
commFile = (fileName and nmap.fetchfile(fileName)) or fileName
-- Finally, fall back to dns-srv-names
commFile = commFile or nmap.fetchfile("nselib/data/dns-srv-names")
local srvlist = {}
if commFile then
for l in io.lines(commFile) do
if not l:match("#!comment:") then
table.insert(srvlist, l)
end
end
i = 1
threads = {}
howmany = math.floor(#srvlist/max_threads)+1
condvar = nmap.condvar( srvresults )
stdnse.debug1("SRV's per thread: "..howmany)
repeat
local j = math.min(i+howmany, #srvlist)
local name_iter = array_iter(srvlist, i, j)
threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true
i = j+1
until i > #srvlist
local done
-- wait for all threads to finish
while( not(done) ) do
done = true
for thread in pairs(threads) do
if (coroutine.status(thread) ~= "dead") then done = false end
end
if ( not(done) ) then
condvar("wait")
end
end
else
stdnse.debug1("Cannot find srvlist file, skipping")
end
end
local response = stdnse.output_table()
if(#results==0) then
setmetatable(results, { __tostring = function(t) return "No results." end })
end
response["DNS Brute-force hostnames"] = results
if(dosrv) then
if(#srvresults==0) then
setmetatable(srvresults, { __tostring = function(t) return "No results." end })
end
response["SRV results"] = srvresults
end
return response
end
|