nexpose_ticketing 1.0.2 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -0
- data/bin/nexpose_ticketing +45 -0
- data/lib/nexpose_ticketing/config/jira.config +5 -1
- data/lib/nexpose_ticketing/config/remedy.config +4 -1
- data/lib/nexpose_ticketing/config/servicedesk.config +4 -1
- data/lib/nexpose_ticketing/config/servicenow.config +4 -1
- data/lib/nexpose_ticketing/config/ticket_service.config +13 -5
- data/lib/nexpose_ticketing/helpers/base_helper.rb +43 -0
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +149 -97
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +37 -52
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +30 -49
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +39 -56
- data/lib/nexpose_ticketing/modes/base_mode.rb +199 -0
- data/lib/nexpose_ticketing/modes/default_mode.rb +50 -0
- data/lib/nexpose_ticketing/modes/ip_mode.rb +52 -0
- data/lib/nexpose_ticketing/modes/vulnerability_mode.rb +50 -0
- data/lib/nexpose_ticketing/nx_logger.rb +44 -33
- data/lib/nexpose_ticketing/queries.rb +30 -27
- data/lib/nexpose_ticketing/report_helper.rb +1 -0
- data/lib/nexpose_ticketing/ticket_metrics.rb +39 -0
- data/lib/nexpose_ticketing/ticket_repository.rb +65 -206
- data/lib/nexpose_ticketing/ticket_service.rb +470 -441
- data/lib/nexpose_ticketing/version.rb +1 -1
- metadata +15 -16
- data/bin/nexpose_jira +0 -27
- data/bin/nexpose_remedy +0 -27
- data/bin/nexpose_servicedesk +0 -27
- data/bin/nexpose_servicenow +0 -27
- data/lib/nexpose_ticketing/common_helper.rb +0 -344
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'nexpose_ticketing/nx_logger'
|
2
|
+
|
3
|
+
class BaseMode
|
4
|
+
|
5
|
+
# Initializes the mode
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
@log = NexposeTicketing::NxLogger.instance
|
9
|
+
end
|
10
|
+
|
11
|
+
# True if this mode supports ticket updates
|
12
|
+
def updates_supported?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the fields used to identify individual tickets
|
17
|
+
def get_matching_fields
|
18
|
+
['']
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the ticket's title
|
22
|
+
def get_title(row)
|
23
|
+
"#{nil} => #{nil}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generates a unique identifier for a ticket
|
27
|
+
def get_nxid(nexpose_id, row)
|
28
|
+
"#{nil}c#{nil}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the base ticket description object
|
32
|
+
def get_description(nexpose_id, row)
|
33
|
+
description
|
34
|
+
end
|
35
|
+
|
36
|
+
# Updates the ticket description based on row data
|
37
|
+
def update_description(description, row)
|
38
|
+
description
|
39
|
+
end
|
40
|
+
|
41
|
+
# Converts the ticket description object into a formatted string
|
42
|
+
def print_description(description)
|
43
|
+
''
|
44
|
+
end
|
45
|
+
|
46
|
+
# Cuts the title down to size specified in config, if necessary
|
47
|
+
def truncate_title(title)
|
48
|
+
return title if title.length <= @options[:max_title_length]
|
49
|
+
"#{title[0, @options[:max_title_length]-3]}..."
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the suffix used for query method names
|
53
|
+
def get_query_suffix
|
54
|
+
'_by_ip'
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_queries
|
58
|
+
file_name = "#{self.class.to_s.downcase}_queries.rb"
|
59
|
+
file_path = File.join(File.dirname(__FILE__), "../queries/#{file_name}")
|
60
|
+
@queries = []
|
61
|
+
|
62
|
+
@queries << YAML.load_file(file_path)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generates a final description string based on a description hash.
|
66
|
+
#
|
67
|
+
# - +ticket_desc+ - The ticket description to be formatted.
|
68
|
+
# - +nxid+ - The NXID to be appended to the ticket.
|
69
|
+
#
|
70
|
+
# * *Returns* :
|
71
|
+
# - String containing ticket description text.
|
72
|
+
#
|
73
|
+
def finalize_description(ticket_desc, nxid)
|
74
|
+
nxid_line = "\n\n\n#{nxid}"
|
75
|
+
|
76
|
+
#If the ticket is too long, truncate it to fit the NXID
|
77
|
+
max_len = @options[:max_ticket_length]
|
78
|
+
if max_len > 0 and (ticket_desc + nxid_line).length > max_len
|
79
|
+
#Leave space for newline characters, nxid and ellipsis (...)
|
80
|
+
ticket_desc = ticket_desc[0...max_len - (nxid_line.length+5)]
|
81
|
+
ticket_desc << "\n...\n"
|
82
|
+
end
|
83
|
+
|
84
|
+
"#{ticket_desc}#{nxid_line}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Formats the row data to be inserted into a 'D' or 'I' mode ticket description.
|
88
|
+
#
|
89
|
+
# - +row+ - CSV row containing vulnerability data.
|
90
|
+
#
|
91
|
+
# * *Returns* :
|
92
|
+
# - String formatted with vulnerability data.
|
93
|
+
#
|
94
|
+
def get_vuln_info(row)
|
95
|
+
ticket = get_header(row)
|
96
|
+
ticket << get_discovery_info(row)
|
97
|
+
ticket << get_references(row)
|
98
|
+
ticket << "\n#{get_solutions(row)}"
|
99
|
+
ticket.gsub("\n", "\n ")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Generates the vulnerability header from the row data.
|
103
|
+
#
|
104
|
+
# - +row+ - CSV row containing vulnerability data.
|
105
|
+
#
|
106
|
+
# * *Returns* :
|
107
|
+
# - String formatted with vulnerability data.
|
108
|
+
#
|
109
|
+
def get_header(row)
|
110
|
+
ticket = "\n=============================="
|
111
|
+
ticket << "\nVulnerability ID: #{row['vulnerability_id']}"
|
112
|
+
ticket << "\nNexpose ID: #{row['vuln_nexpose_id']}"
|
113
|
+
ticket << "\nCVSS Score: #{row['cvss_score']}"
|
114
|
+
ticket << "\n=============================="
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generates a short summary for a vulnerability.
|
118
|
+
#
|
119
|
+
# - +row+ - CSV row containing vulnerability data.
|
120
|
+
#
|
121
|
+
# * *Returns* :
|
122
|
+
# - String containing a short summary of the vulnerability.
|
123
|
+
#
|
124
|
+
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
|
128
|
+
summary.length <= 100 ? summary : summary[0...100]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Formats the solutions for a vulnerability in a format suitable to be inserted into a ticket.
|
132
|
+
#
|
133
|
+
# - +row+ - CSV row containing vulnerability data.
|
134
|
+
#
|
135
|
+
# * *Returns* :
|
136
|
+
# - String formatted with solution information.
|
137
|
+
#
|
138
|
+
def get_solutions(row)
|
139
|
+
row['solutions'].to_s.gsub('|', "\n").gsub('~', "\n--\n")
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_discovery_info(row)
|
143
|
+
return '' if row['first_discovered'].to_s == ""
|
144
|
+
info = "\nFirst Seen: #{row['first_discovered']}\n"
|
145
|
+
info << "Last Seen: #{row['most_recently_discovered']}\n"
|
146
|
+
info
|
147
|
+
end
|
148
|
+
|
149
|
+
# Formats the references for a vulnerability in a format suitable to be inserted into a ticket.
|
150
|
+
#
|
151
|
+
# - +row+ - CSV row containing vulnerability data.
|
152
|
+
#
|
153
|
+
# * *Returns* :
|
154
|
+
# - String formatted with source and reference.
|
155
|
+
#
|
156
|
+
def get_references(row)
|
157
|
+
num_refs = @options[:max_num_refs]
|
158
|
+
return '' if row['references'].nil? || num_refs == 0
|
159
|
+
|
160
|
+
refs = row['references'].split(', ')[0..num_refs]
|
161
|
+
refs[num_refs] = '...' if refs.count > num_refs
|
162
|
+
"\nSources:\n#{refs.map { |r| " - #{r}" }.join("\n")}\n"
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
# Returns the assets for a vulnerability in a format suitable to be inserted into a ticket.
|
167
|
+
#
|
168
|
+
# - +row+ - CSV row containing vulnerability data.
|
169
|
+
#
|
170
|
+
# * *Returns* :
|
171
|
+
# - String formatted with affected assets.
|
172
|
+
#
|
173
|
+
def get_assets(row)
|
174
|
+
assets = "\n#{row['comparison'] || 'Affected' } Assets\n"
|
175
|
+
|
176
|
+
row['assets'].to_s.split('~').each do |a|
|
177
|
+
asset = a.split('|')
|
178
|
+
assets << " - #{asset[1]} #{"\t(#{asset[2]})" if !asset[2].empty?}\n"
|
179
|
+
end
|
180
|
+
assets
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the relevant row values for printing.
|
184
|
+
#
|
185
|
+
# - +fields+ - The fields which are relevant to the ticket.
|
186
|
+
# - +row+ - CSV row containing vulnerability data.
|
187
|
+
#
|
188
|
+
# * *Returns* :
|
189
|
+
# - String formatted with relevant fields.
|
190
|
+
#
|
191
|
+
def get_field_info(fields, row)
|
192
|
+
fields.map { |x| "#{x.gsub("_", " ")}: #{row[x]}" }.join(", ")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Catch-all method when a unknown method is called
|
196
|
+
def method_missing(name, *args)
|
197
|
+
@log.log_message("Method #{name} not implemented for #{@options[:ticket_mode]} mode.")
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative './base_mode.rb'
|
2
|
+
|
3
|
+
class DefaultMode < BaseMode
|
4
|
+
|
5
|
+
# Initializes the mode
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
# True if this mode supports ticket updates
|
11
|
+
def updates_supported?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the fields used to identify individual tickets
|
16
|
+
def get_matching_fields
|
17
|
+
['ip_address', 'vulnerability_id']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the ticket's title
|
21
|
+
def get_title(row)
|
22
|
+
truncate_title "#{row['ip_address']} => #{get_short_summary(row)}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generates a unique identifier for a ticket
|
26
|
+
def get_nxid(nexpose_id, row)
|
27
|
+
"#{nexpose_id}d#{row['asset_id']}d#{row['vulnerability_id']}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the base ticket description object
|
31
|
+
def get_description(nexpose_id, row)
|
32
|
+
description = { nxid: "NXID: #{get_nxid(nexpose_id, row)}" }
|
33
|
+
fields = ['header', 'references', 'solutions']
|
34
|
+
fields.each { |f| description[f.intern] = self.send("get_#{f}", row) }
|
35
|
+
description[:header] << get_discovery_info(row)
|
36
|
+
description
|
37
|
+
end
|
38
|
+
|
39
|
+
# Updates the ticket description based on row data
|
40
|
+
def update_description(description, row)
|
41
|
+
description
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the ticket description object into a formatted string
|
45
|
+
def print_description(description)
|
46
|
+
fields = [:header, :references, :solutions].map { |f| description[f] }
|
47
|
+
finalize_description(fields.join("\n"),
|
48
|
+
description[:nxid])
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative './base_mode.rb'
|
2
|
+
|
3
|
+
class IPMode < BaseMode
|
4
|
+
|
5
|
+
# Initializes the mode
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the fields used to identify individual tickets
|
11
|
+
def get_matching_fields
|
12
|
+
['ip_address']
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the ticket's title
|
16
|
+
def get_title(row)
|
17
|
+
truncate_title "#{row['ip_address']} => Vulnerabilities"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generates a unique identifier for a ticket
|
21
|
+
def get_nxid(nexpose_id, row)
|
22
|
+
"#{nexpose_id}i#{row['ip_address']}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the base ticket description object
|
26
|
+
def get_description(nexpose_id, row)
|
27
|
+
description = { nxid: "NXID: #{get_nxid(nexpose_id, row)}" }
|
28
|
+
status = row['comparison']
|
29
|
+
description[:ticket_status] = status
|
30
|
+
header = "++ #{status} Vulnerabilities ++\n" if !status.nil?
|
31
|
+
description[:vulnerabilities] = [ header.to_s + get_vuln_info(row) ]
|
32
|
+
description
|
33
|
+
end
|
34
|
+
|
35
|
+
# Updates the ticket description based on row data
|
36
|
+
def update_description(description, row)
|
37
|
+
header = ""
|
38
|
+
if description[:ticket_status] != row['comparison']
|
39
|
+
header = "++ #{row['comparison']} Vulnerabilities ++\n"
|
40
|
+
description[:ticket_status] = row['comparison']
|
41
|
+
end
|
42
|
+
|
43
|
+
description[:vulnerabilities] << "#{header}#{get_vuln_info(row)}"
|
44
|
+
description
|
45
|
+
end
|
46
|
+
|
47
|
+
# Converts the ticket description object into a formatted string
|
48
|
+
def print_description(description)
|
49
|
+
finalize_description(description[:vulnerabilities].join("\n"),
|
50
|
+
description[:nxid])
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative './base_mode.rb'
|
2
|
+
|
3
|
+
class VulnerabilityMode < BaseMode
|
4
|
+
|
5
|
+
# Initializes the mode
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the fields used to identify individual tickets
|
11
|
+
def get_matching_fields
|
12
|
+
['vulnerability_id']
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the ticket's title
|
16
|
+
def get_title(row)
|
17
|
+
truncate_title "Vulnerability: #{row['title']}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generates a unique identifier for a ticket
|
21
|
+
def get_nxid(nexpose_id, row)
|
22
|
+
"#{nexpose_id}v#{row['vulnerability_id']}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the suffix used for query method names
|
26
|
+
def get_query_suffix
|
27
|
+
'_by_vuln_id'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the base ticket description object
|
31
|
+
def get_description(nexpose_id, row)
|
32
|
+
description = { nxid: "NXID: #{get_nxid(nexpose_id, row)}" }
|
33
|
+
fields = ['header', 'references', 'solutions', 'assets']
|
34
|
+
fields.each { |f| description[f.intern] = self.send("get_#{f}", row) }
|
35
|
+
description
|
36
|
+
end
|
37
|
+
|
38
|
+
# Updates the ticket description based on row data
|
39
|
+
def update_description(description, row)
|
40
|
+
description[:assets] += "\n#{get_assets(row)}"
|
41
|
+
description
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the ticket description object into a formatted string
|
45
|
+
def print_description(description)
|
46
|
+
fields = [:header, :assets, :references, :solutions]
|
47
|
+
finalize_description(fields.map { |f| description[f] }.join("\n"),
|
48
|
+
description[:nxid])
|
49
|
+
end
|
50
|
+
end
|
@@ -6,27 +6,23 @@ require 'singleton'
|
|
6
6
|
module NexposeTicketing
|
7
7
|
class NxLogger
|
8
8
|
include Singleton
|
9
|
-
attr_accessor :options, :statistic_key, :product, :logger_file
|
10
9
|
LOG_PATH = "./logs/rapid7_%s.log"
|
11
10
|
KEY_FORMAT = "external.integration.%s"
|
12
11
|
PRODUCT_FORMAT = "%s_%s"
|
13
12
|
|
14
13
|
DEFAULT_LOG = 'integration'
|
15
|
-
PRODUCT_RANGE =
|
14
|
+
PRODUCT_RANGE = 4..30
|
16
15
|
KEY_RANGE = 3..15
|
17
16
|
|
18
17
|
ENDPOINT = '/data/external/statistic/'
|
19
18
|
|
20
19
|
def initialize()
|
21
|
-
|
20
|
+
create_calls
|
21
|
+
@logger_file = get_log_path @product
|
22
22
|
setup_logging(true, 'info')
|
23
23
|
end
|
24
24
|
|
25
25
|
def setup_statistics_collection(vendor, product_name, gem_version)
|
26
|
-
#Remove illegal characters
|
27
|
-
vendor.to_s.gsub!('-', '_')
|
28
|
-
product_name.to_s.gsub!('-', '_')
|
29
|
-
|
30
26
|
begin
|
31
27
|
@statistic_key = get_statistic_key vendor
|
32
28
|
@product = get_product product_name, gem_version
|
@@ -35,13 +31,14 @@ module NexposeTicketing
|
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
|
-
def setup_logging(enabled, log_level = 'info')
|
39
|
-
|
40
|
-
log_message('Logging disabled.')
|
41
|
-
return
|
42
|
-
end
|
34
|
+
def setup_logging(enabled, log_level = 'info', stdout=false)
|
35
|
+
@stdout = stdout
|
43
36
|
|
44
|
-
|
37
|
+
log_message('Logging disabled.') unless enabled || @log.nil?
|
38
|
+
@enabled = enabled
|
39
|
+
return unless @enabled
|
40
|
+
|
41
|
+
@logger_file = get_log_path @product
|
45
42
|
|
46
43
|
require 'logger'
|
47
44
|
directory = File.dirname(@logger_file)
|
@@ -58,24 +55,19 @@ module NexposeTicketing
|
|
58
55
|
log_message("Logging enabled at level <#{log_level}>")
|
59
56
|
end
|
60
57
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
# Logs an error message
|
72
|
-
def log_error_message(message)
|
73
|
-
@log.error(message) unless @log.nil?
|
58
|
+
def create_calls
|
59
|
+
levels = [:info, :debug, :error, :warn]
|
60
|
+
levels.each do |level|
|
61
|
+
method_name =
|
62
|
+
define_singleton_method("log_#{level.to_s}_message") do |message|
|
63
|
+
puts message if @stdout
|
64
|
+
@log.send(level, message) unless !@enabled || @log.nil?
|
65
|
+
end
|
66
|
+
end
|
74
67
|
end
|
75
68
|
|
76
|
-
|
77
|
-
|
78
|
-
@log.warn(message) unless @log.nil?
|
69
|
+
def log_message(message)
|
70
|
+
log_info_message message
|
79
71
|
end
|
80
72
|
|
81
73
|
def log_stat_message(message)
|
@@ -92,13 +84,28 @@ module NexposeTicketing
|
|
92
84
|
return nil
|
93
85
|
end
|
94
86
|
|
87
|
+
vendor.gsub!('-', '_')
|
88
|
+
vendor.slice! vendor.rindex('_') until vendor.count('_') <= 1
|
89
|
+
|
90
|
+
vendor.delete! "^A-Za-z0-9\_"
|
91
|
+
|
95
92
|
KEY_FORMAT % vendor[0...KEY_RANGE.max].downcase
|
96
93
|
end
|
97
94
|
|
98
95
|
def get_product(product, version)
|
99
|
-
return nil if (product.nil? ||
|
96
|
+
return nil if ((product.nil? || product.empty?) ||
|
97
|
+
(version.nil? || version.empty?))
|
98
|
+
|
99
|
+
product.gsub!('-', '_')
|
100
|
+
product.slice! product.rindex('_') until product.count('_') <= 1
|
101
|
+
|
102
|
+
product.delete! "^A-Za-z0-9\_"
|
103
|
+
version.delete! "^A-Za-z0-9\.\-"
|
104
|
+
|
100
105
|
product = (PRODUCT_FORMAT % [product, version])[0...PRODUCT_RANGE.max]
|
101
106
|
|
107
|
+
product.slice! product.rindex(/[A-Z0-9]/i)+1..-1
|
108
|
+
|
102
109
|
if product.length < PRODUCT_RANGE.min
|
103
110
|
log_stat_message("Product length below minimum <#{PRODUCT_RANGE.min}>.")
|
104
111
|
return nil
|
@@ -107,9 +114,12 @@ module NexposeTicketing
|
|
107
114
|
end
|
108
115
|
|
109
116
|
def generate_payload(statistic_value='')
|
110
|
-
|
111
|
-
|
112
|
-
|
117
|
+
product_name, separator, version = @product.to_s.rpartition('_')
|
118
|
+
payload_value = {'version' => version}.to_json
|
119
|
+
|
120
|
+
payload = {'statistic-key' => @statistic_key.to_s,
|
121
|
+
'statistic-value' => payload_value,
|
122
|
+
'product' => product_name}
|
113
123
|
JSON.generate(payload)
|
114
124
|
end
|
115
125
|
|
@@ -126,6 +136,7 @@ module NexposeTicketing
|
|
126
136
|
log_stat_message "Received code #{response.code} from Nexpose console."
|
127
137
|
log_stat_message "Received message #{response.msg} from Nexpose console."
|
128
138
|
log_stat_message 'Finished sending statistics data to Nexpose.'
|
139
|
+
|
129
140
|
response.code
|
130
141
|
end
|
131
142
|
|
@@ -4,9 +4,8 @@ module NexposeTicketing
|
|
4
4
|
# for Nexpose.
|
5
5
|
# Copyright:: Copyright (c) 2014 Rapid7, LLC.
|
6
6
|
module Queries
|
7
|
-
|
8
7
|
# Formats SQL query for riskscore based on user config options.
|
9
|
-
|
8
|
+
#
|
10
9
|
# * *Args* :
|
11
10
|
# - +riskScore+ - riskscore for assets to match in results of query.
|
12
11
|
#
|
@@ -63,8 +62,9 @@ module NexposeTicketing
|
|
63
62
|
# -Returns |asset_id| |ip_address| |current_scan| |vulnerability_id||solution_id| |nexpose_id|
|
64
63
|
# |url| |summary| |fix|
|
65
64
|
#
|
66
|
-
def self.
|
67
|
-
"SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
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
68
|
string_agg(DISTINCT 'Summary: ' || coalesce(ds.summary, 'None') ||
|
69
69
|
'|Nexpose ID: ' || ds.nexpose_id ||
|
70
70
|
'|Fix: ' || coalesce(proofAsText(ds.fix), 'None') ||
|
@@ -90,7 +90,7 @@ module NexposeTicketing
|
|
90
90
|
JOIN dim_asset da ON subs.asset_id = da.asset_id
|
91
91
|
JOIN fact_asset fa ON fa.asset_id = da.asset_id
|
92
92
|
#{createRiskString( options[:riskScore])}
|
93
|
-
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id,
|
93
|
+
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id, dv.nexpose_id,
|
94
94
|
fa.riskscore, dv.cvss_score, fasva.first_discovered, fasva.most_recently_discovered
|
95
95
|
ORDER BY da.ip_address, subs.vulnerability_id"
|
96
96
|
end
|
@@ -102,7 +102,7 @@ module NexposeTicketing
|
|
102
102
|
# |url| |summary| |fix|
|
103
103
|
#
|
104
104
|
def self.all_new_vulns_by_vuln_id(options = {})
|
105
|
-
"SELECT DISTINCT on (subs.vulnerability_id) subs.vulnerability_id, dv.title, MAX(dv.cvss_score) as cvss_score,
|
105
|
+
"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
106
|
string_agg(DISTINCT subs.asset_id ||
|
107
107
|
'|' || da.ip_address ||
|
108
108
|
'|' || coalesce(da.host_name, '') ||
|
@@ -132,7 +132,7 @@ module NexposeTicketing
|
|
132
132
|
JOIN fact_asset_vulnerability_age fasva ON subs.vulnerability_id = fasva.vulnerability_id AND subs.asset_id = fasva.asset_id
|
133
133
|
#{createRiskString(options[:riskScore])}
|
134
134
|
|
135
|
-
GROUP BY subs.vulnerability_id, dv.title
|
135
|
+
GROUP BY subs.vulnerability_id, dv.title, dv.nexpose_id
|
136
136
|
ORDER BY subs.vulnerability_id"
|
137
137
|
end
|
138
138
|
|
@@ -145,8 +145,9 @@ module NexposeTicketing
|
|
145
145
|
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
146
146
|
# |url| |summary| |fix|
|
147
147
|
#
|
148
|
-
def self.
|
149
|
-
"SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
148
|
+
def self.new_vulns_since_scan_by_ip(options = {})
|
149
|
+
"SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
150
|
+
subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
150
151
|
string_agg(DISTINCT 'Summary: ' || coalesce(ds.summary, 'None') ||
|
151
152
|
'|Nexpose ID: ' || ds.nexpose_id ||
|
152
153
|
'|Fix: ' || coalesce(proofAsText(ds.fix), 'None') ||
|
@@ -173,7 +174,7 @@ module NexposeTicketing
|
|
173
174
|
AND subs.current_scan > #{options[:scan_id]}
|
174
175
|
JOIN fact_asset fa ON fa.asset_id = da.asset_id
|
175
176
|
#{createRiskString(options[:riskScore])}
|
176
|
-
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id,
|
177
|
+
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id, dv.nexpose_id,
|
177
178
|
fa.riskscore, dv.cvss_score, fasva.first_discovered, fasva.most_recently_discovered
|
178
179
|
ORDER BY da.ip_address, subs.vulnerability_id"
|
179
180
|
end
|
@@ -188,8 +189,9 @@ module NexposeTicketing
|
|
188
189
|
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
189
190
|
# |url| |summary| |fix|
|
190
191
|
#
|
191
|
-
def self.
|
192
|
-
"SELECT DISTINCT on (subs.vulnerability_id) subs.vulnerability_id, dv.
|
192
|
+
def self.new_vulns_since_scan_by_vuln_id(options = {})
|
193
|
+
"SELECT DISTINCT on (subs.vulnerability_id) subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
194
|
+
dv.title, MAX(dv.cvss_score) as cvss_score,
|
193
195
|
string_agg(DISTINCT subs.asset_id ||
|
194
196
|
'|' || da.ip_address ||
|
195
197
|
'|' || coalesce(da.host_name, '') ||
|
@@ -219,7 +221,7 @@ module NexposeTicketing
|
|
219
221
|
AND subs.current_scan > #{options[:scan_id]}
|
220
222
|
JOIN fact_asset fa ON fa.asset_id = da.asset_id
|
221
223
|
#{createRiskString(options[:riskScore])}
|
222
|
-
GROUP BY subs.vulnerability_id, dv.title
|
224
|
+
GROUP BY subs.vulnerability_id, dv.title, dv.nexpose_id
|
223
225
|
ORDER BY vulnerability_id"
|
224
226
|
end
|
225
227
|
|
@@ -266,8 +268,9 @@ module NexposeTicketing
|
|
266
268
|
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
267
269
|
# |url| |summary| |fix| |comparison|
|
268
270
|
#
|
269
|
-
def self.
|
270
|
-
"SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
271
|
+
def self.all_vulns_since_scan_by_ip(options = {})
|
272
|
+
"SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
273
|
+
subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
271
274
|
string_agg(DISTINCT 'Summary: ' || coalesce(ds.summary, 'None') ||
|
272
275
|
'|Nexpose ID: ' || ds.nexpose_id ||
|
273
276
|
'|Fix: ' || coalesce(proofAsText(ds.fix), 'None') ||
|
@@ -293,12 +296,11 @@ module NexposeTicketing
|
|
293
296
|
JOIN fact_asset fa ON fa.asset_id = subs.asset_id
|
294
297
|
#{createRiskString(options[:riskScore])}
|
295
298
|
AND subs.current_scan > #{options[:scan_id]}
|
296
|
-
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id,
|
299
|
+
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id, dv.nexpose_id,
|
297
300
|
fa.riskscore, dv.cvss_score, subs.comparison
|
298
|
-
|
299
301
|
UNION
|
300
|
-
|
301
|
-
|
302
|
+
SELECT DISTINCT on (da.ip_address, subs.vulnerability_id) subs.asset_id, da.ip_address, da.host_name, subs.current_scan,
|
303
|
+
subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
302
304
|
string_agg(DISTINCT 'Summary: ' || coalesce(ds.summary, 'None') ||
|
303
305
|
'|Nexpose ID: ' || ds.nexpose_id ||
|
304
306
|
'|Fix: ' || coalesce(proofAsText(ds.fix), 'None') ||
|
@@ -327,9 +329,8 @@ module NexposeTicketing
|
|
327
329
|
JOIN fact_asset fa ON fa.asset_id = subs.asset_id
|
328
330
|
#{createRiskString(options[:riskScore])}
|
329
331
|
AND subs.current_scan > #{options[:scan_id]}
|
330
|
-
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id,
|
332
|
+
GROUP BY subs.asset_id, da.ip_address, da.host_name, subs.current_scan, subs.vulnerability_id, dv.nexpose_id,
|
331
333
|
fa.riskscore, dv.cvss_score, fasva.first_discovered, fasva.most_recently_discovered, subs.comparison
|
332
|
-
|
333
334
|
ORDER BY ip_address, comparison"
|
334
335
|
end
|
335
336
|
|
@@ -343,8 +344,9 @@ module NexposeTicketing
|
|
343
344
|
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
344
345
|
# |url| |summary| |fix| |comparison|
|
345
346
|
#
|
346
|
-
def self.
|
347
|
-
"SELECT DISTINCT on (subs.vulnerability_id, subs.comparison) subs.vulnerability_id, dv.
|
347
|
+
def self.all_vulns_since_scan_by_vuln_id(options = {})
|
348
|
+
"SELECT DISTINCT on (subs.vulnerability_id, subs.comparison) subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
349
|
+
dv.title, MAX(dv.cvss_score) as cvss_score,
|
348
350
|
string_agg(DISTINCT subs.asset_id ||
|
349
351
|
'|' || da.ip_address ||
|
350
352
|
'|' || coalesce(da.host_name, '') ||
|
@@ -374,11 +376,12 @@ module NexposeTicketing
|
|
374
376
|
JOIN fact_asset fa ON fa.asset_id = subs.asset_id
|
375
377
|
#{createRiskString(options[:riskScore])}
|
376
378
|
AND subs.current_scan > #{options[:scan_id]}
|
377
|
-
GROUP BY subs.vulnerability_id, dv.title, subs.comparison
|
379
|
+
GROUP BY subs.vulnerability_id, dv.title, dv.nexpose_id, subs.comparison
|
378
380
|
|
379
381
|
UNION
|
380
382
|
|
381
|
-
SELECT DISTINCT on (subs.vulnerability_id, subs.comparison) subs.vulnerability_id, dv.
|
383
|
+
SELECT DISTINCT on (subs.vulnerability_id, subs.comparison) subs.vulnerability_id, dv.nexpose_id as vuln_nexpose_id,
|
384
|
+
dv.title, MAX(dv.cvss_score) as cvss_score,
|
382
385
|
string_agg(DISTINCT subs.asset_id ||
|
383
386
|
'|' || da.ip_address ||
|
384
387
|
'|' || coalesce(da.host_name, '') ||
|
@@ -410,7 +413,7 @@ module NexposeTicketing
|
|
410
413
|
JOIN fact_asset fa ON fa.asset_id = subs.asset_id
|
411
414
|
#{createRiskString(options[:riskScore])}
|
412
415
|
AND subs.current_scan > #{options[:scan_id]}
|
413
|
-
GROUP BY subs.vulnerability_id, dv.title, subs.comparison
|
416
|
+
GROUP BY subs.vulnerability_id, dv.title, dv.nexpose_id, subs.comparison
|
414
417
|
|
415
418
|
ORDER BY vulnerability_id, comparison"
|
416
419
|
end
|
@@ -462,7 +465,7 @@ module NexposeTicketing
|
|
462
465
|
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |comparison|
|
463
466
|
#
|
464
467
|
def self.old_tickets_by_vuln_id(options = {})
|
465
|
-
"SELECT subs.vulnerability_id, subs.asset_id, subs.ip_address, subs.current_scan, subs.comparison
|
468
|
+
"SELECT DISTINCT on(subs.vulnerability_id) subs.vulnerability_id, subs.asset_id, subs.ip_address, subs.current_scan, subs.comparison
|
466
469
|
FROM (
|
467
470
|
SELECT fasv.asset_id, s.ip_address, fasv.vulnerability_id, s.current_scan, baselineComparison(fasv.scan_id, s.current_scan) as comparison, fa.riskscore
|
468
471
|
FROM fact_asset_scan_vulnerability_finding fasv
|