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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
|
local ipOps = require "ipOps"
local nmap = require "nmap"
local ospf = require "ospf"
local packet = require "packet"
local stdnse = require "stdnse"
local target = require "target"
local os = require "os"
local string = require "string"
local table = require "table"
local have_ssl, openssl = pcall(require,'openssl')
description = [[
Discover IPv4 networks using Open Shortest Path First version 2(OSPFv2) protocol.
The script works by listening for OSPF Hello packets from the 224.0.0.5
multicast address. The script then replies and attempts to create a neighbor
relationship, in order to discover network database.
If no interface was provided as a script argument or through the -e option,
the script will fail unless a single interface is present on the system.
]]
---
-- @usage
-- nmap --script=broadcast-ospf2-discover
-- nmap --script=broadcast-ospf2-discover -e wlan0
--
-- @args broadcast-ospf2-discover.md5_key MD5 digest key to use if message digest
-- authentication is disclosed.
--
-- @args broadcast-ospf2-discover.router_id Router ID to use. Defaults to 0.0.0.1
--
-- @args broadcast-ospf2-discover.timeout Time in seconds that the script waits for
-- hello from other routers. Defaults to 10 seconds, matching OSPFv2 default
-- value for hello interval.
--
-- @args broadcast-ospf2-discover.interface Interface to send on (overrides -e). Mandatory
-- if not using -e and multiple interfaces are present.
--
-- @output
-- Pre-scan script results:
-- | broadcast-ospf2-discover:
-- | Area ID: 0.0.0.0
-- | External Routes
-- | 192.168.24.0/24
-- |_ Use the newtargets script-arg to add the results as targets
--
author = "Emiliano Ticci"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"broadcast", "discovery", "safe"}
prerule = function()
if nmap.address_family() ~= "inet" then
stdnse.print_verbose("is IPv4 only.")
return false
end
if not nmap.is_privileged() then
stdnse.print_verbose("not running for lack of privileges.")
return false
end
return true
end
-- Script constants
OSPF_ALL_ROUTERS = "224.0.0.5"
OSPF_MSG_HELLO = 0x01
OSPF_MSG_DBDESC = 0x02
OSPF_MSG_LSREQ = 0x03
OSPF_MSG_LSUPD = 0x04
local md5_key, router_id
-- Convenience functions
local function fail(err) return stdnse.format_output(false, err) end
-- Print OSPFv2 LSA Header packet details to debug output.
-- @param hello OSPFv2 LSA Header packet
local ospfDumpLSAHeader = function(lsa_h)
if 2 > nmap.debugging() then
return
end
stdnse.print_debug(2, "| LS Age: %s", lsa_h.age)
stdnse.print_debug(2, "| Options: %s", lsa_h.options)
stdnse.print_debug(2, "| LS Type: %s", lsa_h.type)
stdnse.print_debug(2, "| Link State ID: %s", lsa_h.id)
stdnse.print_debug(2, "| Advertising Router: %s", lsa_h.adv_router)
stdnse.print_debug(2, "| Sequence: 0x%s", lsa_h.sequence)
stdnse.print_debug(2, "| Checksum: 0x%s", lsa_h.checksum)
stdnse.print_debug(2, "| Length: %s", lsa_h.length)
end
-- Print OSPFv2 Database Description packet details to debug output.
-- @param hello OSPFv2 Database Description packet
local ospfDumpDBDesc = function(db_desc)
if 2 > nmap.debugging() then
return
end
stdnse.print_debug(2, "| MTU: %s", db_desc.mtu)
stdnse.print_debug(2, "| Options: %s", db_desc.options)
stdnse.print_debug(2, "| Init: %s", db_desc.init)
stdnse.print_debug(2, "| More: %s", db_desc.more)
stdnse.print_debug(2, "| Master: %s", db_desc.master)
stdnse.print_debug(2, "| Sequence: %s", db_desc.sequence)
if #db_desc.lsa_headers > 0 then
stdnse.print_debug(2, "| LSA Headers:")
for i, lsa_h in ipairs(db_desc.lsa_headers) do
ospfDumpLSAHeader(lsa_h)
if i < #db_desc.lsa_headers then
stdnse.print_debug(2, "|")
end
end
end
end
-- Print OSPFv2 Hello packet details to debug output.
-- @param hello OSPFv2 Hello packet
local ospfDumpHello = function(hello)
if 2 > nmap.debugging() then
return
end
stdnse.print_debug(2, "| Router ID: %s", hello.header.router_id)
stdnse.print_debug(2, "| Area ID: %s", ipOps.fromdword(hello.header.area_id))
stdnse.print_debug(2, "| Checksum: %s", hello.header.chksum)
stdnse.print_debug(2, "| Auth Type: %s", hello.header.auth_type)
if hello.header.auth_type == 0x01 then
stdnse.print_debug(2, "| Auth Password: %s", hello.header.auth_data.password)
elseif hello.header.auth_type == 0x02 then
stdnse.print_debug(2, "| Auth Crypt Key ID: %s", hello.header.auth_data.keyid)
stdnse.print_debug(2, "| Auth Data Length: %s", hello.header.auth_data.length)
stdnse.print_debug(2, "| Auth Crypt Seq: %s", hello.header.auth_data.seq)
end
stdnse.print_debug(2, "| Netmask: %s", hello.netmask)
stdnse.print_debug(2, "| Hello interval: %s", hello.interval)
stdnse.print_debug(2, "| Options: %s", hello.options)
stdnse.print_debug(2, "| Priority: %s", hello.prio)
stdnse.print_debug(2, "| Dead interval: %s", hello.router_dead_interval)
stdnse.print_debug(2, "| Designated Router: %s", hello.DR)
stdnse.print_debug(2, "| Backup Router: %s", hello.BDR)
stdnse.print_debug(2, "| Neighbors: %s", table.concat(hello.neighbors, ","))
end
-- Print OSPFv2 LS Request packet details to debug output.
-- @param ls_req OSPFv2 LS Request packet
local ospfDumpLSRequest = function(ls_req)
if 2 > nmap.debugging() then
return
end
for i, req in ipairs(ls_req.ls_requests) do
stdnse.print_debug(2, "| LS Type: %s", req.type)
stdnse.print_debug(2, "| Link State ID: %s", req.id)
stdnse.print_debug(2, "| Avertising Router: %s", req.adv_router)
if i < #ls_req.ls_requests then
stdnse.print_debug(2, "|")
end
end
end
-- Print OSPFv2 LS Update packet details to debug output.
-- @param ls_upd OSPFv2 LS Update packet
local ospfDumpLSUpdate = function(ls_upd)
stdnse.print_debug(2, "| Number of LSAs: %s", ls_upd.num_lsas)
for i, lsa in ipairs(ls_upd.lsas) do
-- Only Type 1 (Router-LSA) and Type 5 (AS-External-LSA) are supported at the moment
ospfDumpLSAHeader(lsa.header)
if lsa.header.type == 1 then
stdnse.print_debug(2, "| Flags: %s", lsa.flags)
stdnse.print_debug(2, "| Number of links: %s", lsa.num_links)
for j, link in ipairs(lsa.links) do
stdnse.print_debug(2, "| Link ID: %s", link.id)
stdnse.print_debug(2, "| Link Data: %s", link.data)
stdnse.print_debug(2, "| Link Type: %s", link.type)
stdnse.print_debug(2, "| Number of Metrics: %s", link.num_metrics)
stdnse.print_debug(2, "| 0 Metric: %s", link.metric)
if j < #lsa.links then
stdnse.print_debug(2, "|")
end
end
if i < #ls_upd.lsas then
stdnse.print_debug(2, "|")
end
elseif lsa.header.type == 5 then
stdnse.print_debug(2, "| Netmask: %s", lsa.netmask)
stdnse.print_debug(2, "| External Type: %s", lsa.ext_type)
stdnse.print_debug(2, "| Metric: %s", lsa.metric)
stdnse.print_debug(2, "| Forwarding Address: %s", lsa.fw_address)
stdnse.print_debug(2, "| External Route Tag: %s", lsa.ext_tag)
end
end
end
-- Send OSPFv2 packet to specified destination.
-- @param interface Source interface to use
-- @param ip_dst Destination IP address
-- @param mac_dst Destination MAC address
-- @param ospf_packet Raw OSPF packet
local ospfSend = function(interface, ip_dst, mac_dst, ospf_packet)
local dnet = nmap.new_dnet()
local probe = packet.Frame:new()
probe.mac_src = interface.mac
probe.mac_dst = mac_dst
probe.ip_bin_src = ipOps.ip_to_str(interface.address)
probe.ip_bin_dst = ipOps.ip_to_str(ip_dst)
probe.l3_packet = ospf_packet
probe.ip_dsf = 0xc0
probe.ip_p = 89
probe.ip_ttl = 1
probe:build_ip_packet()
probe:build_ether_frame()
dnet:ethernet_open(interface.device)
dnet:ethernet_send(probe.frame_buf)
dnet:ethernet_close()
end
-- Prepare OSPFv2 packet header for reply.
-- @param packet_in Source packet
-- @param packet_out Destination packet
local ospfSetHeader = function(packet_in, packet_out)
packet_out.header:setRouterId(router_id)
packet_out.header:setAreaID(packet_in.header.area_id)
if packet_in.header.auth_type == 0x01 then
packet_out.header.auth_type = 0x01
packet_out.header.auth_data.password = packet_in.header.auth_data.password
elseif packet_in.header.auth_type == 0x02 then
packet_out.header.auth_type = 0x02
packet_out.header.auth_data.key = md5_key
packet_out.header.auth_data.keyid = packet_in.header.auth_data.keyid
packet_out.header.auth_data.length = 16
packet_out.header.auth_data.seq = os.time()
end
return packet_out
end
-- Reply to OSPFv2 Database Description with an LS Request.
-- @param interface Source interface
-- @param mac_dst Destination MAC address
-- @param db_desc_in OSPFv2 Database Description packet to reply for
local ospfSendLSRequest = function(interface, mac_dst, db_desc_in)
local ls_req_out = ospf.OSPF.LSRequest:new()
ls_req_out = ospfSetHeader(db_desc_in, ls_req_out)
for i, lsa_h in ipairs(db_desc_in.lsa_headers) do
ls_req_out:addRequest(lsa_h.type, lsa_h.id, lsa_h.adv_router)
end
stdnse.print_debug(2, "Crafted OSPFv2 LS Request packet with the following parameters:")
ospfDumpLSRequest(ls_req_out)
ospfSend(interface, db_desc_in.header.router_id, mac_dst, tostring(ls_req_out))
end
-- Reply to given OSPFv2 Database Description packet.
-- @param interface Source interface
-- @param mac_dst Destination MAC address
-- @param db_desc_in OSPFv2 Database Description packet to reply for
local ospfReplyDBDesc = function(interface, mac_dst, db_desc_in)
local reply = false
local db_desc_out = ospf.OSPF.DBDescription:new()
db_desc_out = ospfSetHeader(db_desc_in, db_desc_out)
if db_desc_in.init == true then
db_desc_out.init = false
db_desc_out.more = db_desc_in.more
db_desc_out.master = false
db_desc_out.sequence = db_desc_in.sequence
reply = true
elseif #db_desc_in.lsa_headers > 0 then
ospfSendLSRequest(interface, mac_dst, db_desc_in)
return true
end
if reply then
stdnse.print_debug(2, "Crafted OSPFv2 Database Description packet with the following parameters:")
ospfDumpDBDesc(db_desc_out)
ospfSend(interface, db_desc_in.header.router_id, mac_dst, tostring(db_desc_out))
end
return reply
end
-- Reply to given OSPFv2 Hello packet sending another Hello to
-- "All OSPF Routers" multicast address (224.0.0.5).
-- @param interface Source interface
-- @param hello_in OSPFv2 Hello packet to reply for
local ospfReplyHello = function(interface, hello_in)
local hello_out = ospf.OSPF.Hello:new()
hello_out = ospfSetHeader(hello_in, hello_out)
hello_out.interval = hello_in.interval
hello_out.router_dead_interval = hello_in.router_dead_interval
hello_out:setDesignatedRouter(hello_in.header.router_id)
hello_out:setNetmask(hello_in.netmask)
hello_out:addNeighbor(hello_in.header.router_id)
stdnse.print_debug(2, "Crafted OSPFv2 Hello packet with the following parameters:")
ospfDumpHello(hello_out)
ospfSend(interface, OSPF_ALL_ROUTERS, "\x01\x00\x5e\x00\x00\x05", tostring(hello_out))
end
-- Listen for OSPF packets on a specified interface.
-- @param interface Interface to use
-- @param timeout Amount of time to listen in seconds
local ospfListen = function(interface, timeout)
local status, l2_data, l3_data, ospf_raw, _
local start = nmap.clock_ms()
stdnse.print_debug("Start listening on interface %s...", interface.shortname)
local listener = nmap.new_socket()
listener:set_timeout(1000)
listener:pcap_open(interface.device, 1500, true, "ip proto 89 and not (ip src host " .. interface.address .. ")")
while (nmap.clock_ms() - start) < (timeout * 1000) do
status, _, l2_data, l3_data = listener:pcap_receive()
if status then
stdnse.print_debug(2, "Packet received on interface %s.", interface.shortname)
local p = packet.Packet:new(l3_data, #l3_data)
local ospf_raw = string.sub(l3_data, p.ip_hl * 4 + 1)
if ospf_raw:byte(1) == 0x02 and ospf_raw:byte(2) == OSPF_MSG_HELLO then
stdnse.print_debug(2, "OSPFv2 Hello packet detected.")
local ospf_hello = ospf.OSPF.Hello.parse(ospf_raw)
stdnse.print_debug(2, "Captured OSPFv2 Hello packet with the following parameters:")
ospfDumpHello(ospf_hello)
-- Additional checks required for message digest authentication
if ospf_hello.header.auth_type == 0x02 then
if not md5_key then
return fail("Argument md5_key must be present when message digest authentication is disclosed.")
elseif not have_ssl then
return fail("Cannot handle message digest authentication unless openssl is compiled in.")
end
end
ospfReplyHello(interface, ospf_hello)
start = nmap.clock_ms()
elseif ospf_raw:byte(1) == 0x02 and ospf_raw:byte(2) == OSPF_MSG_DBDESC then
stdnse.print_debug(2, "OSPFv2 Database Description packet detected.")
local ospf_db_desc = ospf.OSPF.DBDescription.parse(ospf_raw)
stdnse.print_debug(2, "Captured OSPFv2 Database Description packet with the following parameters:")
ospfDumpDBDesc(ospf_db_desc)
if not ospfReplyDBDesc(interface, string.sub(l2_data, 7, 12), ospf_db_desc) then
return
end
elseif ospf_raw:byte(1) == 0x02 and ospf_raw:byte(2) == OSPF_MSG_LSUPD then
stdnse.print_debug(2, "OSPFv2 LS Update packet detected.")
local ospf_ls_upd = ospf.OSPF.LSUpdate.parse(ospf_raw)
stdnse.print_debug(2, "Captured OSPFv2 LS Update packet with the following parameters:")
ospfDumpLSUpdate(ospf_ls_upd)
local targets = {}
for i, lsa in ipairs(ospf_ls_upd.lsas) do
-- Only Type 1 (Router-LSA) and Type 5 (AS-External-LSA) are supported at the moment
if lsa.header.type == 1 then
for j, link in ipairs(lsa.links) do
if link.type == 3 then
local target = link.id .. ipOps.subnet_to_cidr(link.data)
targets[target] = 1
end
end
elseif lsa.header.type == 5 then
local target = lsa.header.id .. ipOps.subnet_to_cidr(lsa.netmask)
targets[target] = 1
end
end
local output = stdnse.output_table()
if next(targets) then
local out_links = {}
output["Area ID"] = ipOps.fromdword(ospf_ls_upd.header.area_id)
output["External Routes"] = out_links
for t, _ in pairs(targets) do
table.insert(out_links, t)
if target.ALLOW_NEW_TARGETS then
target.add(t)
end
end
if not target.ALLOW_NEW_TARGETS then
stdnse.verbose("Use the newtargets script-arg to add the results as targets")
end
end
return output
end
end
end
listener:pcap_close()
end
action = function()
-- Get script arguments
md5_key = stdnse.get_script_args(SCRIPT_NAME .. ".md5_key") or false
router_id = stdnse.get_script_args(SCRIPT_NAME .. ".router_id") or "0.0.0.1"
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 10
local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
stdnse.print_debug("Value for router ID argument: %s.", router_id)
stdnse.print_debug("Value for timeout argument: %s.", timeout)
-- Determine interface to use
interface = interface or nmap.get_interface()
if interface then
interface = nmap.get_interface_info(interface)
if not interface then
return fail(("Failed to retrieve %s interface information."):format(interface))
end
stdnse.print_debug("Will use %s interface.", interface.shortname)
else
local interface_list = nmap.list_interfaces()
local interface_good = {}
for _, os_interface in ipairs(interface_list) do
if os_interface.address and os_interface.link == "ethernet" and
os_interface.address:match("%d+%.%d+%.%d+%.%d+") then
stdnse.print_debug(2, "Found usable interface: %s.", os_interface.shortname)
table.insert(interface_good, os_interface)
end
end
if #interface_good == 1 then
interface = interface_good[1]
stdnse.print_debug("Will use %s interface.", interface.shortname)
elseif #interface_good == 0 then
return fail("Source interface not found.")
else
return fail("Ambiguous source interface, please specify it with -e or interface parameter.")
end
end
return ospfListen(interface, timeout)
end
|