CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/parser/nmap_document.rb
Views: 11780
1
# -*- coding: binary -*-
2
require "rex/parser/nokogiri_doc_mixin"
3
4
module Rex
5
module Parser
6
7
# If Nokogiri is available, define Nmap document class.
8
load_nokogiri && class NmapDocument < Nokogiri::XML::SAX::Document
9
10
include NokogiriDocMixin
11
12
attr_accessor :result
13
def initialize(args, db, &block)
14
@result = Rex::Parser::ParsedResult.new
15
super
16
end
17
18
def determine_port_state(v)
19
case v
20
when "open"
21
Msf::ServiceState::Open
22
when "closed"
23
Msf::ServiceState::Closed
24
when "filtered"
25
Msf::ServiceState::Filtered
26
else
27
Msf::ServiceState::Unknown
28
end
29
end
30
31
# Compare OS fingerprinting data
32
def better_os_match(orig_hash,new_hash)
33
return false unless new_hash.has_key? "accuracy"
34
return true unless orig_hash.has_key? "accuracy"
35
new_hash["accuracy"].to_i > orig_hash["accuracy"].to_i
36
end
37
38
# Triggered every time a new element is encountered. We keep state
39
# ourselves with the @state variable, turning things on when we
40
# get here (and turning things off when we exit in end_element()).
41
def start_element(name=nil,attrs=[])
42
attrs = normalize_attrs(attrs)
43
block = @block
44
@state[:current_tag][name] = true
45
case name
46
when "status"
47
record_host_status(attrs)
48
when "address"
49
record_address(attrs)
50
when "osclass"
51
record_host_osclass(attrs)
52
when "osmatch"
53
record_host_osmatch(attrs)
54
when "uptime"
55
record_host_uptime(attrs)
56
when "hostname"
57
record_hostname(attrs)
58
when "port"
59
record_port(attrs)
60
when "state"
61
record_port_state(attrs)
62
when "service"
63
record_port_service(attrs)
64
when "script"
65
record_port_script(attrs)
66
record_host_script(attrs)
67
record_vuln_script(attrs)
68
# Ignoring post scripts completely
69
when "table"
70
record_vuln_table(attrs)
71
when "elem"
72
record_vuln_values(attrs)
73
when "trace"
74
record_host_trace(attrs)
75
when "hop"
76
record_host_hop(attrs)
77
end
78
end
79
80
# When we exit a tag, this is triggered.
81
def end_element(name=nil)
82
block = @block
83
case name
84
when "os"
85
collect_os_data
86
@state[:os] = {}
87
when "port"
88
collect_port_data
89
@state[:port] = {}
90
when "host" # Roll everything up now
91
collect_host_data
92
host_object = report_host &block
93
if host_object
94
db.report_import_note(@args[:workspace],host_object)
95
report_services(host_object,&block)
96
report_fingerprint(host_object)
97
report_uptime(host_object)
98
report_traceroute(host_object)
99
end
100
@state.delete_if {|k| k != :current_tag}
101
@report_data = {:workspace => @args[:workspace]}
102
when "script"
103
report_vulns
104
end
105
@state[:current_tag].delete name
106
end
107
108
def characters(text)
109
@state[:add_characters] << text if @state[:add_characters]
110
@state.delete(:add_characters)
111
end
112
113
# We can certainly get fancier with self.send() magic, but
114
# leaving this pretty simple for now.
115
116
def record_host_hop(attrs)
117
return unless in_tag("host")
118
return unless in_tag("trace")
119
hops = attr_hash(attrs)
120
hops["name"] = hops.delete "host"
121
@state[:trace][:hops] << hops
122
end
123
124
def record_host_trace(attrs)
125
return unless in_tag("host")
126
@state[:trace] = attr_hash(attrs)
127
@state[:trace][:hops] = []
128
end
129
130
def record_host_uptime(attrs)
131
return unless in_tag("host")
132
@state[:uptime] = attr_hash(attrs)
133
end
134
135
def record_host_osmatch(attrs)
136
return unless in_tag("host")
137
return unless in_tag("os")
138
temp_hash = attr_hash(attrs)
139
if temp_hash["accuracy"].to_i == 100
140
@state[:os] ||= {}
141
@state[:os]["osmatch"] = temp_hash["name"]
142
end
143
end
144
145
def record_host_osclass(attrs)
146
return unless in_tag("host")
147
return unless in_tag("os")
148
@state[:os] ||= {}
149
temp_hash = attr_hash(attrs)
150
if better_os_match(@state[:os],temp_hash)
151
@state[:os] = temp_hash
152
end
153
end
154
155
def record_hostname(attrs)
156
return unless in_tag("host")
157
if attr_hash(attrs)["type"] == "PTR"
158
@state[:hostname] = attr_hash(attrs)["name"]
159
end
160
end
161
162
def record_host_script(attrs)
163
return unless in_tag("host")
164
return if in_tag("port")
165
temp_hash = attr_hash(attrs)
166
167
if temp_hash["id"] and temp_hash["output"]
168
@state[:scripts] ||= []
169
@state[:scripts] << { temp_hash["id"] => temp_hash["output"] }
170
end
171
end
172
173
def record_port_script(attrs)
174
return unless in_tag("host")
175
return unless in_tag("port")
176
temp_hash = attr_hash(attrs)
177
if temp_hash["id"] and temp_hash["output"]
178
@state[:port][:scripts] ||= []
179
@state[:port][:scripts] << { temp_hash["id"] => temp_hash["output"] }
180
end
181
end
182
183
def record_port_service(attrs)
184
return unless in_tag("host")
185
return unless in_tag("port")
186
svc = attr_hash(attrs)
187
if svc["name"] && @args[:fix_services]
188
svc["name"] = db.nmap_msf_service_map(svc["name"])
189
end
190
@state[:port] = @state[:port].merge(svc)
191
end
192
193
def record_port_state(attrs)
194
return unless in_tag("host")
195
return unless in_tag("port")
196
temp_hash = attr_hash(attrs)
197
@state[:port] = @state[:port].merge(temp_hash)
198
end
199
200
def record_port(attrs)
201
return unless in_tag("host")
202
@state[:port] ||= {}
203
svc = attr_hash(attrs)
204
@state[:port] = @state[:port].merge(svc)
205
end
206
207
def record_host_status(attrs)
208
return unless in_tag("host")
209
attrs.each do |k,v|
210
next unless k == "state"
211
if v == 'up'
212
@state[:host_alive] = true
213
else
214
@state[:host_alive] = false
215
end
216
end
217
end
218
219
def record_address(attrs)
220
return unless in_tag("host")
221
@state[:addresses] ||= {}
222
address = nil
223
type = nil
224
attrs.each do |k,v|
225
if k == "addr"
226
address = v
227
elsif k == "addrtype"
228
type = v
229
end
230
end
231
@state[:addresses][type] = address
232
end
233
234
def collect_os_data
235
return unless in_tag("host")
236
if @state[:os]
237
@report_data[:os_fingerprint] = {
238
:type => "host.os.nmap_fingerprint",
239
:data => {
240
:os_vendor => @state[:os]["vendor"],
241
:os_family => @state[:os]["osfamily"],
242
:os_version => @state[:os]["osgen"],
243
:os_accuracy => @state[:os]["accuracy"].to_i
244
}
245
}
246
if @state[:os].has_key? "osmatch"
247
@report_data[:os_fingerprint][:data][:os_match] = @state[:os]["osmatch"]
248
end
249
end
250
end
251
252
def collect_host_data
253
if @state[:host_alive] == true
254
@report_data[:state] = Msf::HostState::Alive
255
elsif @state[:host_alive] == false
256
@report_data[:state] = Msf::HostState::Dead
257
# Default to alive if no host state available (masscan)
258
else
259
@report_data[:state] = Msf::HostState::Alive
260
end
261
if @state[:addresses]
262
if @state[:addresses].has_key? "ipv4"
263
@report_data[:host] = @state[:addresses]["ipv4"]
264
elsif @state[:addresses].has_key? "ipv6"
265
@report_data[:host] = @state[:addresses]["ipv6"]
266
end
267
end
268
if @state[:addresses] and @state[:addresses].has_key?("mac")
269
@report_data[:mac] = @state[:addresses]["mac"]
270
end
271
if @state[:hostname]
272
@report_data[:name] = @state[:hostname]
273
end
274
if @state[:uptime]
275
@report_data[:last_boot] = @state[:uptime]["lastboot"]
276
end
277
if @state[:trace] and @state[:trace].has_key?(:hops)
278
@report_data[:traceroute] = @state[:trace]
279
end
280
if @state[:scripts]
281
@report_data[:scripts] = @state[:scripts]
282
end
283
end
284
285
def collect_port_data
286
return unless in_tag("host")
287
if @args[:fix_services]
288
if @state[:port]["state"] == "filtered"
289
return
290
end
291
end
292
@report_data[:ports] ||= []
293
port_hash = {}
294
extra = []
295
@state[:port].each do |k,v|
296
case k
297
when "protocol"
298
port_hash[:proto] = v
299
when "portid"
300
port_hash[:port] = v
301
when "state"
302
port_hash[:state] = determine_port_state(v)
303
when "name"
304
port_hash[:name] = v
305
when "tunnel"
306
port_hash[:name] = "#{v}/#{port_hash[:name] || 'unknown'}"
307
when "reason"
308
port_hash[:reason] = v
309
when "product"
310
extra[0] = v
311
when "version"
312
extra[1] = v
313
when "extrainfo"
314
extra[2] = v
315
when :scripts
316
port_hash[:scripts] = v
317
end
318
end
319
port_hash[:info] = extra.compact.join(" ") unless extra.empty?
320
# Skip localhost port results when they're unknown
321
if( port_hash[:reason] == "localhost-response" &&
322
port_hash[:state] == Msf::ServiceState::Unknown )
323
@report_data[:ports]
324
else
325
@report_data[:ports] << port_hash
326
end
327
end
328
329
def report_traceroute(host_object)
330
return unless host_object.kind_of? ::Mdm::Host
331
return unless @report_data[:traceroute]
332
tr_note = {
333
:workspace => host_object.workspace,
334
:host => host_object,
335
:type => "host.nmap.traceroute",
336
:data => { 'port' => @report_data[:traceroute]["port"].to_i,
337
'proto' => @report_data[:traceroute]["proto"].to_s,
338
'hops' => @report_data[:traceroute][:hops] }
339
}
340
db_report(:note, tr_note)
341
end
342
343
def report_uptime(host_object)
344
return unless host_object.kind_of? ::Mdm::Host
345
return unless @report_data[:last_boot]
346
up_note = {
347
:workspace => host_object.workspace,
348
:host => host_object,
349
:type => "host.last_boot",
350
:data => { :time => @report_data[:last_boot] }
351
}
352
db_report(:note, up_note)
353
end
354
355
def report_fingerprint(host_object)
356
return unless host_object.kind_of? ::Mdm::Host
357
return unless @report_data[:os_fingerprint]
358
fp_note = @report_data[:os_fingerprint].merge(
359
{
360
:workspace => host_object.workspace,
361
:host => host_object
362
})
363
db_report(:note, fp_note)
364
end
365
366
def report_host(&block)
367
if host_is_okay
368
scripts = @report_data.delete(:scripts) || []
369
host_object = db_report(:host, @report_data.merge( :workspace => @args[:workspace] ) )
370
db.emit(:address,@report_data[:host],&block) if block
371
372
scripts.each do |script|
373
script.each_pair do |k,v|
374
ntype =
375
nse_note = {
376
:workspace => host_object.workspace,
377
:host => host_object,
378
:type => "nmap.nse.#{k}.host",
379
:data => { 'output' => v },
380
:update => :unique_data
381
}
382
db_report(:note, nse_note)
383
end
384
end
385
@result.record_host(host_object)
386
host_object
387
end
388
end
389
390
def report_services(host_object,&block)
391
return unless host_object.kind_of? ::Mdm::Host
392
return unless @report_data[:ports]
393
return if @report_data[:ports].empty?
394
reported = []
395
@report_data[:ports].each do |svc|
396
scripts = svc.delete(:scripts) || []
397
wspace = db.workspaces({:id => host_object.workspace.id}).first
398
svc_obj = db_report(:service, svc.merge(:host => host_object, :workspace => wspace.name))
399
scripts.each do |script|
400
script.each_pair do |k,v|
401
ntype =
402
nse_note = {
403
:workspace => wspace,
404
:host => host_object,
405
:service => svc_obj,
406
:type => "nmap.nse.#{k}." + (svc[:proto] || "tcp") +".#{svc[:port]}",
407
:data => { 'output' => v },
408
:update => :unique_data
409
}
410
db_report(:note, nse_note)
411
end
412
end
413
reported << svc_obj
414
end
415
reported
416
end
417
418
def report_vulns
419
if @state[:vulners]
420
vuln_info = {
421
:workspace => @args[:workspace],
422
:host => @state[:addresses]["ipv4"],
423
:port => @state[:port]["portid"],
424
:proto => @state[:port]["protocol"],
425
:name => @state[:vulners][:cpe],
426
:refs => @state[:vulners][:refs]
427
}
428
db_report(:vuln, vuln_info)
429
@state.delete :vulners
430
end
431
end
432
433
def record_vuln_table(attrs)
434
if attrs.dig(0, 0) == 'key' && @state[:vulners] == {}
435
@state[:vulners][:cpe] = attrs[0][1]
436
@state[:vulners][:refs] = []
437
end
438
end
439
440
def record_vuln_values(attrs)
441
if attrs.dig(0, 0) == 'key' && attrs.dig(0, 1) == 'id' && @state[:vulners]
442
@state[:add_characters] = @state[:vulners][:refs]
443
end
444
end
445
446
def record_vuln_script(attrs)
447
if attrs[0][0] == 'id' && attrs[0][1] == 'vulners'
448
@state[:vulners] = {}
449
end
450
end
451
452
end
453
454
end
455
end
456
457
458