librex 0.0.34 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +1 -1
- data/lib/rex/parser/foundstone_nokogiri.rb +340 -0
- data/lib/rex/parser/mbsa_nokogiri.rb +255 -0
- data/lib/rex/parser/nexpose_raw_nokogiri.rb +13 -14
- data/lib/rex/parser/nexpose_simple_nokogiri.rb +10 -13
- data/lib/rex/parser/nmap_nokogiri.rb +17 -17
- data/lib/rex/parser/nokogiri_doc_mixin.rb +56 -2
- data/lib/rex/pescan/scanner.rb +14 -1
- data/lib/rex/pescan/search.rb +17 -12
- data/lib/rex/post/meterpreter/packet.rb +2 -1
- metadata +5 -3
data/README.markdown
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
A non-official re-packaging of the Rex library as a gem for easy of usage of the Metasploit REX framework in a non Metasploit application. I received permission from HDM to create this package.
|
4
4
|
|
5
5
|
Currently based on:
|
6
|
-
SVN Revision:
|
6
|
+
SVN Revision: 12804
|
7
7
|
|
8
8
|
# Credits
|
9
9
|
The Metasploit development team <http://www.metasploit.com>
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"nokogiri_doc_mixin")
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module Parser
|
5
|
+
|
6
|
+
# If Nokogiri is available, define Template document class.
|
7
|
+
load_nokogiri && class FoundstoneDocument < Nokogiri::XML::SAX::Document
|
8
|
+
|
9
|
+
include NokogiriDocMixin
|
10
|
+
|
11
|
+
def start_document
|
12
|
+
@report_type_ok = true # Optimistic
|
13
|
+
end
|
14
|
+
|
15
|
+
# Triggered every time a new element is encountered. We keep state
|
16
|
+
# ourselves with the @state variable, turning things on when we
|
17
|
+
# get here (and turning things off when we exit in end_element()).
|
18
|
+
def start_element(name=nil,attrs=[])
|
19
|
+
attrs = normalize_attrs(attrs)
|
20
|
+
block = @block
|
21
|
+
return unless @report_type_ok
|
22
|
+
@state[:current_tag][name] = true
|
23
|
+
case name
|
24
|
+
when "ReportInfo"
|
25
|
+
check_for_correct_report_type(attrs,&block)
|
26
|
+
when "Host"
|
27
|
+
record_host(attrs)
|
28
|
+
when "Service"
|
29
|
+
record_service(attrs)
|
30
|
+
when "Port", "Protocol", "Banner"
|
31
|
+
@state[:has_text] = true
|
32
|
+
when "Vuln" # under VulnsFound, ignore risk 0 things
|
33
|
+
record_vuln(attrs)
|
34
|
+
when "Risk" # for Vuln
|
35
|
+
@state[:has_text] = true
|
36
|
+
when "CVE" # Under Vuln
|
37
|
+
@state[:has_text] = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# When we exit a tag, this is triggered.
|
42
|
+
def end_element(name=nil)
|
43
|
+
block = @block
|
44
|
+
return unless @report_type_ok
|
45
|
+
case name
|
46
|
+
when "Host" # Wrap it up
|
47
|
+
collect_host_data
|
48
|
+
host_object = report_host &block
|
49
|
+
if host_object
|
50
|
+
db.report_import_note(@args[:wspace],host_object)
|
51
|
+
report_fingerprint(host_object)
|
52
|
+
report_services(host_object)
|
53
|
+
report_vulns(host_object)
|
54
|
+
end
|
55
|
+
# Reset the state once we close a host
|
56
|
+
@state.delete_if {|k| k != :current_tag}
|
57
|
+
when "Port"
|
58
|
+
@state[:has_text] = false
|
59
|
+
collect_port
|
60
|
+
when "Protocol"
|
61
|
+
@state[:has_text] = false
|
62
|
+
collect_protocol
|
63
|
+
when "Banner"
|
64
|
+
collect_banner
|
65
|
+
@state[:has_text] = false
|
66
|
+
when "Service"
|
67
|
+
collect_service
|
68
|
+
when "Vuln"
|
69
|
+
collect_vuln
|
70
|
+
when "Risk"
|
71
|
+
@state[:has_text] = false
|
72
|
+
collect_risk
|
73
|
+
when "CVE"
|
74
|
+
@state[:has_text] = false
|
75
|
+
collect_cve
|
76
|
+
end
|
77
|
+
@state[:current_tag].delete name
|
78
|
+
end
|
79
|
+
|
80
|
+
# Nothing technically stopping us from parsing this as well,
|
81
|
+
# but saving this for later
|
82
|
+
def check_for_correct_report_type(attrs,&block)
|
83
|
+
report_type = attr_hash(attrs)["ReportType"]
|
84
|
+
if report_type == "Network Inventory"
|
85
|
+
@report_type_ok = true
|
86
|
+
else
|
87
|
+
if report_type == "Risk Data"
|
88
|
+
msg = "The Foundstone/Mcafee report type '#{report_type}' is not currently supported"
|
89
|
+
msg << ",\nso no data will be imported. Please use the 'Network Inventory' report instead."
|
90
|
+
else
|
91
|
+
msg = ".\nThe Foundstone/Macafee report type '#{report_type}' is unsupported."
|
92
|
+
end
|
93
|
+
db.emit(:warning,msg,&block) if block
|
94
|
+
@report_type_ok = false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def collect_risk
|
99
|
+
return unless in_tag("VulnsFound")
|
100
|
+
return unless in_tag("HostData")
|
101
|
+
return unless in_tag("Host")
|
102
|
+
risk = @text.to_s.to_i
|
103
|
+
@state[:vuln][:risk] = risk
|
104
|
+
@text = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def collect_cve
|
108
|
+
return unless in_tag("VulnsFound")
|
109
|
+
return unless in_tag("HostData")
|
110
|
+
return unless in_tag("Host")
|
111
|
+
cve = @text.to_s
|
112
|
+
@state[:vuln][:cves] ||= []
|
113
|
+
@state[:vuln][:cves] << cve unless cve == "CVE-MAP-NOMATCH"
|
114
|
+
@text = nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# Determines if we should keep the vuln or not. Note that
|
118
|
+
# we cannot tie them to a service.
|
119
|
+
def collect_vuln
|
120
|
+
return unless in_tag("VulnsFound")
|
121
|
+
return unless in_tag("HostData")
|
122
|
+
return unless in_tag("Host")
|
123
|
+
return if @state[:vuln][:risk] == 0
|
124
|
+
@report_data[:vulns] ||= []
|
125
|
+
vuln_hash = {}
|
126
|
+
vuln_hash[:name] = @state[:vuln]["VulnName"]
|
127
|
+
refs = []
|
128
|
+
refs << "FID-#{@state[:vuln]["id"]}"
|
129
|
+
if @state[:vuln][:cves]
|
130
|
+
@state[:vuln][:cves].each {|cve| refs << cve}
|
131
|
+
end
|
132
|
+
vuln_hash[:refs] = refs
|
133
|
+
@report_data[:vulns] << vuln_hash
|
134
|
+
end
|
135
|
+
|
136
|
+
# These are per host.
|
137
|
+
def record_vuln(attrs)
|
138
|
+
return unless in_tag("VulnsFound")
|
139
|
+
return unless in_tag("HostData")
|
140
|
+
return unless in_tag("Host")
|
141
|
+
@state[:vulns] ||= []
|
142
|
+
|
143
|
+
@state[:vuln] = attr_hash(attrs) # id and VulnName
|
144
|
+
end
|
145
|
+
|
146
|
+
def record_service(attrs)
|
147
|
+
return unless in_tag("ServicesFound")
|
148
|
+
return unless in_tag("Host")
|
149
|
+
@state[:service] = attr_hash(attrs)
|
150
|
+
end
|
151
|
+
|
152
|
+
def collect_port
|
153
|
+
return unless in_tag("Service")
|
154
|
+
return unless in_tag("ServicesFound")
|
155
|
+
return unless in_tag("Host")
|
156
|
+
return if @text.nil? || @text.empty?
|
157
|
+
@state[:service][:port] = @text.strip
|
158
|
+
@text = nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def collect_protocol
|
162
|
+
return unless in_tag("Service")
|
163
|
+
return unless in_tag("ServicesFound")
|
164
|
+
return unless in_tag("Host")
|
165
|
+
return if @text.nil? || @text.empty?
|
166
|
+
@state[:service][:proto] = @text.strip
|
167
|
+
@text = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def collect_banner
|
171
|
+
return unless in_tag("Service")
|
172
|
+
return unless in_tag("ServicesFound")
|
173
|
+
return unless in_tag("Host")
|
174
|
+
return if @text.nil? || @text.empty?
|
175
|
+
banner = normalize_foundstone_banner(@state[:service]["ServiceName"],@text)
|
176
|
+
unless banner.nil? || banner.empty?
|
177
|
+
@state[:service][:banner] = banner
|
178
|
+
end
|
179
|
+
@text = nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def collect_service
|
183
|
+
return unless in_tag("ServicesFound")
|
184
|
+
return unless in_tag("Host")
|
185
|
+
return unless @state[:service][:port]
|
186
|
+
@report_data[:ports] ||= []
|
187
|
+
port_hash = {}
|
188
|
+
port_hash[:port] = @state[:service][:port]
|
189
|
+
port_hash[:proto] = @state[:service][:proto]
|
190
|
+
port_hash[:info] = @state[:service][:banner]
|
191
|
+
port_hash[:name] = db.nmap_msf_service_map(@state[:service]["ServiceName"])
|
192
|
+
@report_data[:ports] << port_hash
|
193
|
+
end
|
194
|
+
|
195
|
+
def record_host(attrs)
|
196
|
+
return unless in_tag("HostData")
|
197
|
+
@state[:host] = attr_hash(attrs)
|
198
|
+
end
|
199
|
+
|
200
|
+
def collect_host_data
|
201
|
+
@report_data[:host] = @state[:host]["IPAddress"]
|
202
|
+
if @state[:host]["NBName"] && !@state[:host]["NBName"].empty?
|
203
|
+
@report_data[:name] = @state[:host]["NBName"]
|
204
|
+
elsif @state[:host]["DNSName"] && !@state[:host]["DNSName"].empty?
|
205
|
+
@report_data[:name] = @state[:host]["DNSName"]
|
206
|
+
end
|
207
|
+
if @state[:host]["OSName"] && !@state[:host]["OSName"].empty?
|
208
|
+
@report_data[:os_fingerprint] = @state[:host]["OSName"]
|
209
|
+
end
|
210
|
+
@report_data[:state] = Msf::HostState::Alive
|
211
|
+
@report_data[:mac] = @state[:mac] if @state[:mac]
|
212
|
+
end
|
213
|
+
|
214
|
+
def report_host(&block)
|
215
|
+
return unless in_tag("HostData")
|
216
|
+
if host_is_okay
|
217
|
+
db.emit(:address,@report_data[:host],&block) if block
|
218
|
+
host_info = @report_data.merge(:workspace => @args[:wspace])
|
219
|
+
db_report(:host,host_info)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def report_fingerprint(host_object)
|
224
|
+
fp_note = {
|
225
|
+
:workspace => host_object.workspace,
|
226
|
+
:host => host_object,
|
227
|
+
:type => 'host.os.foundstone_fingerprint',
|
228
|
+
:data => {:os => @report_data[:os_fingerprint] }
|
229
|
+
}
|
230
|
+
db_report(:note, fp_note)
|
231
|
+
end
|
232
|
+
|
233
|
+
def report_services(host_object)
|
234
|
+
return unless in_tag("HostData")
|
235
|
+
return unless host_object.kind_of? Msf::DBManager::Host
|
236
|
+
return unless @report_data[:ports]
|
237
|
+
return if @report_data[:ports].empty?
|
238
|
+
@report_data[:ports].each do |svc|
|
239
|
+
db_report(:service, svc.merge(:host => host_object))
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def report_vulns(host_object)
|
244
|
+
return unless in_tag("HostData")
|
245
|
+
return unless host_object.kind_of? Msf::DBManager::Host
|
246
|
+
return unless @report_data[:vulns]
|
247
|
+
return if @report_data[:vulns].empty?
|
248
|
+
@report_data[:vulns].each do |vuln|
|
249
|
+
db_report(:vuln, vuln.merge(:host => host_object))
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Foundstone's banners are pretty free-form
|
254
|
+
# and often not just banners. Clean them up
|
255
|
+
# for the :info field, delegate off for other
|
256
|
+
# protocol data we can use.
|
257
|
+
def normalize_foundstone_banner(service,banner)
|
258
|
+
return "" if(banner.nil? || banner.strip.empty?)
|
259
|
+
if first_line_only? service
|
260
|
+
return (first_line banner)
|
261
|
+
elsif needs_more_processing? service
|
262
|
+
return process_service(service,banner)
|
263
|
+
else
|
264
|
+
return (first_line banner)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Services where we only care about the first
|
269
|
+
# line of the banner tag.
|
270
|
+
def first_line_only?(service)
|
271
|
+
svcs = %w{
|
272
|
+
vnc ftp ftps smtp oracle-tns nntp ssh ntp
|
273
|
+
}
|
274
|
+
9.times {|i| svcs << "vnc-#{i}"}
|
275
|
+
svcs.include? service
|
276
|
+
end
|
277
|
+
|
278
|
+
# Services where we need to do more processing
|
279
|
+
# before handing the banner back.
|
280
|
+
def needs_more_processing?(service)
|
281
|
+
svcs = %w{
|
282
|
+
microsoft-ds loc-srv http https sunrpc netbios-ns
|
283
|
+
}
|
284
|
+
svcs.include? service
|
285
|
+
end
|
286
|
+
|
287
|
+
def first_line(str)
|
288
|
+
str.split("\n").first.to_s.strip
|
289
|
+
end
|
290
|
+
|
291
|
+
# XXX: Actually implement more of these
|
292
|
+
def process_service(service,banner)
|
293
|
+
meth = "process_service_#{service.gsub("-","_")}"
|
294
|
+
if self.respond_to? meth
|
295
|
+
self.send meth, banner
|
296
|
+
else
|
297
|
+
return (first_line banner)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# XXX: Register a proper netbios note as the regular
|
302
|
+
# scanner does.
|
303
|
+
def process_service_netbios_ns(banner)
|
304
|
+
mac_regex = /[0-9A-Fa-f:]{17}/
|
305
|
+
@state[:mac] = banner[mac_regex]
|
306
|
+
first_line banner
|
307
|
+
end
|
308
|
+
|
309
|
+
# XXX: Make this behave more like the smb scanner
|
310
|
+
def process_service_microsoft_ds(banner)
|
311
|
+
lm_regex = /Native LAN Manager/
|
312
|
+
lm_banner = nil
|
313
|
+
banner.each_line { |line|
|
314
|
+
if line[lm_regex]
|
315
|
+
lm_banner = line
|
316
|
+
break
|
317
|
+
end
|
318
|
+
}
|
319
|
+
lm_banner || first_line(banner)
|
320
|
+
end
|
321
|
+
|
322
|
+
def process_service_http(banner)
|
323
|
+
server = nil
|
324
|
+
banner.each_line do |line|
|
325
|
+
if line =~ /^Server:\s+(.*)/
|
326
|
+
server = $1
|
327
|
+
break
|
328
|
+
end
|
329
|
+
end
|
330
|
+
server || first_line(banner)
|
331
|
+
end
|
332
|
+
|
333
|
+
alias :process_service_https :process_service_http
|
334
|
+
alias :process_service_rtsp :process_service_http
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"nokogiri_doc_mixin")
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module Parser
|
5
|
+
|
6
|
+
# If Nokogiri is available, define Template document class.
|
7
|
+
load_nokogiri && class MbsaDocument < Nokogiri::XML::SAX::Document
|
8
|
+
|
9
|
+
include NokogiriDocMixin
|
10
|
+
|
11
|
+
# Triggered every time a new element is encountered. We keep state
|
12
|
+
# ourselves with the @state variable, turning things on when we
|
13
|
+
# get here (and turning things off when we exit in end_element()).
|
14
|
+
def start_element(name=nil,attrs=[])
|
15
|
+
attrs = normalize_attrs(attrs)
|
16
|
+
block = @block
|
17
|
+
@state[:current_tag][name] = true
|
18
|
+
case name
|
19
|
+
when "SecScan"
|
20
|
+
record_host(attrs)
|
21
|
+
when "IP" # TODO: Check to see if IPList/IP is useful to import
|
22
|
+
when "Check" # A list of MBSA checks. They have an ID and a Name.
|
23
|
+
record_check(attrs)
|
24
|
+
when "Advice" # Check advice. Free form text about the check
|
25
|
+
@state[:has_text] = true
|
26
|
+
when "Detail" # Check/Detail is where missing fixes are.
|
27
|
+
record_detail(attrs)
|
28
|
+
when "UpdateData" # Info about installed/missing hotfixes
|
29
|
+
record_updatedata(attrs)
|
30
|
+
when "Title" # MSB Title
|
31
|
+
@state[:has_text] = true
|
32
|
+
when "InformationURL" # Only use this if we don't have a Bulletin ID
|
33
|
+
@state[:has_text] = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# This breaks xml-encoded characters, so need to append
|
38
|
+
def characters(text)
|
39
|
+
return unless @state[:has_text]
|
40
|
+
@text ||= ""
|
41
|
+
@text << text
|
42
|
+
end
|
43
|
+
|
44
|
+
# When we exit a tag, this is triggered.
|
45
|
+
def end_element(name=nil)
|
46
|
+
block = @block
|
47
|
+
case name
|
48
|
+
when "SecScan" # Wrap it up
|
49
|
+
collect_host_data
|
50
|
+
host_object = report_host &block
|
51
|
+
if host_object
|
52
|
+
db.report_import_note(@args[:wspace],host_object)
|
53
|
+
report_fingerprint(host_object)
|
54
|
+
report_vulns(host_object,&block)
|
55
|
+
end
|
56
|
+
# Reset the state once we close a host
|
57
|
+
@state.delete_if {|k| k != :current_tag}
|
58
|
+
when "Check"
|
59
|
+
collect_check_data
|
60
|
+
when "Advice"
|
61
|
+
@state[:has_text] = false
|
62
|
+
collect_advice_data
|
63
|
+
when "Detail"
|
64
|
+
collect_detail_data
|
65
|
+
when "UpdateData"
|
66
|
+
collect_updatedata
|
67
|
+
when "Title"
|
68
|
+
@state[:has_text] = false
|
69
|
+
collect_title
|
70
|
+
when "InformationURL"
|
71
|
+
collect_url
|
72
|
+
@state[:has_text] = false
|
73
|
+
end
|
74
|
+
@state[:current_tag].delete name
|
75
|
+
end
|
76
|
+
|
77
|
+
def report_fingerprint(host_object)
|
78
|
+
return unless host_object.kind_of? Msf::DBManager::Host
|
79
|
+
return unless @report_data[:os_fingerprint]
|
80
|
+
fp_note = @report_data[:os_fingerprint].merge(
|
81
|
+
{
|
82
|
+
:workspace => host_object.workspace,
|
83
|
+
:host => host_object
|
84
|
+
})
|
85
|
+
db_report(:note, fp_note)
|
86
|
+
end
|
87
|
+
|
88
|
+
def collect_url
|
89
|
+
return unless in_tag("References")
|
90
|
+
return unless in_tag("UpdateData")
|
91
|
+
return unless in_tag("Detail")
|
92
|
+
return unless in_tag("Check")
|
93
|
+
@state[:update][:url] = @text.to_s.strip
|
94
|
+
@text = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def report_vulns(host_object, &block)
|
98
|
+
return unless host_object.kind_of? Msf::DBManager::Host
|
99
|
+
return unless @report_data[:vulns]
|
100
|
+
return if @report_data[:vulns].empty?
|
101
|
+
@report_data[:vulns].each do |vuln|
|
102
|
+
next unless vuln[:refs]
|
103
|
+
if vuln[:refs].empty?
|
104
|
+
next
|
105
|
+
end
|
106
|
+
if block
|
107
|
+
db.emit(:vuln, ["Missing #{vuln[:name]}",1], &block) if block
|
108
|
+
end
|
109
|
+
db_report(:vuln, vuln.merge(:host => host_object))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def collect_title
|
114
|
+
return unless in_tag("SecScan")
|
115
|
+
return unless in_tag("Check")
|
116
|
+
collect_bulletin_title
|
117
|
+
@text = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def collect_bulletin_title
|
121
|
+
return unless @state[:check_state]["ID"] == 500.to_s
|
122
|
+
return unless in_tag("UpdateData")
|
123
|
+
return unless @state[:update]
|
124
|
+
return if @text.to_s.strip.empty?
|
125
|
+
@state[:update]["Title"] = @text.to_s.strip
|
126
|
+
end
|
127
|
+
|
128
|
+
def collect_updatedata
|
129
|
+
return unless in_tag("SecScan")
|
130
|
+
return unless in_tag("Check")
|
131
|
+
return unless in_tag("Detail")
|
132
|
+
collect_missing_update
|
133
|
+
@state[:updates] = {}
|
134
|
+
end
|
135
|
+
|
136
|
+
def collect_missing_update
|
137
|
+
return unless @state[:check_state]["ID"] == 500.to_s
|
138
|
+
return if @state[:update]["IsInstalled"] == "true"
|
139
|
+
@report_data[:missing_updates] ||= []
|
140
|
+
this_update = {}
|
141
|
+
this_update[:name] = @state[:update]["Title"].to_s.strip
|
142
|
+
this_update[:refs] = []
|
143
|
+
if @state[:update]["BulletinID"].empty?
|
144
|
+
this_update[:refs] << "URL-#{@state[:update][:url]}"
|
145
|
+
else
|
146
|
+
this_update[:refs] << "MSB-#{@state[:update]["BulletinID"]}"
|
147
|
+
end
|
148
|
+
@report_data[:missing_updates] << this_update
|
149
|
+
end
|
150
|
+
|
151
|
+
# So far, just care about Host OS
|
152
|
+
# There is assuredly more interesting things going on in here.
|
153
|
+
def collect_advice_data
|
154
|
+
return unless in_tag("SecScan")
|
155
|
+
return unless in_tag("Check")
|
156
|
+
collect_os_name
|
157
|
+
@text = nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def collect_os_name
|
161
|
+
return unless @state[:check_state]["ID"] == 10101.to_s
|
162
|
+
return unless @text
|
163
|
+
return if @text.strip.empty?
|
164
|
+
os_match = @text.match(/Computer is running (.*)/)
|
165
|
+
return unless os_match
|
166
|
+
os_info = os_match[1]
|
167
|
+
os_vendor = os_info[/Microsoft/]
|
168
|
+
os_family = os_info[/Windows/]
|
169
|
+
os_version = os_info[/(XP|2000 Advanced Server|2000|2003|2008|SBS|Vista|7 .* Edition|7)/]
|
170
|
+
if os_info
|
171
|
+
@report_data[:os_fingerprint] = {}
|
172
|
+
@report_data[:os_fingerprint][:type] = "host.os.mbsa_fingerprint"
|
173
|
+
@report_data[:os_fingerprint][:data] = {
|
174
|
+
:os_vendor => os_vendor,
|
175
|
+
:os_family => os_family,
|
176
|
+
:os_version => os_version,
|
177
|
+
:os_accuracy => 100,
|
178
|
+
:os_match => os_info.gsub(/\x2e$/,"")
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def collect_detail_data
|
184
|
+
return unless in_tag("SecScan")
|
185
|
+
return unless in_tag("Check")
|
186
|
+
if @report_data[:missing_updates]
|
187
|
+
@report_data[:vulns] = @report_data[:missing_updates]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def collect_check_data
|
192
|
+
return unless in_tag("SecScan")
|
193
|
+
@state[:check_state] = {}
|
194
|
+
end
|
195
|
+
|
196
|
+
def collect_host_data
|
197
|
+
return unless @state[:address]
|
198
|
+
return if @state[:address].strip.empty?
|
199
|
+
@report_data[:host] = @state[:address].strip
|
200
|
+
if @state[:hostname] && !@state[:hostname].empty?
|
201
|
+
@report_data[:name] = @state[:hostname]
|
202
|
+
end
|
203
|
+
@report_data[:state] = Msf::HostState::Alive
|
204
|
+
end
|
205
|
+
|
206
|
+
def report_host(&block)
|
207
|
+
if host_is_okay
|
208
|
+
db.emit(:address,@report_data[:host],&block) if block
|
209
|
+
host_info = @report_data.merge(:workspace => @args[:wspace])
|
210
|
+
db_report(:host, host_info)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def record_updatedata(attrs)
|
215
|
+
return unless in_tag("SecScan")
|
216
|
+
return unless in_tag("Check")
|
217
|
+
return unless in_tag("Detail")
|
218
|
+
update_attrs = attr_hash(attrs)
|
219
|
+
@state[:update] = attr_hash(attrs)
|
220
|
+
end
|
221
|
+
|
222
|
+
def record_host(attrs)
|
223
|
+
host_attrs = attr_hash(attrs)
|
224
|
+
@state[:address] = host_attrs["IP"]
|
225
|
+
@state[:hostname] = host_attrs["Machine"]
|
226
|
+
end
|
227
|
+
|
228
|
+
def record_check(attrs)
|
229
|
+
return unless in_tag("SecScan")
|
230
|
+
@state[:check_state] = attr_hash(attrs)
|
231
|
+
end
|
232
|
+
|
233
|
+
def record_detail(attrs)
|
234
|
+
return unless in_tag("SecScan")
|
235
|
+
return unless in_tag("Check")
|
236
|
+
@state[:detail_state] = attr_hash(attrs)
|
237
|
+
end
|
238
|
+
|
239
|
+
# We need to override the usual host_is_okay because MBSA apparently
|
240
|
+
# doesn't report on open ports at all.
|
241
|
+
def host_is_okay
|
242
|
+
return false unless @report_data[:host]
|
243
|
+
return false unless valid_ip(@report_data[:host])
|
244
|
+
return false unless @report_data[:state] == Msf::HostState::Alive
|
245
|
+
if @args[:blacklist]
|
246
|
+
return false if @args[:blacklist].include?(@report_data[:host])
|
247
|
+
end
|
248
|
+
return true
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
@@ -43,13 +43,6 @@ module Rex
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
# This breaks on xml-encoded characters, so need to append
|
47
|
-
def characters(text)
|
48
|
-
return unless @state[:has_text]
|
49
|
-
@text ||= ""
|
50
|
-
@text << text
|
51
|
-
end
|
52
|
-
|
53
46
|
# When we exit a tag, this is triggered.
|
54
47
|
def end_element(name=nil)
|
55
48
|
block = @block
|
@@ -109,7 +102,8 @@ module Rex
|
|
109
102
|
return unless @report_data[:vuln][:matches].kind_of? Array
|
110
103
|
refs = normalize_references(@report_data[:vuln][:refs])
|
111
104
|
refs << "NEXPOSE-#{report_data[:vuln]["id"]}"
|
112
|
-
|
105
|
+
vuln_instances = @report_data[:vuln][:matches].size
|
106
|
+
db.emit(:vuln, [refs.last,vuln_instances], &block) if block
|
113
107
|
data = {
|
114
108
|
:workspace => @args[:wspace],
|
115
109
|
:name => refs.last,
|
@@ -122,7 +116,7 @@ module Rex
|
|
122
116
|
host_data[:host] = match[:host]
|
123
117
|
host_data[:port] = match[:port] if match[:port]
|
124
118
|
host_data[:proto] = match[:protocol] if match[:protocol]
|
125
|
-
|
119
|
+
db_report(:vuln, host_data)
|
126
120
|
if match[:key]
|
127
121
|
hosts_keys[host_data[:host]] ||= []
|
128
122
|
hosts_keys[host_data[:host]] << match[:key]
|
@@ -147,7 +141,7 @@ module Rex
|
|
147
141
|
next if key_note[:data][data[:name]].include? key_value
|
148
142
|
key_note[:data][data[:name]] << key_value
|
149
143
|
end
|
150
|
-
|
144
|
+
db_report(:note, key_note)
|
151
145
|
end
|
152
146
|
end
|
153
147
|
|
@@ -240,7 +234,7 @@ module Rex
|
|
240
234
|
note[:data][:product] = @report_data[:os]["os_product"] if @report_data[:os]["os_prduct"]
|
241
235
|
note[:data][:version] = @report_data[:os]["os_version"] if @report_data[:os]["os_version"]
|
242
236
|
note[:data][:arch] = @report_data[:os]["os_arch"] if @report_data[:os]["os_arch"]
|
243
|
-
|
237
|
+
db_report(:note, note)
|
244
238
|
end
|
245
239
|
|
246
240
|
def report_services(host_object)
|
@@ -249,7 +243,7 @@ module Rex
|
|
249
243
|
return if @report_data[:ports].empty?
|
250
244
|
reported = []
|
251
245
|
@report_data[:ports].each do |svc|
|
252
|
-
reported <<
|
246
|
+
reported << db_report(:service, svc.merge(:host => host_object))
|
253
247
|
end
|
254
248
|
reported
|
255
249
|
end
|
@@ -277,7 +271,12 @@ module Rex
|
|
277
271
|
end
|
278
272
|
end
|
279
273
|
if @state[:service]
|
280
|
-
|
274
|
+
if state[:service]["name"] == "<unknown>"
|
275
|
+
sname = nil
|
276
|
+
else
|
277
|
+
sname = db.nmap_msf_service_map(@state[:service]["name"])
|
278
|
+
end
|
279
|
+
port_hash[:name] = sname
|
281
280
|
end
|
282
281
|
if @state[:service_fingerprint]
|
283
282
|
info = []
|
@@ -347,7 +346,7 @@ module Rex
|
|
347
346
|
def report_host(&block)
|
348
347
|
if host_is_okay
|
349
348
|
db.emit(:address,@report_data[:host],&block) if block
|
350
|
-
host_object =
|
349
|
+
host_object = db_report(:host, @report_data.merge(
|
351
350
|
:workspace => @args[:wspace] ) )
|
352
351
|
if host_object
|
353
352
|
db.report_import_note(host_object.workspace, host_object)
|
@@ -41,13 +41,6 @@ module Rex
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
# This breaks xml-encoded characters, so need to append
|
45
|
-
def characters(text)
|
46
|
-
return unless @state[:has_text]
|
47
|
-
@text ||= ""
|
48
|
-
@text << text
|
49
|
-
end
|
50
|
-
|
51
44
|
# When we exit a tag, this is triggered.
|
52
45
|
def end_element(name=nil)
|
53
46
|
block = @block
|
@@ -108,7 +101,7 @@ module Rex
|
|
108
101
|
data[:port] = vuln[:port]
|
109
102
|
data[:proto] = vuln[:proto]
|
110
103
|
end
|
111
|
-
|
104
|
+
db_report(:vuln,data)
|
112
105
|
end
|
113
106
|
|
114
107
|
end
|
@@ -240,7 +233,7 @@ module Rex
|
|
240
233
|
def report_host(&block)
|
241
234
|
if host_is_okay
|
242
235
|
db.emit(:address,@report_data[:host],&block) if block
|
243
|
-
host_object =
|
236
|
+
host_object = db_report(:host, @report_data.merge(
|
244
237
|
:workspace => @args[:wspace] ) )
|
245
238
|
if host_object
|
246
239
|
db.report_import_note(host_object.workspace, host_object)
|
@@ -267,7 +260,7 @@ module Rex
|
|
267
260
|
:version => @report_data[:host_fingerprint]["version"],
|
268
261
|
:arch => @report_data[:host_fingerprint]["architecture"]
|
269
262
|
}
|
270
|
-
|
263
|
+
db_report(:note, note.merge(:data => data))
|
271
264
|
end
|
272
265
|
|
273
266
|
def record_service(attrs)
|
@@ -292,8 +285,12 @@ module Rex
|
|
292
285
|
when "port"
|
293
286
|
port_hash[:port] = v
|
294
287
|
when "name"
|
295
|
-
|
296
|
-
|
288
|
+
sname = v.to_s.downcase.split("(")[0].strip
|
289
|
+
if sname == "<unknown>"
|
290
|
+
port_hash[:name] = nil
|
291
|
+
else
|
292
|
+
port_hash[:name] = db.nmap_msf_service_map(sname)
|
293
|
+
end
|
297
294
|
end
|
298
295
|
end
|
299
296
|
if @state[:service_fingerprint]
|
@@ -317,7 +314,7 @@ module Rex
|
|
317
314
|
return if @report_data[:ports].empty?
|
318
315
|
reported = []
|
319
316
|
@report_data[:ports].each do |svc|
|
320
|
-
reported <<
|
317
|
+
reported << db_report(:service, svc.merge(:host => host_object))
|
321
318
|
end
|
322
319
|
reported
|
323
320
|
end
|
@@ -303,43 +303,43 @@ module Rex
|
|
303
303
|
def report_traceroute(host_object)
|
304
304
|
return unless host_object.kind_of? ::Msf::DBManager::Host
|
305
305
|
return unless @report_data[:traceroute]
|
306
|
-
|
306
|
+
tr_note = {
|
307
307
|
:workspace => host_object.workspace,
|
308
308
|
:host => host_object,
|
309
309
|
:type => "host.nmap.traceroute",
|
310
|
-
:data => {
|
311
|
-
'port' => @report_data[:traceroute]["port"].to_i,
|
310
|
+
:data => { 'port' => @report_data[:traceroute]["port"].to_i,
|
312
311
|
'proto' => @report_data[:traceroute]["proto"].to_s,
|
313
|
-
'hops' => @report_data[:traceroute][:hops]
|
314
|
-
|
315
|
-
)
|
312
|
+
'hops' => @report_data[:traceroute][:hops] }
|
313
|
+
}
|
314
|
+
db_report(:note, tr_note)
|
316
315
|
end
|
317
316
|
|
318
317
|
def report_uptime(host_object)
|
319
318
|
return unless host_object.kind_of? ::Msf::DBManager::Host
|
320
319
|
return unless @report_data[:last_boot]
|
321
|
-
|
320
|
+
up_note = {
|
322
321
|
:workspace => host_object.workspace,
|
323
322
|
:host => host_object,
|
324
323
|
:type => "host.last_boot",
|
325
|
-
:data => { :time => @report_data[:last_boot] }
|
326
|
-
|
324
|
+
:data => { :time => @report_data[:last_boot] }
|
325
|
+
}
|
326
|
+
db_report(:note, up_note)
|
327
327
|
end
|
328
328
|
|
329
329
|
def report_fingerprint(host_object)
|
330
330
|
return unless host_object.kind_of? ::Msf::DBManager::Host
|
331
331
|
return unless @report_data[:os_fingerprint]
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
)
|
332
|
+
fp_note = @report_data[:os_fingerprint].merge(
|
333
|
+
{
|
334
|
+
:workspace => host_object.workspace,
|
335
|
+
:host => host_object
|
336
|
+
})
|
337
|
+
db_report(:note, fp_note)
|
338
338
|
end
|
339
339
|
|
340
340
|
def report_host(&block)
|
341
341
|
if host_is_okay
|
342
|
-
host_object =
|
342
|
+
host_object = db_report(:host, @report_data.merge(
|
343
343
|
:workspace => @args[:wspace] ) )
|
344
344
|
db.emit(:address,@report_data[:host],&block) if block
|
345
345
|
host_object
|
@@ -352,7 +352,7 @@ module Rex
|
|
352
352
|
return if @report_data[:ports].empty?
|
353
353
|
reported = []
|
354
354
|
@report_data[:ports].each do |svc|
|
355
|
-
reported <<
|
355
|
+
reported << db_report(:service, svc.merge(:host => host_object))
|
356
356
|
end
|
357
357
|
reported
|
358
358
|
end
|
@@ -114,11 +114,48 @@ module Parser
|
|
114
114
|
return true
|
115
115
|
end
|
116
116
|
|
117
|
-
# XXX:
|
117
|
+
# XXX: Document classes ought to define this
|
118
118
|
def determine_port_state(v)
|
119
119
|
return v
|
120
120
|
end
|
121
121
|
|
122
|
+
# Circumvent the unknown attribute logging by the various reporters. They
|
123
|
+
# seem to be there just for debugging anyway.
|
124
|
+
def db_report(table, data)
|
125
|
+
raise "Data should be a hash" unless data.kind_of? Hash
|
126
|
+
nonempty_data = data.reject {|k,v| v.nil?}
|
127
|
+
valid_attrs = db_valid_attributes(table)
|
128
|
+
raise "Unknown table `#{table}'" if valid_attrs.empty?
|
129
|
+
if table == :note # Notes don't whine about extra stuff
|
130
|
+
just_the_facts = nonempty_data
|
131
|
+
else
|
132
|
+
just_the_facts = nonempty_data.select {|k,v| valid_attrs.include? k.to_s.to_sym}
|
133
|
+
end
|
134
|
+
just_the_facts.empty? ? return : db.send("report_#{table}", just_the_facts)
|
135
|
+
end
|
136
|
+
|
137
|
+
# XXX: It would be better to either have a single registry of acceptable
|
138
|
+
# keys if we're going to alert on bad ones, or to be more forgiving if
|
139
|
+
# the caller is this thing. There is basically no way to tell if
|
140
|
+
# report_host()'s tastes are going to change with this scheme.
|
141
|
+
def db_valid_attributes(table)
|
142
|
+
case table.to_s.to_sym
|
143
|
+
when :host
|
144
|
+
Msf::DBManager::Host.new.attribute_names.map {|x| x.to_sym} |
|
145
|
+
[:host, :workspace]
|
146
|
+
when :service
|
147
|
+
Msf::DBManager::Service.new.attribute_names.map {|x| x.to_sym} |
|
148
|
+
[:host, :host_name, :mac, :workspace]
|
149
|
+
when :vuln
|
150
|
+
Msf::DBManager::Vuln.new.attribute_names.map {|x| x.to_sym} |
|
151
|
+
[:host, :refs, :workspace, :port, :proto]
|
152
|
+
when :note
|
153
|
+
[:anything]
|
154
|
+
else
|
155
|
+
[]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
122
159
|
# Nokogiri 1.4.4 (and presumably beyond) generates attrs as pairs,
|
123
160
|
# like [["value1","foo"],["value2","bar"]] (but not hashes for some
|
124
161
|
# reason). 1.4.3.1 (and presumably 1.4.3.x and prior) generates attrs
|
@@ -139,13 +176,30 @@ module Parser
|
|
139
176
|
return attr_pairs
|
140
177
|
end
|
141
178
|
|
179
|
+
# This breaks xml-encoded characters, so need to append.
|
180
|
+
# It's on the end_element tag name to turn the appending
|
181
|
+
# off and clear out the data.
|
182
|
+
def characters(text)
|
183
|
+
return unless @state[:has_text]
|
184
|
+
@text ||= ""
|
185
|
+
@text << text
|
186
|
+
end
|
187
|
+
|
188
|
+
# Effectively the same as characters()
|
189
|
+
def cdata_block(text)
|
190
|
+
return unless @state[:has_text]
|
191
|
+
@text ||= ""
|
192
|
+
@text << text
|
193
|
+
end
|
194
|
+
|
142
195
|
def end_document
|
143
196
|
block = @block
|
197
|
+
return unless @report_type_ok
|
144
198
|
unless @state[:current_tag].empty?
|
145
199
|
missing_ends = @state[:current_tag].keys.map {|x| "'#{x}'"}.join(", ")
|
146
200
|
msg = "Warning, the provided file is incomplete, and there may be missing\n"
|
147
201
|
msg << "data. The following tags were not closed: #{missing_ends}."
|
148
|
-
db.emit(:warning,msg,&block)
|
202
|
+
db.emit(:warning,msg,&block) if block
|
149
203
|
end
|
150
204
|
end
|
151
205
|
|
data/lib/rex/pescan/scanner.rb
CHANGED
@@ -29,8 +29,18 @@ module Scanner
|
|
29
29
|
msg = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
|
30
30
|
$stdout.puts pe.ptr_s(vma) + " " + msg
|
31
31
|
if(param['disasm'])
|
32
|
+
#puts [msg].pack('H*').inspect
|
32
33
|
insns = []
|
33
|
-
|
34
|
+
msg.gsub!("; ", "\n")
|
35
|
+
if msg.include?("retn")
|
36
|
+
msg.gsub!("retn", "ret")
|
37
|
+
end
|
38
|
+
#puts msg
|
39
|
+
begin
|
40
|
+
d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, msg).disassemble
|
41
|
+
rescue Metasm::ParseError
|
42
|
+
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [msg].pack('H*')[0])
|
43
|
+
end
|
34
44
|
addr = 0
|
35
45
|
while ((di = d2.disassemble_instruction(addr)))
|
36
46
|
insns << di.instruction
|
@@ -39,6 +49,9 @@ module Scanner
|
|
39
49
|
$stdout.puts disasm
|
40
50
|
addr = di.next_addr
|
41
51
|
end
|
52
|
+
# ::Rex::Assembly::Nasm.disassemble([msg].pack("H*")).split("\n").each do |line|
|
53
|
+
# $stdout.puts "\tnasm: #{line.strip}"
|
54
|
+
#end
|
42
55
|
end
|
43
56
|
end
|
44
57
|
end
|
data/lib/rex/pescan/search.rb
CHANGED
@@ -3,40 +3,44 @@ module PeScan
|
|
3
3
|
module Search
|
4
4
|
|
5
5
|
require "rex/assembly/nasm"
|
6
|
-
|
6
|
+
|
7
7
|
class DumpRVA
|
8
8
|
attr_accessor :pe
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(pe)
|
11
11
|
self.pe = pe
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def config(param)
|
15
15
|
@address = pe.vma_to_rva(param['args'])
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def scan(param)
|
19
19
|
config(param)
|
20
|
-
|
20
|
+
|
21
21
|
$stdout.puts "[#{param['file']}]"
|
22
|
-
|
22
|
+
|
23
23
|
# Adjust based on -A and -B flags
|
24
24
|
pre = param['before'] || 0
|
25
25
|
suf = param['after'] || 16
|
26
|
-
|
26
|
+
|
27
27
|
@address -= pre
|
28
28
|
@address = 0 if (@address < 0 || ! @address)
|
29
|
-
|
29
|
+
|
30
30
|
begin
|
31
31
|
buf = pe.read_rva(@address, suf)
|
32
32
|
rescue ::Rex::PeParsey::WtfError
|
33
33
|
return
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
$stdout.puts pe.ptr_s(pe.rva_to_vma(@address)) + " " + buf.unpack("H*")[0]
|
37
37
|
if(param['disasm'])
|
38
38
|
insns = []
|
39
|
-
|
39
|
+
buf.gsub!("; ", "\n")
|
40
|
+
if buf.include?("retn")
|
41
|
+
buf.gsub!("retn", "ret")
|
42
|
+
end
|
43
|
+
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, buf)
|
40
44
|
addr = 0
|
41
45
|
while ((di = d2.disassemble_instruction(addr)))
|
42
46
|
insns << di.instruction
|
@@ -46,7 +50,8 @@ module Search
|
|
46
50
|
addr = di.next_addr
|
47
51
|
end
|
48
52
|
end
|
49
|
-
|
53
|
+
|
54
|
+
end
|
50
55
|
end
|
51
56
|
|
52
57
|
class DumpOffset < DumpRVA
|
@@ -56,7 +61,7 @@ module Search
|
|
56
61
|
rescue Rex::PeParsey::BoundsError
|
57
62
|
end
|
58
63
|
end
|
59
|
-
end
|
64
|
+
end
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
@@ -224,7 +224,8 @@ class Tlv
|
|
224
224
|
# Converts the TLV to raw.
|
225
225
|
#
|
226
226
|
def to_r
|
227
|
-
|
227
|
+
# Forcibly convert to ASCII-8BIT encoding
|
228
|
+
raw = value.to_s.unpack("C*").pack("C*")
|
228
229
|
|
229
230
|
if (self.type & TLV_META_TYPE_STRING == TLV_META_TYPE_STRING)
|
230
231
|
raw += "\x00"
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: librex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.35
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Metasploit Development Team
|
@@ -11,11 +11,11 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-
|
14
|
+
date: 2011-06-01 00:00:00 -05:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|
18
|
-
description: Rex provides a variety of classes useful for security testing and exploit development. Based on SVN Revision
|
18
|
+
description: Rex provides a variety of classes useful for security testing and exploit development. Based on SVN Revision 12804
|
19
19
|
email:
|
20
20
|
- hdm@metasploit.com
|
21
21
|
- jacob.hammack@hammackj.com
|
@@ -153,10 +153,12 @@ files:
|
|
153
153
|
- lib/rex/parser/apple_backup_manifestdb.rb
|
154
154
|
- lib/rex/parser/arguments.rb
|
155
155
|
- lib/rex/parser/arguments.rb.ut.rb
|
156
|
+
- lib/rex/parser/foundstone_nokogiri.rb
|
156
157
|
- lib/rex/parser/ini.rb
|
157
158
|
- lib/rex/parser/ini.rb.ut.rb
|
158
159
|
- lib/rex/parser/ip360_aspl_xml.rb
|
159
160
|
- lib/rex/parser/ip360_xml.rb
|
161
|
+
- lib/rex/parser/mbsa_nokogiri.rb
|
160
162
|
- lib/rex/parser/nessus_xml.rb
|
161
163
|
- lib/rex/parser/netsparker_xml.rb
|
162
164
|
- lib/rex/parser/nexpose_raw_nokogiri.rb
|