librex 0.0.34 → 0.0.35
Sign up to get free protection for your applications and to get access to all the features.
- 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
|