librex 0.0.35 → 0.0.36

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.
@@ -148,7 +148,7 @@ protected
148
148
  closed = true
149
149
  wlog("monitor_rsock: closed remote socket due to nil read")
150
150
  end
151
- rescue ::Exception
151
+ rescue ::Exception => e
152
152
  closed = true
153
153
  wlog("monitor_rsock: exception during read: #{e.class} #{e}")
154
154
  end
@@ -169,10 +169,11 @@ protected
169
169
  # This way we naturally perform a resend if a failure occured.
170
170
  # Catches an edge case with meterpreter TCP channels where remote send
171
171
  # failes gracefully and a resend is required.
172
- if (sent.nil? or sent <= 0)
172
+ if (sent.nil?)
173
+ closed = true
173
174
  wlog("monitor_rsock: failed writing, socket must be dead")
174
175
  break
175
- else
176
+ elsif (sent > 0)
176
177
  total_sent += sent
177
178
  end
178
179
  rescue ::IOError, ::EOFError => e
@@ -0,0 +1,394 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)),"nokogiri_doc_mixin")
2
+ require 'rex'
3
+ require 'uri'
4
+
5
+ module Rex
6
+ module Parser
7
+
8
+ # If Nokogiri is available, define the Acunetix document class.
9
+ load_nokogiri && class AcunetixDocument < Nokogiri::XML::SAX::Document
10
+
11
+ include NokogiriDocMixin
12
+
13
+ # The resolver prefers your local /etc/hosts (or windows equiv), but will
14
+ # fall back to regular DNS. It retains a cache for the import to avoid
15
+ # spamming your network with DNS requests.
16
+ attr_reader :resolv_cache
17
+
18
+ # If name resolution of the host fails out completely, you will not be
19
+ # able to import that Scan task. Other scan tasks in the same report
20
+ # should be unaffected.
21
+ attr_reader :parse_warnings
22
+
23
+ def start_document
24
+ @parse_warnings = []
25
+ @resolv_cache = {}
26
+ end
27
+
28
+ def start_element(name=nil,attrs=[])
29
+ attrs = normalize_attrs(attrs)
30
+ block = @block
31
+ @state[:current_tag][name] = true
32
+ case name
33
+ when "Scan" # Start of the thing.
34
+ when "Name", "StartURL", "Banner", "Os"
35
+ @state[:has_text] = true
36
+ when "LoginSequence" # Skipping for now
37
+ when "Crawler"
38
+ record_crawler(attrs)
39
+ when "FullURL"
40
+ @state[:has_text] = true
41
+ when "Variable"
42
+ record_variable(attrs)
43
+ when "Request", "Response"
44
+ @state[:has_text] = true
45
+ end
46
+ end
47
+
48
+ def end_element(name=nil)
49
+ block = @block
50
+ case name
51
+ when "Scan"
52
+ # Clears most of the @state out, we're done with this web site.
53
+ @state.delete_if {|k| k != :current_tag}
54
+ when "Name"
55
+ @state[:has_text] = false
56
+ collect_scan_name
57
+ collect_report_item_name
58
+ @text = nil
59
+ when "StartURL" # Populates @state[:starturl_uri], we use this a lot
60
+ @state[:has_text] = false
61
+ collect_host
62
+ collect_service
63
+ @text = nil
64
+ handle_parse_warnings &block
65
+ host_object = report_host &block
66
+ if host_object
67
+ report_starturl_service(host_object,&block)
68
+ db.report_import_note(@args[:wspace],host_object)
69
+ end
70
+ when "Banner"
71
+ @state[:has_text] = false
72
+ collect_and_report_banner
73
+ when "Os"
74
+ @state[:has_text] = false
75
+ report_os_fingerprint
76
+ when "LoginSequence" # This comes up later in the report anyway
77
+ when "Crawler"
78
+ report_starturl_web_site(&block)
79
+ when "FullURL"
80
+ @state[:has_text] = false
81
+ report_web_site(@text,&block)
82
+ @text = nil
83
+ when "Inputs"
84
+ report_web_form(&block)
85
+ when "Request"
86
+ @state[:has_text] = false
87
+ collect_page_request
88
+ @text = nil
89
+ when "Response"
90
+ @state[:has_text] = false
91
+ collect_page_response
92
+ @text = nil
93
+ report_web_page(&block)
94
+ end
95
+ @state[:current_tag].delete name
96
+ end
97
+
98
+ def collect_page_response
99
+ return unless in_tag("TechnicalDetails")
100
+ return unless in_tag("ReportItem")
101
+ return unless @text
102
+ return if @text.to_s.empty?
103
+ @state[:page_response] = @text
104
+ end
105
+
106
+ def collect_page_request
107
+ return unless in_tag("TechnicalDetails")
108
+ return unless in_tag("ReportItem")
109
+ return unless @text
110
+ return if @text.to_s.empty?
111
+ @state[:page_request] = @text
112
+ end
113
+
114
+ def collect_scan_name
115
+ return unless in_tag("Scan")
116
+ return if in_tag("ReportItems")
117
+ return if in_tag("Crawler")
118
+ return unless @text
119
+ return if @text.strip.empty?
120
+ @state[:scan_name] = @text.strip
121
+ end
122
+
123
+ def collect_host
124
+ return unless in_tag("Scan")
125
+ return unless @text
126
+ return if @text.strip.empty?
127
+ uri = URI.parse(@text) rescue nil
128
+ return unless uri
129
+ address = resolve_scan_starturl_address(uri)
130
+ @report_data[:host] = address
131
+ @report_data[:state] = Msf::HostState::Alive
132
+ end
133
+
134
+ def collect_service
135
+ return unless @report_data[:host]
136
+ return unless in_tag("Scan")
137
+ return unless @text
138
+ return if @text.strip.empty?
139
+ uri = URI.parse(@text) rescue nil
140
+ return unless uri
141
+ @state[:starturl_uri] = uri
142
+ @report_data[:ports] ||= []
143
+ @report_data[:ports] << @state[:starturl_port]
144
+ end
145
+
146
+ def collect_and_report_banner
147
+ return unless (svc = @state[:starturl_service_object]) # Yes i want assignment
148
+ return unless @text
149
+ return if @text.strip.empty?
150
+ return unless in_tag("Scan")
151
+ svc_info = {
152
+ :host => svc.host,
153
+ :port => svc.port,
154
+ :proto => svc.proto,
155
+ :info => @text.strip
156
+ }
157
+ db_report(:service, svc_info)
158
+ @text = nil
159
+ end
160
+
161
+ def collect_report_item_name
162
+ return unless in_tag("ReportItem")
163
+ return unless @text
164
+ return if @text.strip.empty?
165
+ @state[:report_item] = @text
166
+ end
167
+
168
+ # @state[:fullurl] is set by report_web_site
169
+ def record_variable(attrs)
170
+ return unless in_tag("Inputs")
171
+ return unless @state[:fullurl].kind_of? URI
172
+ method = attr_hash(attrs)["Type"]
173
+ return unless method
174
+ return if method.strip.empty?
175
+ @state[:form_variables] ||= []
176
+ @state[:form_variables] << [attr_hash(attrs)["Name"],method]
177
+ end
178
+
179
+ def record_crawler(attrs)
180
+ return unless in_tag("Scan")
181
+ return unless @state[:starturl_service_object]
182
+ starturl = attr_hash(attrs)["StartUrl"]
183
+ return unless starturl
184
+ @state[:crawler_starturl] = starturl
185
+ end
186
+
187
+ def report_web_form(&block)
188
+ return unless in_tag("SiteFiles")
189
+ return unless @state[:web_site]
190
+ return unless @state[:fullurl].kind_of? URI
191
+ return unless @state[:form_variables].kind_of? Array
192
+ return if @state[:form_variables].empty?
193
+ method = @state[:form_variables].first[1]
194
+ vars = @state[:form_variables].map {|x| x[0]}
195
+ form_info = {}
196
+ form_info[:web_site] = @state[:web_site]
197
+ form_info[:path] = @state[:fullurl].path
198
+ form_info[:query] = @state[:fullurl].query
199
+ form_info[:method] = method
200
+ form_info[:params] = vars
201
+ url = @state[:fullurl].to_s
202
+ db.emit(:web_form,url,&block) if block
203
+ db_report(:web_form,form_info)
204
+ @state[:fullurl] = nil
205
+ @state[:form_variables] = nil
206
+ end
207
+
208
+ def report_web_page(&block)
209
+ return if should_skip_this_page
210
+ return unless @state[:web_site]
211
+ return unless @state[:page_request]
212
+ return if @state[:page_request].strip.empty?
213
+ return unless @state[:page_response]
214
+ return if @state[:page_response].strip.empty?
215
+ path,query_string = parse_request(@state[:page_request])
216
+ return unless path
217
+ parsed_response = parse_response(@state[:page_response])
218
+ return unless parsed_response
219
+ web_page_info = {}
220
+ web_page_info[:web_site] = @state[:web_site]
221
+ web_page_info[:path] = path
222
+ web_page_info[:code] = parsed_response[:code].to_i
223
+ web_page_info[:headers] = parsed_response[:headers]
224
+ web_page_info[:body] = parsed_response[:body]
225
+ web_page_info[:query] = query_string || ""
226
+ url = ""
227
+ url << @state[:web_site].service.name.to_s << "://"
228
+ url << @state[:web_site].vhost.to_s << ":"
229
+ url << path
230
+ uri = URI.parse(url) rescue nil
231
+ return unless uri # Sanity checker
232
+ db.emit(:web_page, url, &block) if block
233
+ web_page_object = db_report(:web_page,web_page_info)
234
+ @state[:page_request] = @state[:page_response] = nil
235
+ @state[:web_page] = web_page_object
236
+ end
237
+
238
+ # Reasons why we shouldn't collect a particular web page.
239
+ def should_skip_this_page
240
+ if @state[:report_item] =~ /Unrestricted File Upload/
241
+ # This means that the page being collected is something the
242
+ # auditor put there, so it's not useful to report on.
243
+ return true
244
+ end
245
+ return false
246
+ end
247
+
248
+ # XXX Rex::Proto::Http::Packet seems broken for
249
+ # actually parsing requests and responses, but all I
250
+ # need are the headers anyway
251
+ def parse_request(request)
252
+ headers = Rex::Proto::Http::Packet::Header.new
253
+ headers.from_s request.dup # It's destructive.
254
+ return unless headers.cmd_string
255
+ verb,req = headers.cmd_string.split(/\s+/)
256
+ return unless verb
257
+ return unless req
258
+ path,query_string = req.split(/\?/)[0,2]
259
+ end
260
+
261
+ def parse_response(response)
262
+ headers = Rex::Proto::Http::Packet::Header.new
263
+ headers.from_s response.dup # It's destructive.
264
+ return unless headers.cmd_string
265
+ http,code,msg = headers.cmd_string.split(/\s+/)
266
+ return unless code
267
+ return unless code.to_i.to_s == code
268
+ parsed = {}
269
+ parsed[:code] = code
270
+ parsed[:headers] = {}
271
+ headers.each do |k,v|
272
+ parsed[:headers][k.to_s.downcase] = []
273
+ parsed[:headers][k.to_s.downcase] << v
274
+ end
275
+ parsed[:body] = "" # We never seem to get this from Acunetix
276
+ parsed
277
+ end
278
+
279
+ def report_host(&block)
280
+ return unless @report_data[:host]
281
+ return unless in_tag("Scan")
282
+ if host_is_okay
283
+ db.emit(:address,@report_data[:host],&block) if block
284
+ host_info = @report_data.merge(:workspace => @args[:wspace])
285
+ db_report(:host,host_info)
286
+ end
287
+ end
288
+
289
+ # The service is super important, so we hang on to it for the
290
+ # rest of the scan.
291
+ def report_starturl_service(host_object,&block)
292
+ return unless host_object
293
+ return unless @state[:starturl_uri]
294
+ name = @state[:starturl_uri].scheme
295
+ port = @state[:starturl_uri].port
296
+ addr = host_object.address
297
+ svc = {
298
+ :host => host_object,
299
+ :port => port,
300
+ :name => name.dup,
301
+ :proto => "tcp"
302
+ }
303
+ if name and port
304
+ db.emit(:service,[addr,port].join(":"),&block) if block
305
+ @state[:starturl_service_object] = db_report(:service,svc)
306
+ end
307
+ end
308
+
309
+ def report_web_site(url,&block)
310
+ return unless in_tag("Crawler")
311
+ return unless url
312
+ return if url.strip.empty?
313
+ uri = URI.parse(url) rescue nil
314
+ return unless uri
315
+ host = uri.host
316
+ port = uri.port
317
+ scheme = uri.scheme
318
+ return unless scheme[/^https?/]
319
+ return unless (host && port && scheme)
320
+ address = resolve_address(host)
321
+ return unless address
322
+ service_info = [ @args[:wspace], address, "tcp", port ]
323
+ service_object = db.get_service(*service_info)
324
+ service_object = db_report(:service,service_info) unless service_object
325
+ web_site_info = {
326
+ :workspace => @args[:wspace],
327
+ :service => service_object,
328
+ :vhost => host,
329
+ :ssl => (scheme == "https")
330
+ }
331
+ @state[:web_site] = db_report(:web_site,web_site_info)
332
+ @state[:fullurl] = uri
333
+ end
334
+
335
+ def report_starturl_web_site(&block)
336
+ return unless @state[:crawler_starturl]
337
+ starturl = @state[:crawler_starturl].dup
338
+ report_web_site(starturl,&block)
339
+ end
340
+
341
+ def report_os_fingerprint
342
+ return unless @state[:starturl_service_object]
343
+ return unless @text
344
+ return if @text.strip.empty?
345
+ return unless in_tag("Scan")
346
+ host = @state[:starturl_service_object].host
347
+ fp_note = {
348
+ :workspace => host.workspace,
349
+ :host => host,
350
+ :type => 'host.os.acunetix_fingerprint',
351
+ :data => {:os => @text}
352
+ }
353
+ db_report(:note, fp_note)
354
+ @text = nil
355
+ end
356
+
357
+ def resolve_port(uri)
358
+ @state[:port] = uri.port
359
+ unless @state[:port]
360
+ @parse_warnings << "Could not determine a port for '#{@state[:scan_name]}'"
361
+ end
362
+ @state[:port] = uri.port
363
+ end
364
+
365
+ def resolve_address(host)
366
+ return @resolv_cache[host] if @resolv_cache[host]
367
+ address = Rex::Socket.resolv_to_dotted(host) rescue nil
368
+ @resolv_cache[host] = address
369
+ return address
370
+ end
371
+
372
+ def resolve_scan_starturl_address(uri)
373
+ if uri.host
374
+ address = resolve_address(uri.host)
375
+ unless address
376
+ @parse_warnings << "Could not resolve address for '#{uri.host}', skipping '#{@state[:scan_name]}'"
377
+ end
378
+ else
379
+ @parse_warnings << "Could not determine a host for '#{@state[:scan_name]}'"
380
+ end
381
+ address
382
+ end
383
+
384
+ def handle_parse_warnings(&block)
385
+ return if @parse_warnings.empty?
386
+ @parse_warnings.each do |pwarn|
387
+ db.emit(:warning, pwarn, &block) if block
388
+ end
389
+ end
390
+
391
+ end
392
+ end
393
+ end
394
+
@@ -0,0 +1,366 @@
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 AppScan document class.
7
+ load_nokogiri && class AppscanDocument < Nokogiri::XML::SAX::Document
8
+
9
+ include NokogiriDocMixin
10
+
11
+ # The resolver prefers your local /etc/hosts (or windows equiv), but will
12
+ # fall back to regular DNS. It retains a cache for the import to avoid
13
+ # spamming your network with DNS requests.
14
+ attr_reader :resolv_cache
15
+
16
+ # If name resolution of the host fails out completely, you will not be
17
+ # able to import that Scan task. Other scan tasks in the same report
18
+ # should be unaffected.
19
+ attr_reader :parse_warning
20
+
21
+ def start_document
22
+ @parse_warnings = []
23
+ @resolv_cache = {}
24
+ end
25
+
26
+ def start_element(name=nil,attrs=[])
27
+ attrs = normalize_attrs(attrs)
28
+ block = @block
29
+ @state[:current_tag][name] = true
30
+ case name
31
+ when "Issue" # Start of the stuff we want
32
+ collect_issue(attrs)
33
+ when "Entity" # Start of the stuff we want
34
+ collect_entity(attrs)
35
+ when "Severity", "Url", "OriginalHttpTraffic"
36
+ @state[:has_text] = true
37
+ end
38
+ end
39
+
40
+ def end_element(name=nil)
41
+ block = @block
42
+ case name
43
+ when "Issue" # Wrap it up
44
+ record_issue
45
+ # Reset the state once we close an issue
46
+ @state = @state.select do
47
+ |k| [:current_tag, :web_sites].include? k
48
+ end
49
+ when "Url" # Populates @state[:web_site]
50
+ @state[:has_text] = false
51
+ record_url
52
+ @text = nil
53
+ report_web_site(&block)
54
+ handle_parse_warnings(&block)
55
+ when "Severity"
56
+ @state[:has_text] = false
57
+ record_risk
58
+ @text = nil
59
+ when "OriginalHttpTraffic" # Request and response
60
+ @state[:has_text] = false
61
+ record_request_and_response
62
+ report_service_info
63
+ page_info = report_web_page(&block)
64
+ if page_info
65
+ form_info = report_web_form(page_info,&block)
66
+ if form_info
67
+ report_web_vuln(form_info,&block)
68
+ end
69
+ end
70
+ @text = nil
71
+ end
72
+ @state[:current_tag].delete name
73
+ end
74
+
75
+ def report_web_vuln(form_info,&block)
76
+ return unless(in_issue && has_text)
77
+ return unless form_info.kind_of? Hash
78
+ return unless @state[:issue]
79
+ return unless @state[:issue]["Noise"]
80
+ return unless @state[:issue]["Noise"].to_s.downcase == "false"
81
+ return unless @state[:issue][:vuln_param]
82
+ web_vuln_info = {}
83
+ web_vuln_info[:web_site] = form_info[:web_site]
84
+ web_vuln_info[:path] = form_info[:path]
85
+ web_vuln_info[:query] = form_info[:query]
86
+ web_vuln_info[:method] = form_info[:method]
87
+ web_vuln_info[:params] = form_info[:params]
88
+ web_vuln_info[:pname] = @state[:issue][:vuln_param]
89
+ web_vuln_info[:proof] = "" # TODO: pick this up from <Difference> maybe?
90
+ web_vuln_info[:risk] = @state[:issue][:risk]
91
+ web_vuln_info[:name] = @state[:issue]["IssueTypeID"]
92
+ web_vuln_info[:category] = "imported"
93
+ web_vuln_info[:confidence] = 100 # Seems pretty binary, noise or not
94
+ db.emit(:web_vuln, web_vuln_info[:name], &block) if block
95
+ web_vuln = db_report(:web_vuln, web_vuln_info)
96
+ end
97
+
98
+ def collect_entity(attrs)
99
+ return unless in_issue
100
+ return unless @state[:issue].kind_of? Hash
101
+ ent_hash = attr_hash(attrs)
102
+ return unless ent_hash
103
+ return unless ent_hash["Type"].to_s.downcase == "parameter"
104
+ @state[:issue][:vuln_param] = ent_hash["Name"]
105
+ end
106
+
107
+ def report_web_form(page_info,&block)
108
+ return unless(in_issue && has_text)
109
+ return unless page_info.kind_of? Hash
110
+ return unless @state[:request_body]
111
+ return if @state[:request_body].strip.empty?
112
+ web_form_info = {}
113
+ web_form_info[:web_site] = page_info[:web_site]
114
+ web_form_info[:path] = page_info[:path]
115
+ web_form_info[:query] = page_info[:query]
116
+ web_form_info[:method] = @state[:request_headers].cmd_string.split(/\s+/)[0]
117
+ parsed_params = parse_params(@state[:request_body])
118
+ return unless parsed_params
119
+ return if parsed_params.empty?
120
+ web_form_info[:params] = parsed_params
121
+ web_form = db_report(:web_form, web_form_info)
122
+ @state[:web_forms] ||= []
123
+ unless @state[:web_forms].include? web_form
124
+ db.emit(:web_form, @state[:uri].to_s, &block) if block
125
+ @state[:web_forms] << web_form
126
+ end
127
+ web_form_info
128
+ end
129
+
130
+ def parse_params(request_body)
131
+ return unless request_body
132
+ pairs = request_body.split(/&/)
133
+ params = []
134
+ pairs.each do |pair|
135
+ param,value = pair.split("=",2)
136
+ params << [param,""] # Can't tell what's default
137
+ end
138
+ params
139
+ end
140
+
141
+ def report_web_page(&block)
142
+ return unless(in_issue && has_text)
143
+ return unless @state[:web_site]
144
+ return unless @state[:response_headers]
145
+ return unless @state[:uri]
146
+ web_page_info = {}
147
+ web_page_info[:web_site] = @state[:web_site]
148
+ web_page_info[:path] = @state[:uri].path
149
+ web_page_info[:body] = @state[:response_body].to_s
150
+ web_page_info[:query] = @state[:uri].query
151
+ code = @state[:response_headers].cmd_string.split(/\s+/)[1]
152
+ return unless code
153
+ web_page_info[:code] = code
154
+ parsed_headers = {}
155
+ @state[:response_headers].each do |k,v|
156
+ parsed_headers[k.to_s.downcase] ||= []
157
+ parsed_headers[k.to_s.downcase] << v
158
+ end
159
+ return if parsed_headers.empty?
160
+ web_page_info[:headers] = parsed_headers
161
+ web_page = db_report(:web_page, web_page_info)
162
+ @state[:web_pages] ||= []
163
+ unless @state[:web_pages].include? web_page
164
+ db.emit(:web_page, @state[:uri].to_s, &block) if block
165
+ @state[:web_pages] << web_page
166
+ end
167
+ web_page_info
168
+ end
169
+
170
+ def report_service_info
171
+ return unless(in_issue && has_text)
172
+ return unless @state[:web_site]
173
+ return unless @state[:response_headers]
174
+ banner = @state[:response_headers]["server"]
175
+ return unless banner
176
+ service = @state[:web_site].service
177
+ return unless service.info.to_s.empty?
178
+ service_info = {
179
+ :host => service.host,
180
+ :port => service.port,
181
+ :proto => service.proto,
182
+ :info => banner
183
+ }
184
+ db_report(:service, service_info)
185
+ end
186
+
187
+ def record_request_and_response
188
+ return unless(in_issue && has_text)
189
+ return unless @state[:web_site]
190
+ really_original_traffic = unindent_and_crlf(@text)
191
+ split_traffic = really_original_traffic.split(/\r\n\r\n/)
192
+ request_headers_text = split_traffic.first
193
+ content_length = 0
194
+ if request_headers_text =~ /\ncontent-length:\s+([0-9]+)/mni
195
+ content_length = $1.to_i
196
+ end
197
+ if(content_length > 0) and (split_traffic[1].to_s.size >= content_length)
198
+ request_body_text = split_traffic[1].to_s[0,content_length]
199
+ else
200
+ request_body_text = nil
201
+ end
202
+ response_headers_text = split_traffic[1].to_s[content_length,split_traffic[1].to_s.size].lstrip
203
+ request = request_headers_text
204
+ return unless(request && response_headers_text)
205
+ response_body_text = split_traffic[2]
206
+ req_header = Rex::Proto::Http::Packet::Header.new
207
+ res_header = Rex::Proto::Http::Packet::Header.new
208
+ req_header.from_s request_headers_text.dup
209
+ res_header.from_s response_headers_text.dup
210
+ @state[:request_headers] = req_header
211
+ @state[:request_body] = request_body_text
212
+ @state[:response_headers] = res_header
213
+ @state[:response_body] = response_body_text
214
+ end
215
+
216
+ # Appscan tab-indents which makes parsing a little difficult. They
217
+ # also don't record CRLFs, just LFs.
218
+ def unindent_and_crlf(text)
219
+ second_line = text.split(/\r*\n/)[1]
220
+ indent_level = second_line.size - second_line.lstrip.size
221
+ unindented_text_lines = []
222
+ text.split(/\r*\n/).each do |line|
223
+ if line =~ /^\t{#{indent_level}}/
224
+ unindented_line = line[indent_level,line.size]
225
+ unindented_text_lines << unindented_line
226
+ else
227
+ unindented_text_lines << line
228
+ end
229
+ end
230
+ unindented_text_lines.join("\r\n")
231
+ end
232
+
233
+ def record_risk
234
+ return unless(in_issue && has_text)
235
+ @state[:issue] ||= {}
236
+ @state[:issue][:risk] = map_severity_to_risk
237
+ end
238
+
239
+ def map_severity_to_risk
240
+ case @text.to_s.downcase
241
+ when "high" ; 5
242
+ when "medium" ; 3
243
+ when "low" ; 1
244
+ else ; 0
245
+ end
246
+ end
247
+
248
+ # TODO
249
+ def record_issue
250
+ return unless in_issue
251
+ return unless @report_data[:issue].kind_of? Hash
252
+ return unless @state[:web_site]
253
+ return if @state[:issue]["Noise"].to_s.downcase == "true"
254
+ end
255
+
256
+ def collect_issue(attrs)
257
+ return unless in_issue
258
+ @state[:issue] = {}
259
+ @state[:issue].merge! attr_hash(attrs)
260
+ end
261
+
262
+ def report_web_site(&block)
263
+ return unless @state[:uri]
264
+ uri = @state[:uri]
265
+ hostname = uri.host # Assume the first one is the real hostname
266
+ address = resolve_issue_url_address(uri)
267
+ return unless address
268
+ unless @resolv_cache.values.include? address
269
+ db.emit(:address, address, &block) if block
270
+ end
271
+ port = resolve_port(uri)
272
+ return unless port
273
+ scheme = uri.scheme
274
+ return unless scheme
275
+ web_site_info = {:workspace => @args[:wspace]}
276
+ web_site_info[:vhost] = hostname
277
+ service_obj = check_for_existing_service(address,port)
278
+ if service_obj
279
+ web_site_info[:service] = service_obj
280
+ else
281
+ web_site_info[:host] = address
282
+ web_site_info[:port] = port
283
+ web_site_info[:ssl] = scheme == "https"
284
+ end
285
+ web_site_obj = db_report(:web_site, web_site_info)
286
+ @state[:web_sites] ||= []
287
+ unless @state[:web_sites].include? web_site_obj
288
+ url = "#{uri.scheme}://#{uri.host}:#{uri.port}"
289
+ db.emit(:web_site, url, &block) if block
290
+ db.report_import_note(@args[:wspace], web_site_obj.service.host)
291
+ @state[:web_sites] << web_site_obj
292
+ end
293
+ @state[:service] = service_obj || web_site_obj.service
294
+ @state[:host] = (service_obj || web_site_obj.service).host
295
+ @state[:web_site] = web_site_obj
296
+ end
297
+
298
+ def check_for_existing_service(address,port)
299
+ db.get_service(@args[:wspace],address,"tcp",port)
300
+ end
301
+
302
+ def resolve_port(uri)
303
+ @state[:port] = uri.port
304
+ unless @state[:port]
305
+ @parse_warnings << "Could not determine a port for '#{@state[:scan_name]}'"
306
+ end
307
+ return @state[:port]
308
+ end
309
+
310
+ def resolve_address(host)
311
+ return @resolv_cache[host] if @resolv_cache[host]
312
+ address = Rex::Socket.resolv_to_dotted(host) rescue nil
313
+ @resolv_cache[host] = address
314
+ if address
315
+ block = @block
316
+ db.emit(:address, address, &block) if block
317
+ end
318
+ return address
319
+ end
320
+
321
+ # Alias this
322
+ def resolve_issue_url_address(uri)
323
+ if uri.host
324
+ address = resolve_address(uri.host)
325
+ unless address
326
+ @parse_warnings << "Could not resolve address for '#{uri.host}', skipping."
327
+ end
328
+ else
329
+ @parse_warnings << "Could not determine a host for this import."
330
+ end
331
+ address
332
+ end
333
+
334
+ def handle_parse_warnings(&block)
335
+ return if @parse_warnings.empty?
336
+ @parse_warnings.each do |pwarn|
337
+ db.emit(:warning, pwarn, &block) if block
338
+ end
339
+ end
340
+
341
+ def record_url
342
+ return unless in_issue
343
+ return unless has_text
344
+ uri = URI.parse(@text) rescue nil
345
+ return unless uri
346
+ @state[:uri] = uri
347
+ end
348
+
349
+ def in_issue
350
+ return false unless in_tag("Issue")
351
+ return false unless in_tag("Issues")
352
+ return false unless in_tag("XmlReport")
353
+ return true
354
+ end
355
+
356
+ def has_text
357
+ return false unless @text
358
+ return false if @text.strip.empty?
359
+ @text = @text.strip
360
+ end
361
+
362
+ end
363
+
364
+ end
365
+ end
366
+