nexpose_ticketing 1.3.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,6 +8,10 @@ class BaseMode
8
8
  @log = NexposeTicketing::NxLogger.instance
9
9
  end
10
10
 
11
+ def set_solution_store(solution_store)
12
+ @solution_store = solution_store
13
+ end
14
+
11
15
  # True if this mode supports ticket updates
12
16
  def updates_supported?
13
17
  true
@@ -122,9 +126,12 @@ class BaseMode
122
126
  # - String containing a short summary of the vulnerability.
123
127
  #
124
128
  def get_short_summary(row)
125
- summary = row['solutions'].to_s
126
- delimiter = summary.index('|')
127
- return summary[summary.index(':')+1...delimiter].strip if delimiter
129
+ solution_ids = row['solution_ids'][1..-2].split(',')
130
+ return '' if solution_ids.first == 'NULL'
131
+
132
+ sol = @solution_store.get_solution(solution_ids.first)
133
+ summary = sol[:summary] || ''
134
+
128
135
  summary.length <= 100 ? summary : summary[0...100]
129
136
  end
130
137
 
@@ -136,11 +143,25 @@ class BaseMode
136
143
  # - String formatted with solution information.
137
144
  #
138
145
  def get_solutions(row)
139
- row['solutions'].to_s.gsub('|', "\n").gsub('~', "\n--\n")
146
+ solution_ids = row['solution_ids'][1..-2].split(',')
147
+ return '' if solution_ids.first == 'NULL'
148
+
149
+ solutions = @solution_store.get_solutions solution_ids
150
+
151
+ solutions.map! do |sol|
152
+ format = "Summary: #{sol[:summary] || 'None'}\n" \
153
+ "Nexpose ID: #{sol[:nexpose_id]}\n\n" \
154
+ "Fix: #{sol[:fix]}\n"
155
+
156
+ format = format + "\nURL: #{sol[:url]}" unless sol[:url].nil?
157
+ format + "\n"
158
+ end
159
+
160
+ solutions.join("\n--\n")
140
161
  end
141
162
 
142
163
  def get_discovery_info(row)
143
- return '' if row['first_discovered'].to_s == ""
164
+ return '' if row['first_discovered'].to_s == ''
144
165
  info = "\nFirst Seen: #{row['first_discovered']}\n"
145
166
  info << "Last Seen: #{row['most_recently_discovered']}\n"
146
167
  info
@@ -175,7 +196,9 @@ class BaseMode
175
196
 
176
197
  row['assets'].to_s.split('~').each do |a|
177
198
  asset = a.split('|')
