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/mbsa_document.rb
Views: 11778
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 MbsaDocument < Nokogiri::XML::SAX::Document
9
10
include NokogiriDocMixin
11
12
# Triggered every time a new element is encountered. We keep state
13
# ourselves with the @state variable, turning things on when we
14
# get here (and turning things off when we exit in end_element()).
15
def start_element(name=nil,attrs=[])
16
attrs = normalize_attrs(attrs)
17
block = @block
18
@state[:current_tag][name] = true
19
case name
20
when "SecScan"
21
record_host(attrs)
22
when "IP" # TODO: Check to see if IPList/IP is useful to import
23
when "Check" # A list of MBSA checks. They have an ID and a Name.
24
record_check(attrs)
25
when "Advice" # Check advice. Free form text about the check
26
@state[:has_text] = true
27
when "Detail" # Check/Detail is where missing fixes are.
28
record_detail(attrs)
29
when "UpdateData" # Info about installed/missing hotfixes
30
record_updatedata(attrs)
31
when "Title" # MSB Title
32
@state[:has_text] = true
33
when "InformationURL" # Only use this if we don't have a Bulletin ID
34
@state[:has_text] = true
35
end
36
end
37
38
# This breaks xml-encoded characters, so need to append
39
def characters(text)
40
return unless @state[:has_text]
41
@text ||= ""
42
@text << text
43
end
44
45
# When we exit a tag, this is triggered.
46
def end_element(name=nil)
47
block = @block
48
case name
49
when "SecScan" # Wrap it up
50
collect_host_data
51
host_object = report_host &block
52
if host_object
53
db.report_import_note(@args[:workspace],host_object)
54
report_fingerprint(host_object)
55
report_vulns(host_object,&block)
56
end
57
# Reset the state once we close a host
58
@state.delete_if {|k| k != :current_tag}
59
when "Check"
60
collect_check_data
61
when "Advice"
62
@state[:has_text] = false
63
collect_advice_data
64
when "Detail"
65
collect_detail_data
66
when "UpdateData"
67
collect_updatedata
68
when "Title"
69
@state[:has_text] = false
70
collect_title
71
when "InformationURL"
72
collect_url
73
@state[:has_text] = false
74
end
75
@state[:current_tag].delete name
76
end
77
78
def report_fingerprint(host_object)
79
return unless host_object.kind_of? ::Mdm::Host
80
return unless @report_data[:os_fingerprint]
81
fp_note = @report_data[:os_fingerprint].merge(
82
{
83
:workspace => host_object.workspace,
84
:host => host_object
85
})
86
db_report(:note, fp_note)
87
end
88
89
def collect_url
90
return unless in_tag("References")
91
return unless in_tag("UpdateData")
92
return unless in_tag("Detail")
93
return unless in_tag("Check")
94
@state[:update][:url] = @text.to_s.strip
95
@text = nil
96
end
97
98
def report_vulns(host_object, &block)
99
return unless host_object.kind_of? ::Mdm::Host
100
return unless @report_data[:vulns]
101
return if @report_data[:vulns].empty?
102
@report_data[:vulns].each do |vuln|
103
next unless vuln[:refs]
104
if vuln[:refs].empty?
105
next
106
end
107
if block
108
db.emit(:vuln, ["Missing #{vuln[:name]}",1], &block) if block
109
end
110
db_report(:vuln, vuln.merge(:host => host_object))
111
end
112
end
113
114
def collect_title
115
return unless in_tag("SecScan")
116
return unless in_tag("Check")
117
collect_bulletin_title
118
@text = nil
119
end
120
121
def collect_bulletin_title
122
return unless @state[:check_state]["ID"] == 500.to_s
123
return unless in_tag("UpdateData")
124
return unless @state[:update]
125
return if @text.to_s.strip.empty?
126
@state[:update]["Title"] = @text.to_s.strip
127
end
128
129
def collect_updatedata
130
return unless in_tag("SecScan")
131
return unless in_tag("Check")
132
return unless in_tag("Detail")
133
collect_missing_update
134
@state[:updates] = {}
135
end
136
137
def collect_missing_update
138
return unless @state[:check_state]["ID"] == 500.to_s
139
return if @state[:update]["IsInstalled"] == "true"
140
@report_data[:missing_updates] ||= []
141
this_update = {}
142
this_update[:name] = @state[:update]["Title"].to_s.strip
143
this_update[:refs] = []
144
if @state[:update]["BulletinID"].empty?
145
this_update[:refs] << "URL-#{@state[:update][:url]}"
146
else
147
this_update[:refs] << "MSB-#{@state[:update]["BulletinID"]}"
148
end
149
@report_data[:missing_updates] << this_update
150
end
151
152
# So far, just care about Host OS
153
# There is assuredly more interesting things going on in here.
154
def collect_advice_data
155
return unless in_tag("SecScan")
156
return unless in_tag("Check")
157
collect_os_name
158
@text = nil
159
end
160
161
def collect_os_name
162
return unless @state[:check_state]["ID"] == 10101.to_s
163
return unless @text
164
return if @text.strip.empty?
165
os_match = @text.match(/Computer is running (.*)/)
166
return unless os_match
167
os_info = os_match[1]
168
os_vendor = os_info[/Microsoft/]
169
os_family = os_info[/Windows/]
170
os_version = os_info[/(XP|2000 Advanced Server|2000|2003|2008|SBS|Vista|7 .* Edition|7)/]
171
if os_info
172
@report_data[:os_fingerprint] = {}
173
@report_data[:os_fingerprint][:type] = "host.os.mbsa_fingerprint"
174
@report_data[:os_fingerprint][:data] = {
175
:os_vendor => os_vendor,
176
:os_family => os_family,
177
:os_version => os_version,
178
:os_accuracy => 100,
179
:os_match => os_info.gsub(/\x2e$/n,"")
180
}
181
end
182
end
183
184
def collect_detail_data
185
return unless in_tag("SecScan")
186
return unless in_tag("Check")
187
if @report_data[:missing_updates]
188
@report_data[:vulns] = @report_data[:missing_updates]
189
end
190
end
191
192
def collect_check_data
193
return unless in_tag("SecScan")
194
@state[:check_state] = {}
195
end
196
197
def collect_host_data
198
return unless @state[:address]
199
return if @state[:address].strip.empty?
200
@report_data[:host] = @state[:address].strip
201
if @state[:hostname] && !@state[:hostname].empty?
202
@report_data[:name] = @state[:hostname]
203
end
204
@report_data[:state] = Msf::HostState::Alive
205
end
206
207
def report_host(&block)
208
if host_is_okay
209
db.emit(:address,@report_data[:host],&block) if block
210
host_info = @report_data.merge(:workspace => @args[:workspace])
211
db_report(:host, host_info)
212
end
213
end
214
215
def record_updatedata(attrs)
216
return unless in_tag("SecScan")
217
return unless in_tag("Check")
218
return unless in_tag("Detail")
219
update_attrs = attr_hash(attrs)
220
@state[:update] = attr_hash(attrs)
221
end
222
223
def record_host(attrs)
224
host_attrs = attr_hash(attrs)
225
@state[:address] = host_attrs["IP"]
226
@state[:hostname] = host_attrs["Machine"]
227
end
228
229
def record_check(attrs)
230
return unless in_tag("SecScan")
231
@state[:check_state] = attr_hash(attrs)
232
end
233
234
def record_detail(attrs)
235
return unless in_tag("SecScan")
236
return unless in_tag("Check")
237
@state[:detail_state] = attr_hash(attrs)
238
end
239
240
# We need to override the usual host_is_okay because MBSA apparently
241
# doesn't report on open ports at all.
242
def host_is_okay
243
return false unless @report_data[:host]
244
return false unless valid_ip(@report_data[:host])
245
return false unless @report_data[:state] == Msf::HostState::Alive
246
if @args[:blacklist]
247
return false if @args[:blacklist].include?(@report_data[:host])
248
end
249
return true
250
end
251
252
end
253
254
end
255
end
256
257
258