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/foundstone_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 Template document class.
8
load_nokogiri && class FoundstoneDocument < Nokogiri::XML::SAX::Document
9
10
include NokogiriDocMixin
11
12
def start_document
13
@report_type_ok = true # Optimistic
14
end
15
16
# Triggered every time a new element is encountered. We keep state
17
# ourselves with the @state variable, turning things on when we
18
# get here (and turning things off when we exit in end_element()).
19
def start_element(name=nil,attrs=[])
20
attrs = normalize_attrs(attrs)
21
block = @block
22
return unless @report_type_ok
23
@state[:current_tag][name] = true
24
case name
25
when "ReportInfo"
26
check_for_correct_report_type(attrs,&block)
27
when "Host"
28
record_host(attrs)
29
when "Service"
30
record_service(attrs)
31
when "Port", "Protocol", "Banner"
32
@state[:has_text] = true
33
when "Vuln" # under VulnsFound, ignore risk 0 things
34
record_vuln(attrs)
35
when "Risk" # for Vuln
36
@state[:has_text] = true
37
when "CVE" # Under Vuln
38
@state[:has_text] = true
39
end
40
end
41
42
# When we exit a tag, this is triggered.
43
def end_element(name=nil)
44
block = @block
45
return unless @report_type_ok
46
case name
47
when "Host" # Wrap it up
48
collect_host_data
49
host_object = report_host &block
50
if host_object
51
db.report_import_note(@args[:workspace],host_object)
52
report_fingerprint(host_object)
53
report_services(host_object)
54
report_vulns(host_object)
55
end
56
# Reset the state once we close a host
57
@state.delete_if {|k| k != :current_tag}
58
@report_data = {:workspace => args[:workspace]}
59
when "Port"
60
@state[:has_text] = false
61
collect_port
62
when "Protocol"
63
@state[:has_text] = false
64
collect_protocol
65
when "Banner"
66
collect_banner
67
@state[:has_text] = false
68
when "Service"
69
collect_service
70
when "Vuln"
71
collect_vuln
72
when "Risk"
73
@state[:has_text] = false
74
collect_risk
75
when "CVE"
76
@state[:has_text] = false
77
collect_cve
78
end
79
@state[:current_tag].delete name
80
end
81
82
# Nothing technically stopping us from parsing this as well,
83
# but saving this for later
84
def check_for_correct_report_type(attrs,&block)
85
report_type = attr_hash(attrs)["ReportType"]
86
if report_type == "Network Inventory"
87
@report_type_ok = true
88
else
89
if report_type == "Risk Data"
90
msg = "The Foundstone/Mcafee report type '#{report_type}' is not currently supported"
91
msg << ",\nso no data will be imported. Please use the 'Network Inventory' report instead."
92
else
93
msg = ".\nThe Foundstone/Macafee report type '#{report_type}' is unsupported."
94
end
95
db.emit(:warning,msg,&block) if block
96
@report_type_ok = false
97
end
98
end
99
100
def collect_risk
101
return unless in_tag("VulnsFound")
102
return unless in_tag("HostData")
103
return unless in_tag("Host")
104
risk = @text.to_s.to_i
105
@state[:vuln][:risk] = risk
106
@text = nil
107
end
108
109
def collect_cve
110
return unless in_tag("VulnsFound")
111
return unless in_tag("HostData")
112
return unless in_tag("Host")
113
cve = @text.to_s
114
@state[:vuln][:cves] ||= []
115
@state[:vuln][:cves] << cve unless cve == "CVE-MAP-NOMATCH"
116
@text = nil
117
end
118
119
# Determines if we should keep the vuln or not. Note that
120
# we cannot tie them to a service.
121
def collect_vuln
122
return unless in_tag("VulnsFound")
123
return unless in_tag("HostData")
124
return unless in_tag("Host")
125
return if @state[:vuln][:risk] == 0
126
@report_data[:vulns] ||= []
127
vuln_hash = {}
128
vuln_hash[:name] = @state[:vuln]["VulnName"]
129
refs = []
130
refs << "FID-#{@state[:vuln]["id"]}"
131
if @state[:vuln][:cves]
132
@state[:vuln][:cves].each {|cve| refs << cve}
133
end
134
vuln_hash[:refs] = refs
135
@report_data[:vulns] << vuln_hash
136
end
137
138
# These are per host.
139
def record_vuln(attrs)
140
return unless in_tag("VulnsFound")
141
return unless in_tag("HostData")
142
return unless in_tag("Host")
143
@state[:vulns] ||= []
144
145
@state[:vuln] = attr_hash(attrs) # id and VulnName
146
end
147
148
def record_service(attrs)
149
return unless in_tag("ServicesFound")
150
return unless in_tag("Host")
151
@state[:service] = attr_hash(attrs)
152
end
153
154
def collect_port
155
return unless in_tag("Service")
156
return unless in_tag("ServicesFound")
157
return unless in_tag("Host")
158
return if @text.nil? || @text.empty?
159
@state[:service][:port] = @text.strip
160
@text = nil
161
end
162
163
def collect_protocol
164
return unless in_tag("Service")
165
return unless in_tag("ServicesFound")
166
return unless in_tag("Host")
167
return if @text.nil? || @text.empty?
168
@state[:service][:proto] = @text.strip
169
@text = nil
170
end
171
172
def collect_banner
173
return unless in_tag("Service")
174
return unless in_tag("ServicesFound")
175
return unless in_tag("Host")
176
return if @text.nil? || @text.empty?
177
banner = normalize_foundstone_banner(@state[:service]["ServiceName"],@text)
178
unless banner.nil? || banner.empty?
179
@state[:service][:banner] = banner
180
end
181
@text = nil
182
end
183
184
def collect_service
185
return unless in_tag("ServicesFound")
186
return unless in_tag("Host")
187
return unless @state[:service][:port]
188
@report_data[:ports] ||= []
189
port_hash = {}
190
port_hash[:port] = @state[:service][:port]
191
port_hash[:proto] = @state[:service][:proto]
192
port_hash[:info] = @state[:service][:banner]
193
port_hash[:name] = db.nmap_msf_service_map(@state[:service]["ServiceName"])
194
@report_data[:ports] << port_hash
195
end
196
197
def record_host(attrs)
198
return unless in_tag("HostData")
199
@state[:host] = attr_hash(attrs)
200
end
201
202
def collect_host_data
203
@report_data[:host] = @state[:host]["IPAddress"]
204
if @state[:host]["NBName"] && !@state[:host]["NBName"].empty?
205
@report_data[:name] = @state[:host]["NBName"]
206
elsif @state[:host]["DNSName"] && !@state[:host]["DNSName"].empty?
207
@report_data[:name] = @state[:host]["DNSName"]
208
end
209
if @state[:host]["OSName"] && !@state[:host]["OSName"].empty?
210
@report_data[:os_fingerprint] = @state[:host]["OSName"]
211
end
212
@report_data[:state] = Msf::HostState::Alive
213
@report_data[:mac] = @state[:mac] if @state[:mac]
214
end
215
216
def report_host(&block)
217
return unless in_tag("HostData")
218
if host_is_okay
219
db.emit(:address,@report_data[:host],&block) if block
220
host_info = @report_data.merge(:workspace => @args[:workspace])
221
db_report(:host,host_info)
222
end
223
end
224
225
def report_fingerprint(host_object)
226
fp_note = {
227
:workspace => host_object.workspace,
228
:host => host_object,
229
:type => 'host.os.foundstone_fingerprint',
230
:data => {:os => @report_data[:os_fingerprint] }
231
}
232
db_report(:note, fp_note)
233
end
234
235
def report_services(host_object)
236
return unless in_tag("HostData")
237
return unless host_object.kind_of? ::Mdm::Host
238
return unless @report_data[:ports]
239
return if @report_data[:ports].empty?
240
@report_data[:ports].each do |svc|
241
db_report(:service, svc.merge(:host => host_object))
242
end
243
end
244
245
def report_vulns(host_object)
246
return unless in_tag("HostData")
247
return unless host_object.kind_of? ::Mdm::Host
248
return unless @report_data[:vulns]
249
return if @report_data[:vulns].empty?
250
@report_data[:vulns].each do |vuln|
251
db_report(:vuln, vuln.merge(:host => host_object))
252
end
253
end
254
255
# Foundstone's banners are pretty free-form
256
# and often not just banners. Clean them up
257
# for the :info field, delegate off for other
258
# protocol data we can use.
259
def normalize_foundstone_banner(service,banner)
260
return "" if(banner.nil? || banner.strip.empty?)
261
if first_line_only? service
262
return (first_line banner)
263
elsif needs_more_processing? service
264
return process_service(service,banner)
265
else
266
return (first_line banner)
267
end
268
end
269
270
# Services where we only care about the first
271
# line of the banner tag.
272
def first_line_only?(service)
273
svcs = %w{
274
vnc ftp ftps smtp oracle-tns nntp ssh ntp
275
}
276
9.times {|i| svcs << "vnc-#{i}"}
277
svcs.include? service
278
end
279
280
# Services where we need to do more processing
281
# before handing the banner back.
282
def needs_more_processing?(service)
283
svcs = %w{
284
microsoft-ds loc-srv http https sunrpc netbios-ns
285
}
286
svcs.include? service
287
end
288
289
def first_line(str)
290
str.split("\n").first.to_s.strip
291
end
292
293
# XXX: Actually implement more of these
294
def process_service(service,banner)
295
meth = "process_service_#{service.gsub("-","_")}"
296
if self.respond_to?(meth, true)
297
self.send meth, banner
298
else
299
return (first_line banner)
300
end
301
end
302
303
# XXX: Register a proper netbios note as the regular
304
# scanner does.
305
def process_service_netbios_ns(banner)
306
mac_regex = /[0-9A-Fa-f:]{17}/
307
@state[:mac] = banner[mac_regex]
308
first_line banner
309
end
310
311
# XXX: Make this behave more like the smb scanner
312
def process_service_microsoft_ds(banner)
313
lm_regex = /Native LAN Manager/
314
lm_banner = nil
315
banner.each_line { |line|
316
if line[lm_regex]
317
lm_banner = line
318
break
319
end
320
}
321
lm_banner || first_line(banner)
322
end
323
324
def process_service_http(banner)
325
server = nil
326
banner.each_line do |line|
327
if line =~ /^Server:\s+(.*)/
328
server = $1
329
break
330
end
331
end
332
server || first_line(banner)
333
end
334
335
alias :process_service_https :process_service_http
336
alias :process_service_rtsp :process_service_http
337
338
end
339
340
end
341
end
342
343
344