178
- assets << " - #{asset[1]} #{"\t(#{asset[2]})" if !asset[2].empty?}\n"
199
+ asset_entry = " - #{asset[1]} "
200
+ asset_entry << "\t(#{asset[2]})" unless (asset[2].nil? || asset[2].empty?)
201
+ assets << "#{asset_entry}\n"
179
202
  end
180
203
  assets
181
204
  end
@@ -14,7 +14,7 @@ class DefaultMode < BaseMode
14
14
 
15
15
  # Returns the fields used to identify individual tickets
16
16
  def get_matching_fields
17
- ['ip_address', 'vulnerability_id']
17
+ %w(ip_address vulnerability_id)
18
18
  end
19
19
 
20
20
  # Returns the ticket's title
@@ -30,7 +30,7 @@ class DefaultMode < BaseMode
30
30
  # Returns the base ticket description object
31
31
  def get_description(nexpose_id, row)
32
32
  description = { nxid: "NXID: #{get_nxid(nexpose_id, row)}" }
33
- fields = ['header', 'references', 'solutions']
33
+ fields = %w(header references solutions)
34
34
  fields.each { |f| description[f.intern] = self.send("get_#{f}", row) }
35
35
  description[:header] << get_discovery_info(row)
36
36
  description
@@ -27,10 +27,15 @@ class VulnerabilityMode < BaseMode
27
27
  '_by_vuln_id'
28
28
  end
29
29
 
30
+ def get_vuln_description(row)
31
+ return '' if row['description'].to_s == ''
32
+ "\nVulnerability Description: #{row['description']}"
33
+ end
34
+
30
35
  # Returns the base ticket description object
31
36
  def get_description(nexpose_id, row)
32
37
  description = { nxid: "NXID: #{get_nxid(nexpose_id, row)}" }
33
- fields = ['header', 'references', 'solutions', 'assets']
38
+ fields = ['header', 'references', 'solutions', 'assets', 'vuln_description']
34
39
  fields.each { |f| description[f.intern] = self.send("get_#{f}", row) }
35
40
  description
36
41
  end
@@ -43,7 +48,7 @@ class VulnerabilityMode < BaseMode
43
48
 
44
49
  # Converts the ticket description object into a formatted string
45
50
  def print_description(description)
46
- fields = [:header, :assets, :references, :solutions]
51
+ fields = [:header, :assets, :references, :solutions, :vuln_description]
47
52
  finalize_description(fields.map { |f| description[f] }.join("\n"),
48
53
  description[:nxid])
49
54
  end
@@ -13,12 +13,9 @@ module NexposeTicketing
13
13
  # - Returns String - Formatted SQL string for inserting into queries.
14
14
  #
15
15
  def self.createRiskString(riskScore)
16
- if riskScore.nil?
17
- riskString = ""
18
- else
19
- riskString = "WHERE fa.riskscore >= #{riskScore}"
20
- end
21
- return riskString
16
+ return '' if riskScore.nil?
17
+
18
+ "WHERE fa.riskscore >= #{riskScore}"
22
19
  end
23
20
 
24
21
  # Formats SQL query for filtering per asset based on user config options.
@@ -32,11 +29,10 @@ module NexposeTicketing
32
29
 
33
30
  def self.createAssetString(options)
34
31
  if options[:tag_run] && options[:nexpose_item]
35
- assetString = "WHERE asset_id = #{options[:nexpose_item]}"
32
+ "WHERE asset_id = #{options[:nexpose_item]}"
36
33
  else
37
- assetString = ""
34
+ ''
38
35
  end
39
- return assetString
40
36
  end
41
37
 
42
38
  # Gets all the latest scans for sites.
@@ -47,6 +43,16 @@ module NexposeTicketing
47
43
  JOIN dim_scan dsc ON ds.last_scan_id = dsc.scan_id'
48
44
  end
49
45
 
46
+ # Returns the solutions for every vulnerability
47
+ # stored within Nexpose
48
+ def self.all_solutions
49
+ "SELECT solution_id, nexpose_id,
50
+ summary,
51
+ proofAsText(fix) as fix,
52
+ url
53
+ FROM dim_solution"
54
+ end
55
+
50
56
  # Gets all the latest scans for tags.
51
57
  # Returns |tag.id| |asset.id| |last_scan_id| |finished|
52
58
  def self.last_tag_scans
@@ -56,43 +62,96 @@ module NexposeTicketing
56
62
  order by dta.tag_id, dta.asset_id, fa.last_scan_id, fa.scan_finished'
57
63
  end
58
64
 
65
+ # Returns the current necessary information on vulnerable_instances for a
66
+ # site / tag to create a ticket, for IP mode
67
+ # * *Returns* :
68
+ # - Returns |asset_id| |vulnerability_id| |first_discovered|
69
+ # |most_recently_discovered| |solution_ids|
70
+ def self.all_new_vulns_by_ip(options={})
71
+ 'SELECT asset_id, vulnerability_id,
72
+ first_discovered, most_recently_discovered,
73
+ array_agg(solution_id) as solution_ids
74
+ FROM (
75
+ SELECT asset_id, vulnerability_id
76
+ FROM fact_asset_vulnerability_finding) favf
77
+ JOIN (
78
+ SELECT asset_id, vulnerability_id, first_discovered,
79
+ most_recently_discovered
80
+ FROM fact_asset_vulnerability_age) fava USING (asset_id, vulnerability_id)
81
+ LEFT JOIN dim_asset_vulnerability_solution USING (asset_id, vulnerability_id)
82
+ GROUP BY asset_id, vulnerability_id, first_discovered, most_recently_discovered
83
+ ORDER BY asset_id, vulnerability_id'
84
+ end
85
+
86
+ # Returns the current necessary information on vulnerable_instances for a
87
+ # site to creating a ticket, for Vulnerability mode
88
+ # * *Returns* :
89
+ # - Returns |vulnerability_id| |solution_ids| |references|
90
+ def self.all_new_vulns_by_vuln_id(options={})
91
+ "SELECT DISTINCT(vulnerability_id) vulnerability_id,
92
+ array_agg(DISTINCT solution_id) as solution_ids,
93
+ string_agg(DISTINCT dvr.source || ': ' || dvr.reference, ', ') as references
94
+ FROM (
95
+ SELECT asset_id, vulnerability_id
96
+ FROM fact_asset_vulnerability_finding) favf
97
+ LEFT JOIN dim_asset_vulnerability_solution USING (asset_id, vulnerability_id)
98
+ LEFT JOIN dim_vulnerability_reference dvr USING (vulnerability_id)
99
+ GROUP BY vulnerability_id
100
+ ORDER BY vulnerability_id"
101
+ end
102
+
103
+ # Returns information on the previous state of the site / tag, for IP mode
104
+ # * *Returns* :
105
+ # - Returns |asset_id| |vulnerability_id| |scan_id|
106
+ def self.last_scan_state_by_ip(options={})
107
+ "SELECT asset_id, vulnerability_id, scan_id
108
+ FROM fact_asset_scan_vulnerability_finding
109
+ WHERE scan_id = #{options[:scan_id]}
110
+ ORDER BY asset_id, vulnerability_id"
111
+ end
112
+
113
+ # Returns information on the previous state of the site, for Vulnerability
114
+ # mode
115
+ # * *Returns* :
116
+ # - Returns |vulnerability_id| |asset_ids| |scan_id|
117
+ def self.last_scan_state_by_vuln_id(options={})
118
+ "SELECT DISTINCT(vulnerability_id) vulnerability_id,
119
+ array_agg(DISTINCT asset_id) as asset_ids, scan_id
120
+ FROM fact_asset_scan_vulnerability_finding
121
+ WHERE scan_id = #{options[:scan_id]}
122
+ GROUP BY vulnerability_id, scan_id
123
+ ORDER BY vulnerability_id"
124
+ end
125
+
126
+ def self.all_new_vulns_by_ip_solutions(options={})
127
+ 'SELECT asset_id, vulnerability_id, summary, fix, url
128
+ FROM (select asset_id, vulnerability_id FROM fact_asset_scan_vulnerability_finding) fasv
129
+ JOIN dim_asset_vulnerability_solution dvs USING (asset_id, vulnerability_id)
130
+ JOIN (select solution_id, summary, fix, url FROM dim_solution) ds USING (solution_id)
131
+ ORDER BY asset_id, vulnerability_id'
132
+ end
133
+
59
134
  # Gets all delta vulns for all sites sorted by IP.
60
135
  #
61
136
  # * *Returns* :
62
- # -Returns |asset_id| |ip_address| |current_scan| |vulnerability_id||solution_id| |nexpose_id|
137
+ # -Returns |asset_id| |ip_address| |current_scan| |vulnerability_id||solution_id| |nexpose_id|
63
138
  # |url| |summary| |fix|
64
139
  #
65
- def self.all_new_vulns_by_ip(options = {})
66
- "SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
67
- subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
68
- string_agg(DISTINCT 'Summary: ' || coalesce(ds.summary, 'None') ||
69
- '|Nexpose ID: ' || ds.nexpose_id ||
70
- '|Fix: ' || coalesce(proofAsText(ds.fix), 'None') ||
71
- '|URL: ' || coalesce(ds.url, 'None'), '~') as solutions,
72
- fa.riskscore, dv.cvss_score,
73
- string_agg(DISTINCT dvr.source || ': ' || dvr.reference, ', ') as references,
74
- fasva.first_discovered, fasva.most_recently_discovered
75
- FROM (SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan
76
- FROM fact_asset_scan_vulnerability_finding fasv
77
- JOIN
78
- (
79
- SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
80
- FROM dim_asset #{createAssetString(options)}) s
81
- ON s.asset_id = fasv.asset_id AND (fasv.scan_id = s.baseline_scan OR fasv.scan_id = s.current_scan)
82
- GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan, fasv.scan_id
83
- HAVING NOT baselineComparison(fasv.scan_id, current_scan) = 'Old'
84
- ) subs
85
- JOIN dim_vulnerability dv USING (vulnerability_id)
86
- LEFT JOIN dim_vulnerability_reference dvr USING (vulnerability_id)
87
- JOIN dim_asset_vulnerability_solution davs USING (vulnerability_id)
88
- JOIN fact_asset_vulnerability_age fasva ON subs.vulnerability_id = fasva.vulnerability_id AND subs.asset_id = fasva.asset_id
89
- JOIN dim_solution ds USING (solution_id)
90
- JOIN dim_asset da ON subs.asset_id = da.asset_id
91
- JOIN fact_asset fa ON fa.asset_id = da.asset_id
92
- #{createRiskString( options[:riskScore])}
93
- GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id, dv.nexpose_id,
94
- fa.riskscore, dv.cvss_score, fasva.first_discovered, fasva.most_recently_discovered
95
- ORDER BY da.ip_address, subs.vulnerability_id"
140
+ def self.all_new_vulns_by_ip_old(options = {})
141
+ "SELECT asset_id, vulnerability_id, ip_address, riskscore, nexpose_id, cvss_score,
142
+ first_discovered, most_recently_discovered,
143
+ array_agg(solution_id) as solution_ids
144
+ FROM (SELECT asset_id, vulnerability_id FROM fact_asset_vulnerability_finding) favf
145
+ JOIN (SELECT vulnerability_id, nexpose_id, cvss_score FROM dim_vulnerability) dv USING (vulnerability_id)
146
+ JOIN (SELECT asset_id, vulnerability_id, first_discovered, most_recently_discovered FROM fact_asset_vulnerability_age) fava USING (asset_id, vulnerability_id)
147
+ JOIN (SELECT asset_id, ip_address FROM dim_asset) da USING (asset_id)
148
+ JOIN (SELECT asset_id, riskscore FROM fact_asset) fa USING (asset_id)
149
+ JOIN dim_asset_vulnerability_solution USING (asset_id, vulnerability_id)
150
+ #{createAssetString(options)}
151
+ #{createRiskString( options[:riskScore])}
152
+ GROUP BY asset_id, vulnerability_id, ip_address, riskscore, nexpose_id, cvss_score,
153
+ first_discovered, most_recently_discovered
154
+ ORDER BY asset_id, vulnerability_id"
96
155
  end
97
156
 
98
157
  # Gets all delta vulns for all sites sorted by vuln ID.
@@ -101,7 +160,7 @@ module NexposeTicketing
101
160
  # -Returns |asset_id| |ip_address| |current_scan| |vulnerability_id||solution_id| |nexpose_id|
102
161
  # |url| |summary| |fix|
103
162
  #
104
- def self.all_new_vulns_by_vuln_id(options = {})
163
+ def self.all_new_vulns_by_vuln_id_old(options = {})
105
164
  "SELECT DISTINCT on (subs.vulnerability_id) subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id, dv.title, MAX(dv.cvss_score) as cvss_score,
106
165
  string_agg(DISTINCT subs.asset_id ||
107
166
  '|' || da.ip_address ||
@@ -0,0 +1,45 @@
1
+ require 'csv'
2
+
3
+ module NexposeTicketing
4
+
5
+ class Store
6
+ def initialize()
7
+ @solutions = {}
8
+ end
9
+
10
+ # For now we don't cache anything
11
+ def self.store_exists?
12
+ return false
13
+ end
14
+
15
+ def set_path(csv_path)
16
+ @csv_path = csv_path
17
+ end
18
+
19
+ def fill_store
20
+ # Should this be a single transaction?
21
+ CSV.foreach(@csv_path, headers: true) do |row|
22
+ @solutions[row['solution_id']] = { nexpose_id: row['nexpose_id'],
23
+ summary: row['summary'],
24
+ fix: row['fix'],
25
+ url: row['url'] }
26
+ end
27
+ end
28
+
29
+ def get_solution(solution_id)
30
+ @solutions.fetch(solution_id, {})
31
+ end
32
+
33
+ def get_solutions(solution_ids)
34
+ sols = []
35
+
36
+ solution_ids.each do |s|
37
+ sol = @solutions[s]
38
+ next if sol.nil?
39
+ sols << sol
40
+ end
41
+
42
+ sols
43
+ end
44
+ end
45
+ end
@@ -27,7 +27,7 @@ module NexposeTicketing
27
27
 
28
28
  def finish
29
29
  return if @start_time == nil
30
- @time_taken = Time.at(Time.now - @start_time).utc.strftime("%H:%M:%S")
30
+ @time_taken = Time.at(Time.now - @start_time).utc.strftime('%H:%M:%S')
31
31
  @start_time = nil
32
32
 
33
33
  @log.log_message("Ticket processing took #{@time_taken} to complete.")
@@ -8,6 +8,7 @@ module NexposeTicketing
8
8
  require 'nexpose_ticketing/nx_logger'
9
9
  require 'nexpose_ticketing/version'
10
10
 
11
+ API_VERSION = '1.2.0'
11
12
  @timeout = 10800
12
13
 
13
14
  def initialize(options = nil)
@@ -21,7 +22,7 @@ module NexposeTicketing
21
22
 
22
23
  def define_query_methods
23
24
  methods = Queries.methods.grep Regexp.new (@method_suffix+'$')
24
-
25
+
25
26
  methods.each do |m|
26
27
  define_singleton_method m do |options, override=nil|
27
28
  request_query(m, options, override)
@@ -29,16 +30,582 @@ module NexposeTicketing
29
30
  end
30
31
  end
31
32
 
32
- def nexpose_login(nexpose_data)
33
- @nsc = Nexpose::Connection.new(nexpose_data[:nxconsole], nexpose_data[:nxuser], nexpose_data[:nxpasswd])
33
+ def nexpose_login(nexpose_data)
34
+ @nsc = Nexpose::Connection.new(nexpose_data[:nxconsole],
35
+ nexpose_data[:nxuser],
36
+ nexpose_data[:nxpasswd])
34
37
  @nsc.login
35
38
  @log = NexposeTicketing::NxLogger.instance
36
- @log.on_connect(nexpose_data[:nxconsole], 3780, @nsc.session_id, "{}")
39
+ @log.on_connect(nexpose_data[:nxconsole], 3780, @nsc.session_id, '{}')
37
40
 
38
41
  #After login, create the report helper
39
42
  @report_helper = NexposeReportHelper::ReportOps.new(@nsc, @timeout)
40
43
  end
41
44
 
45
+ # Logs a message if logging is enabled.
46
+ def log_message(message)
47
+ @log.log_message(message)
48
+ end
49
+
50
+ def log_debug_message(message)
51
+ @log.log_debug_message(message)
52
+ end
53
+
54
+ def get_solution_data
55
+ report_config = @report_helper.generate_sql_report_config()
56
+ report_config.add_filter('version', API_VERSION)
57
+ report_config.add_filter('query', Queries.all_solutions)
58
+ @report_helper.save_generate_cleanup_report_config(report_config)
59
+ end
60
+
61
+ def get_asset_list(item, mode)
62
+ self.send("get_#{mode}_asset_list", item)
63
+ end
64
+
65
+ def get_site_asset_list(site_id)
66
+ @nsc.assets(site_id)
67
+ end
68
+
69
+ def get_tag_asset_list(tag_id)
70
+ Nexpose::Tag.load(@nsc, tag_id).asset_ids
71
+ end
72
+
73
+ def get_vuln_instances(asset_id, severity = 0)
74
+ @nsc.list_device_vulns(asset_id).select {|vuln| vuln.severity >= severity}
75
+ end
76
+
77
+ def get_asset_ip(asset_id)
78
+ Nexpose::Asset.load(@nsc, asset_id).ip
79
+ end
80
+
81
+ def create_solution_hash(options, nexpose_item)
82
+ self.send("create_solution_hash#{@method_suffix}", options, nexpose_item)
83
+ end
84
+
85
+ def create_solution_hash_by_ip(options, nexpose_item)
86
+ report = all_new_vulns(options, nexpose_item)
87
+
88
+ info = {}
89
+ CSV.foreach(report) do |row|
90
+ asset_id, vulnerability_id, first_discov,
91
+ most_recently_discov, solution_ids = row
92
+
93
+ next if asset_id == 'asset_id'
94
+ unless info.key? asset_id.to_s
95
+ info[asset_id.to_s] = {}
96
+ end
97
+
98
+ info[asset_id.to_s][vulnerability_id.to_s] = {
99
+ first_discovered: first_discov,
100
+ most_recently: most_recently_discov,
101
+ solution_ids: solution_ids }
102
+ end
103
+ info
104
+ end
105
+
106
+ def create_solution_hash_by_vuln_id(options, nexpose_item)
107
+ report = all_new_vulns(options, nexpose_item)
108
+
109
+ info = {}
110
+ CSV.foreach(report) do |row|
111
+ vulnerability_id, solution_ids, references = row
112
+
113
+ next if vulnerability_id == 'vulnerability_id'
114
+ unless info.key? vulnerability_id.to_i
115
+ info[vulnerability_id.to_i] = {}
116
+ end
117
+
118
+ info[vulnerability_id.to_i] = { solution_ids: solution_ids,
119
+ refs: references }
120
+ end
121
+ info
122
+ end
123
+
124
+ # Method retrieves current state of a site / tag from Nexpose for use in a
125
+ # Ticketing Integration
126
+ #
127
+ # Params:
128
+ # - Options: The options to use to generate information
129
+ # - Nexpose_Item: The ID of the Site / Tag to generate data for
130
+ #
131
+ # Returns: CSV containing vulnerability information
132
+ def generate_initial_scan_data(options, nexpose_item)
133
+ self.send("initial_scan#{@method_suffix}", options, nexpose_item)
134
+ end
135
+
136
+ def initial_scan_by_ip(options, nexpose_item)
137
+ log_message "Getting vuln info for #{options[:scan_mode]}: " \
138
+ "#{nexpose_item}"
139
+
140
+ query_options = options.dup
141
+ query_options[:report_type] = 'initial' if options[:tag_run]
142
+ info_hash = create_solution_hash_by_ip(query_options, nexpose_item)
143
+
144
+ log_debug_message "Getting assets for #{nexpose_item}"
145
+ assets = get_asset_list(nexpose_item, options[:scan_mode])
146
+
147
+ initial_scan_file = Tempfile.new("vulnerable_items_#{nexpose_item}")
148
+ initial_scan_file.binmode
149
+
150
+ log_debug_message 'Creating CSV for helper'
151
+ CSV.open(initial_scan_file, 'wb') do |csv|
152
+ csv << ['asset_id', 'vulnerability_id', 'first_discovered',
153
+ 'most_recently_discovered', 'ip_address', 'riskscore',
154
+ 'vuln_nexpose_id', 'cvss_score', 'solution_ids']
155
+
156
+ assets.each do |asset|
157
+ if options[:tag_run]
158
+ asset_id = asset.to_s
159
+ asset_deets = Nexpose::Asset.load(@nsc, asset)
160
+ address = asset_deets.ip
161
+ risk_score = asset_deets.assessment.risk_score
162
+ else
163
+ asset_id = asset.id.to_s
164
+ address = asset.address
165
+ risk_score = asset.risk_score
166
+ end
167
+
168
+ next if risk_score < (options[:riskScore] || 0)
169
+
170
+ log_debug_message "Getting vulns for asset #{address}"
171
+ vulns = get_vuln_instances(asset_id, options[:severity])
172
+ vulns.each do |vuln|
173
+ info = info_hash[asset_id][vuln.console_id.to_s]
174
+ csv << [asset_id, vuln.console_id, info[:first_discovered],
175
+ info[:most_recently], address, risk_score,
176
+ vuln.id, vuln.cvss_score, info[:solution_ids]]
177
+ end
178
+ end
179
+ end
180
+ initial_scan_file.flush
181
+
182
+ initial_scan_file
183
+ end
184
+
185
+ def initial_scan_by_vuln_id(options, nexpose_item)
186
+ log_message "Getting vuln info for #{options[:scan_mode]}: " \
187
+ "#{nexpose_item}"
188
+ soln_ref_hash = create_solution_hash_by_vuln_id(options, nexpose_item)
189
+
190
+ log_debug_message "Getting assets for #{nexpose_item}"
191
+ assets = get_asset_list(nexpose_item, options[:scan_mode])
192
+ current_vuln_instances = {}
193
+ assets.each do |asset|
194
+ next if asset.risk_score < (options[:riskScore] || 0)
195
+
196
+ vulns = get_vuln_instances(asset.id.to_s, options[:severity])
197
+
198
+ vulns.each do |vuln|
199
+ unless current_vuln_instances.key? vuln.console_id
200
+ current_vuln_instances[vuln.console_id] = { vuln: vuln.id,
201
+ assets: [] }
202
+ end
203
+
204
+ current_vuln_instances[vuln.console_id][:assets] << asset
205
+ end
206
+ end
207
+
208
+ initial_scan_file = Tempfile.new("vulnerable_items_#{nexpose_item}")
209
+ initial_scan_file.binmode
210
+ log_debug_message 'Creating CSV for helper'
211
+
212
+ CSV.open(initial_scan_file, 'wb') do |csv|
213
+ csv << ['vulnerability_id', 'vuln_nexpose_id', 'title', 'cvss_score',
214
+ 'assets', 'description', 'solution_ids', 'references']
215
+ current_vuln_instances.each do |vuln_id, data|
216
+ vuln_def = Nexpose::VulnerabilityDefinition.load(@nsc, data[:vuln])
217
+ assets = []
218
+ data[:assets].each do |asset|
219
+ assets << "#{asset.id}|#{asset.address}|#{asset.risk_score}"
220
+ end
221
+
222
+ csv << [vuln_id, data[:vuln], vuln_def.title, vuln_def.cvss_score,
223
+ assets.join('~'), vuln_def.description,
224
+ soln_ref_hash[vuln_id][:solution_ids],
225
+ soln_ref_hash[vuln_id][:refs]]
226
+ end
227
+ end
228
+ initial_scan_file.flush
229
+
230
+ initial_scan_file
231
+ end
232
+
233
+ # Method retrieves current state of a site from Nexpose, before creating a
234
+ # diff against the state the last time the integration was run.
235
+ #
236
+ # Params:
237
+ # - Options: The options to use to generate information
238
+ #
239
+ # Returns: The current state of vulnerabilities for assets in a site.
240
+ #
241
+ def generate_delta_scan_data(options)
242
+ self.send("delta_scan#{@method_suffix}", options)
243
+ end
244
+
245
+ def delta_scan_by_ip(options)
246
+ current_vuln_instances = {}
247
+ current_vuln_ids = {}
248
+ ignored_assets = []
249
+
250
+ assets = if options[:tag_run]
251
+ [Nexpose::Asset.load(@nsc, options[:nexpose_item])]
252
+ else
253
+ get_site_asset_list(options[:nexpose_item])
254
+ end
255
+
256
+ assets.each do |asset|
257
+ risk_score = if options[:tag_run]
258
+ asset.assessment.risk_score
259
+ else
260
+ asset.risk_score
261
+ end
262
+ if risk_score < (options[:riskScore] || 0)
263
+ ignored_assets << asset.id
264
+ next
265
+ end
266
+
267
+ vulns = get_vuln_instances(asset.id, options[:severity])
268
+ current_vuln_instances[asset.id.to_s] = { asset: asset, vulns: vulns }
269
+ current_vuln_ids[asset.id.to_s] = vulns.map { |v| v.console_id }
270
+ end
271
+
272
+ state = {}
273
+ previous_state_file = get_previous_state(options,
274
+ 'last_scan_state_by_ip')
275
+
276
+ CSV.foreach(previous_state_file, headers: true) do |row|
277
+ asset_id = row['asset_id'].to_s
278
+ vuln_id = row['vulnerability_id'].to_i
279
+
280
+ next if ignored_assets.include? asset_id
281
+
282
+ if current_vuln_ids.key? asset_id
283
+ unless state.key? asset_id.to_s
284
+ state[asset_id] =
285
+ { asset: current_vuln_instances[asset_id][:asset], new: [],
286
+ same: [], old: [] }
287
+ end
288
+
289
+ success = current_vuln_ids[asset_id].delete(vuln_id)
290
+ if success.nil?
291
+ state[asset_id][:old] << vuln_id
292
+ else
293
+ vulns = current_vuln_instances[asset_id][:vulns]
294
+ state[asset_id][:same] << vulns.find { |v| v.console_id == vuln_id }
295
+ end
296
+ else
297
+ unless state.key? asset_id
298
+ state[asset_id] = { old_ticket: '', new: [], same: [], old: [] }
299
+ end
300
+
301
+ state[asset_id][:old] << vuln_id
302
+ end
303
+ end
304
+
305
+ current_vuln_ids.each_key do |asset_id|
306
+ asset_id = asset_id.to_s
307
+ unless state.key? asset_id
308
+ state[asset_id] =
309
+ { asset: current_vuln_instances[asset_id][:asset], new: [],
310
+ same: [], old: [] }
311
+ end
312
+
313
+ current_vuln_ids[asset_id].each do |vuln_id|
314
+ vulns = current_vuln_instances[asset_id][:vulns]
315
+ state[asset_id][:new] << vulns.find { |v| v.console_id == vuln_id }
316
+ end
317
+ end
318
+
319
+ state.each_key do |asset_id|
320
+ if state[asset_id][:new].size == 0 && state[asset_id][:same].size == 0
321
+ state[asset_id][:old_ticket] = ''
322
+ end
323
+ end
324
+
325
+ state
326
+ end
327
+
328
+ def delta_scan_by_vuln_id(options)
329
+ current_vuln_instances = {}
330
+ current_vuln_ids = {}
331
+ ignored_assets = []
332
+ assets = get_site_asset_list(options[:nexpose_item])
333
+
334
+ assets.each do |asset|
335
+ if asset.risk_score < (options[:riskScore] || 0)
336
+ ignored_assets << asset.id
337
+ next
338
+ end
339
+ vulns = get_vuln_instances(asset.id.to_s, options[:severity])
340
+
341
+ vulns.each do |vuln|
342
+ console_id = vuln.console_id
343
+ unless current_vuln_instances.key? console_id
344
+ current_vuln_instances[console_id] = { vuln: vuln.id,
345
+ assets: {} }
346
+ current_vuln_ids[console_id] = []
347
+ end
348
+
349
+ current_vuln_instances[console_id][:assets][asset.id] = asset
350
+ current_vuln_ids[console_id] << asset.id
351
+ end
352
+ end
353
+
354
+ state = {}
355
+ previous_state_file = get_previous_state(options,
356
+ 'last_scan_state_by_vuln_id')
357
+
358
+ CSV.foreach(previous_state_file, headers: true) do |row|
359
+ vuln_id = row['vulnerability_id'].to_i
360
+ asset_ids = row['asset_ids'][1..-2].split(',').map(&:to_i)
361
+
362
+ if current_vuln_ids.key? vuln_id
363
+ unless state.key? vuln_id
364
+ state[vuln_id] = { vuln: current_vuln_instances[vuln_id][:vuln],
365
+ new: [], same: [], old: [] }
366
+ end
367
+
368
+ asset_ids.each do |asset_id|
369
+ next if ignored_assets.include? asset_id
370
+
371
+ success = current_vuln_ids[vuln_id].delete(asset_id)
372
+
373
+ if success.nil?
374
+ asset_info = { id: asset_id, ip: get_asset_ip(asset_id) }
375
+ state[vuln_id][:old] << asset_info
376
+ else
377
+ asset = current_vuln_instances[vuln_id][:assets][asset_id]
378
+ state[vuln_id][:same] << asset
379
+ end
380
+ end
381
+ else
382
+ unless state.key? vuln_id
383
+ state[vuln_id] = { vuln: vuln_id, old_ticket: 1, new: [],
384
+ same: [], old: [] }
385
+ end
386
+
387
+ state[vuln_id][:old] << asset_ids
388
+ end
389
+ end
390
+
391
+ current_vuln_ids.each_key do |vuln_id|
392
+ unless state.key? vuln_id
393
+ state[vuln_id] =
394
+ { vuln: current_vuln_instances[vuln_id][:vuln], new: [],
395
+ same: [], old: [] }
396
+ end
397
+
398
+ current_vuln_ids[vuln_id].each do |asset_id|
399
+ asset = current_vuln_instances[vuln_id][:assets][asset_id]
400
+ state[vuln_id][:new] << asset
401
+ end
402
+ end
403
+
404
+ state.each_key do |vuln_id|
405
+ if state[vuln_id][:new].size == 0 && state[vuln_id][:same].size == 0
406
+ state[vuln_id][:old_ticket] = 1
407
+ end
408
+ end
409
+
410
+ state
411
+ end
412
+
413
+ # Method converts retrieved information from Nexpose, sorted by the current
414
+ # state, into a CSV to be parsed into tickets for sending to a third party
415
+ # service
416
+ #
417
+ # Params:
418
+ # - options: The options to use to generate information
419
+ # - close_tickets: Whether the user has ticket closures enabled
420
+ #
421
+ # Returns: A CSV containing all necessary information to create tickets
422
+ #
423
+ def generate_delta_csv(options, close_tickets)
424
+ self.send("generate_delta_csv#{@method_suffix}", options, close_tickets)
425
+ end
426
+
427
+ def generate_delta_csv_by_ip(options, close_tickets)
428
+ nexpose_item = options[:nexpose_item]
429
+
430
+ state = generate_delta_scan_data(options)
431
+ info_hash = create_solution_hash(options, nexpose_item)
432
+
433
+ delta_scan_file = Tempfile.new("delta_vulnerable_items_#{nexpose_item}")
434
+ delta_scan_file.binmode
435
+
436
+ CSV.open(delta_scan_file, 'wb') do |csv|
437
+ csv << ['asset_id', 'vulnerability_id', 'first_discovered',
438
+ 'most_recently_discovered', 'ip_address', 'riskscore',
439
+ 'vuln_nexpose_id', 'cvss_score', 'solution_ids', 'comparison']
440
+
441
+ state.each do |asset_id, vulns|
442
+ asset = vulns[:asset]
443
+ asset_id = asset.id.to_s
444
+
445
+ if options[:tag_run]
446
+ address = asset.ip
447
+ risk_score = asset.assessment.risk_score
448
+ else
449
+ address = asset.address
450
+ risk_score = asset.risk_score
451
+ end
452
+
453
+ if vulns.key? :new
454
+ vulns[:new].each do |vuln|
455
+ info = info_hash[asset_id][vuln.console_id.to_s]
456
+ csv << [asset_id, vuln.console_id, info[:first_discovered],
457
+ info[:most_recently], address, risk_score,
458
+ vuln.id, vuln.cvss_score, info[:solution_ids], 'New']
459
+ end
460
+ end
461
+
462
+ if vulns.key? :same
463
+ vulns[:same].each do |vuln|
464
+ info = info_hash[asset_id][vuln.console_id.to_s]
465
+ csv << [asset_id, vuln.console_id, info[:first_discovered],
466
+ info[:most_recently], address, risk_score,
467
+ vuln.id, vuln.cvss_score, info[:solution_ids], 'Same']
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ delta_scan_file.flush
474
+ unless close_tickets
475
+ return { new_csv: delta_scan_file }
476
+ end
477
+
478
+ old_vulns_file = Tempfile.new("delta_vuln_old_items_#{nexpose_item}")
479
+ old_vulns_file.binmode
480
+
481
+ old_vulns_mode = options[:old_vulns_mode]
482
+ CSV.open(old_vulns_file, 'wb') do |csv|
483
+ csv << %w(asset_id vulnerability_id ip_address)
484
+
485
+ if old_vulns_mode == 'old'
486
+ key = old_vulns_mode.intern
487
+ old_vulns = state.select { |asset_id, vulns| vulns[key].size > 0 }
488
+ old_vulns.each do |asset_id, vulns|
489
+ asset = old_vulns[asset_id][:asset]
490
+ vulns[:old].each do |vuln|
491
+ csv << [asset_id, vuln, asset.address]
492
+ end
493
+ end
494
+ else
495
+ key = old_vulns_mode.intern
496
+ old_vulns = state.select{ |asset_id, vulns| vulns.key? key }
497
+ old_vulns.each do |asset_id, vulns|
498
+ ip = if vulns.key? :asset
499
+ vulns[:asset].address
500
+ else
501
+ get_asset_ip(asset_id)
502
+ end
503
+ csv << [asset_id, '', ip]
504
+ end
505
+ end
506
+ end
507
+
508
+ old_vulns_file.flush
509
+ { new_csv: delta_scan_file, old_csv: old_vulns_file }
510
+ end
511
+
512
+ def generate_delta_csv_by_vuln_id(options, close_tickets)
513
+ nexpose_item = options[:nexpose_item]
514
+
515
+ state = generate_delta_scan_data(options)
516
+ info = create_solution_hash(options, nexpose_item)
517
+
518
+ delta_scan_file = Tempfile.new("delta_vulnerable_items_#{nexpose_item}")
519
+ delta_scan_file.binmode
520
+
521
+ old_tickets = []
522
+ CSV.open(delta_scan_file, 'wb') do |csv|
523
+ csv << ['vulnerability_id', 'vuln_nexpose_id', 'title', 'cvss_score',
524
+ 'assets', 'description', 'solution_ids', 'references',
525
+ 'comparison']
526
+
527
+ state.each do |vuln_id, data|
528
+ if data.has_key? :old_ticket
529
+ old_tickets << vuln_id
530
+ next
531
+ end
532
+
533
+ vuln = data[:vuln]
534
+ vuln_def = Nexpose::VulnerabilityDefinition.load(@nsc, vuln)
535
+
536
+ if data[:new].count > 0
537
+ assets = []
538
+ data[:new].each do |asset|
539
+ assets << "#{asset.id}|#{asset.address}|#{asset.risk_score}"
540
+ end
541
+ csv << [vuln_id, vuln, vuln_def.title, vuln_def.cvss_score,
542
+ assets.join('~'), vuln_def.description,
543
+ info[vuln_id][:solution_ids], info[vuln_id][:refs], 'New']
544
+
545
+ if data[:same].count > 0
546
+ assets = []
547
+ data[:same].each do |asset|
548
+ assets << "#{asset.id}|#{asset.address}|#{asset.risk_score}"
549
+ end
550
+
551
+ csv << [vuln_id, vuln, vuln_def.title, vuln_def.cvss_score,
552
+ assets.join('~'), '', '', '', 'Same']
553
+ end
554
+ elsif data[:same].count > 0
555
+ assets = []
556
+ data[:same].each do |asset|
557
+ assets << "#{asset.id}|#{asset.address}|#{asset.risk_score}"
558
+ end
559
+ csv << [vuln_id, vuln, vuln_def.title, vuln_def.cvss_score,
560
+ assets.join('~'), vuln_def.description,
561
+ info[vuln_id][:solution_ids], info[vuln_id][:refs], 'Same']
562
+ end
563
+
564
+ if data[:old].count > 0
565
+ assets = []
566
+ data[:old].each do |asset|
567
+ assets << "#{asset[:id]}|#{asset[:ip]}|"
568
+ end
569
+
570
+ csv << [vuln_id, vuln, vuln_def.title, vuln_def.cvss_score,
571
+ assets.join('~'), '', '', '', 'Old']
572
+ end
573
+ end
574
+ end
575
+ delta_scan_file.flush
576
+
577
+ unless close_tickets
578
+ return { new_csv: delta_scan_file }
579
+ end
580
+
581
+ old_vulns_file = Tempfile.new("delta_vuln_old_items_#{nexpose_item}")
582
+ old_vulns_file.binmode
583
+
584
+ CSV.open(old_vulns_file, 'wb') do |csv|
585
+ csv << %w(vulnerability_id)
586
+ old_tickets.each { |id| csv << [id] }
587
+ end
588
+
589
+ old_vulns_file.flush
590
+ { new_csv: delta_scan_file, old_csv: old_vulns_file }
591
+ end
592
+
593
+ # Returns the previous state of a site
594
+ def get_previous_state(options, query_name)
595
+ report_config = @report_helper.generate_sql_report_config()
596
+ report_config.add_filter('version', API_VERSION)
597
+ report_config.add_filter('query', Queries.send(query_name, options))
598
+ if options[:tag_run]
599
+ report_config.add_filter('device', options[:nexpose_item].to_s)
600
+ else
601
+ report_config.add_filter('site', options[:nexpose_item])
602
+ end
603
+
604
+ report_config.add_filter('vuln-severity', options[:severity] || 0)
605
+
606
+ @report_helper.save_generate_cleanup_report_config(report_config)
607
+ end
608
+
42
609
  # Returns an array of all sites in the users environment.
43
610
  #
44
611
  # * *Returns* :
@@ -59,7 +626,7 @@ module NexposeTicketing
59
626
  def read_last_scans(csv_file_name)
60
627
  file_identifier_histories = Hash.new(-1)
61
628
  CSV.foreach(csv_file_name, headers: true) do |row|
62
- file_identifier_histories[row[0]] = row[1]
629
+ file_identifier_histories[row[0]] = row[1]
63
630
  end
64
631
  file_identifier_histories
65
632
  end
@@ -79,22 +646,35 @@ module NexposeTicketing
79
646
  # * *Args* :
80
647
  # - +csv_file_name+ - CSV File name.
81
648
  #
82
- def load_last_scans(options = {}, report_config = Nexpose::AdhocReportConfig.new(nil, 'sql'))
83
- report_config.add_filter('version', '1.2.0')
84
- sites = Array(options[:sites]).map!(&:to_s)
85
- tags = Array(options[:tags]).map!(&:to_s)
649
+ def load_last_scans(options = {})
650
+ @nsc.login
86
651
 
87
- if(options[:tag_run])
88
- report_config.add_filter('query', Queries.last_tag_scans)
89
- tags.each do |tag|
90
- report_config.add_filter('tag', tag)
652
+ if options[:tag_run]
653
+ # Need a scan ID for every asset associated with that tag
654
+ tags = Array(options[:tags]).map!(&:to_s)
655
+ scan_details = CSV.generate do |csv|
656
+ csv << %w(tag_id asset_id last_scan_id scan_finished)
657
+
658
+ tags.each do |t|
659
+ assets = Nexpose::Tag.load(@nsc, t).asset_ids
660
+ assets.each do |a|
661
+ latest = @nsc.asset_scan_history(a).max_by { |s| s.scan_id }
662
+ csv << [t, a, latest.scan_id, latest.end_time]
663
+ end
664
+ end
91
665
  end
92
666
  else
93
- report_config.add_filter('query', Queries.last_scans)
667
+ sites = Array(options[:sites]).map!(&:to_s)
668
+ scan_details = CSV.generate do |csv|
669
+ csv << %w(site_id last_scan_id finished)
670
+ sites.each do |s|
671
+ scan = @nsc.last_scan(s)
672
+ csv << [s, scan.scan_id, scan.end_time]
673
+ end
674
+ end
94
675
  end
95
676
 
96
- report_output = report_config.generate(@nsc, @timeout)
97
- csv_output = CSV.parse(report_output.chomp, headers: :first_row)
677
+ csv_output = CSV.parse(scan_details.chomp, headers: :first_row)
98
678
 
99
679
  #We only care about sites we are monitoring.
100
680
  trimmed_csv = []
@@ -125,11 +705,9 @@ module NexposeTicketing
125
705
  trimmed_csv << CSV::Row.new('tag_id,last_scan_fingerprint'.split(','), "#{current_tag_id},#{Digest::MD5::hexdigest(tag_finger_print)}".split(','))
126
706
  end
127
707
  else
128
- trimmed_csv << report_output.lines.first
129
- csv_output.each do |row|
130
- if sites.include? row[0].to_s
131
- trimmed_csv << row
132
- end
708
+ trimmed_csv << scan_details.lines.first
709
+ csv_output.each do |row|
710
+ trimmed_csv << row
133
711
  end
134
712
  end
135
713
 
@@ -149,7 +727,7 @@ module NexposeTicketing
149
727
  if options[:vulnerabilityCategories].nil? || options[:vulnerabilityCategories].empty?
150
728
  return nil
151
729
  end
152
-
730
+
153
731
  filter = options[:vulnerabilityCategories].strip.split(',')
154
732
  filter.map { |category| "include:#{category}" }.join(',')
155
733
  end
@@ -162,38 +740,19 @@ module NexposeTicketing
162
740
  file_identifier_histories
163
741
  end
164
742
 
165
- def generate_tag_asset_list(options = {}, report_config = Nexpose::AdhocReportConfig.new(nil, 'sql'))
166
- report_config.add_filter('version', '1.2.0')
167
- tags = Array(options[:tags])
168
- report_config.add_filter('query', Queries.last_tag_scans)
169
- tags.each { |tag| report_config.add_filter('tag', tag) }
170
-
171
- report_output = report_config.generate(@nsc, @timeout)
172
- csv_output = CSV.parse(report_output.chomp, headers: :first_row)
173
- trimmed_csv = []
174
- trimmed_csv << 'asset_id, last_scan_id'
175
- current_tag_id = nil
176
- csv_output.each do |row|
177
- if (tags.include? row[0].to_s) && (row[0].to_i != current_tag_id)
178
- if(current_tag_id.nil?)
179
- #Initial run
180
- current_tag_id = row[0].to_i
181
- else
182
- #New tag ID, finish off the previous tag asset list and start on the new one
183
- save_to_file(options[:csv_file], trimmed_csv)
184
- current_tag_id = row[0].to_i
185
- trimmed_csv = []
743
+ def generate_tag_asset_list(options = {})
744
+ tags = Array(options[:tags]).map!(&:to_s)
186
745
 
187
- # TODO: test this change
188
- trimmed_csv << 'asset_id, last_scan_id'
189
- end
190
- end
746
+ tags.each do |t|
747
+ trimmed_csv = ['asset_id, last_scan_id']
191
748
 
192
- if(current_tag_id == row[0].to_i)
193
- trimmed_csv << "#{row[1].to_s},#{row[2].to_s}"
749
+ assets = Nexpose::Tag.load(@nsc, t).asset_ids
750
+ assets.each do |a|
751
+ scan = @nsc.asset_scan_history(a).max_by { |s| s.scan_id }
752
+ trimmed_csv << "#{a},#{scan.scan_id}"
194
753
  end
754
+ save_to_file(options[:csv_file], trimmed_csv)
195
755
  end
196
- save_to_file(options[:csv_file], trimmed_csv) if trimmed_csv.any?
197
756
  end
198
757
 
199
758
  # Saves CSV scan information to disk
@@ -203,7 +762,7 @@ module NexposeTicketing
203
762
  #
204
763
  def save_to_file(csv_file_name, trimmed_csv, saved_file = nil)
205
764
  unless saved_file.nil?
206
- saved_file.open(csv_file_name, 'w') { |file| file.puts(trimmed_csv) }
765
+ saved_file.open(csv_file_name, 'w') { |file| file.puts(trimmed_csv) }
207
766
  return
208
767
  end
209
768
 
@@ -227,12 +786,13 @@ module NexposeTicketing
227
786
  end
228
787
 
229
788
  def request_query(query_name, options = {}, nexpose_items = nil)
230
- items =
231
- if nexpose_items
232
- Array(nexpose_items)
233
- else
234
- options[:nexpose_item] ? nil : options["#{options[:scan_mode]}s".intern]
235
- end
789
+ items = if nexpose_items
790
+ Array(nexpose_items)
791
+ elsif options[:nexpose_item]
792
+ nil
793
+ else
794
+ options["#{options[:scan_mode]}s".intern]
795
+ end
236
796
 
237
797
  report_config = generate_config(query_name, options, items)
238
798
  end
@@ -250,14 +810,20 @@ module NexposeTicketing
250
810
  report_config.add_filter('version', '1.2.0')
251
811
  report_config.add_filter('query', Queries.send(query_name, options))
252
812
 
253
- id_type = options[:tag_run] ? 'tag' : 'site'
813
+ id_type = if options[:report_type].to_s == 'initial'
814
+ 'tag'
815
+ elsif options[:tag_run]
816
+ 'device'
817
+ else
818
+ 'site'
819
+ end
254
820
 
255
821
  if nexpose_items != nil && !nexpose_items.empty?
256
822
  nexpose_items.each { |id| report_config.add_filter(id_type, id) }
257
823
  else
258
824
  item = options[:tag_run] ? options[:tag] : nexpose_item
259
825
  report_config.add_filter(id_type, item)
260
- end
826
+ end
261
827
 
262
828
  report_config.add_filter('vuln-severity', options[:severity] || 0)
263
829
 
@@ -272,12 +838,12 @@ module NexposeTicketing
272
838
 
273
839
  def method_missing(name, *args, &block)
274
840
  full_method_name = "#{name}#{@method_suffix}"
275
-
841
+
276
842
  unless Queries.respond_to? full_method_name
277
843
  fail %Q{Query request "#{full_method_name}" not understood}
278
844
  end
279
845
 
280
- @log.log_message %Q{Creating query request "#{full_method_name}".}
846
+ log_message %Q{Creating query request "#{full_method_name}".}
281
847
  request_query(full_method_name, args[0], args[1])
282
848
  end
283
849