nexpose_sccm 0.4.0

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.
@@ -0,0 +1,7 @@
1
+ vulnerability ids
2
+ msft-cve-2017-0016
3
+ msft-cve-2017-8527
4
+ msft-cve-2017-0280
5
+ msft-cve-2017-0267
6
+ ssl-cve-2011-3389-beast
7
+ windows-hotfix-ms16-136
@@ -0,0 +1,8 @@
1
+ ---
2
+ :logging:
3
+ :log_level: INFO
4
+ :log_directory: "../logs"
5
+ :log_file: output.log
6
+ :log_stdout: false
7
+ :log_file_rotation: 30
8
+ :log_file_size: 1048576
@@ -0,0 +1,8 @@
1
+ ---
2
+ :nexpose:
3
+ :host: 'nexpose_url'
4
+ :user: 'username'
5
+ :pass: 'password'
6
+ :port: '3780'
7
+ :timeout: 600
8
+ :query_timeout: 1800
@@ -0,0 +1,8 @@
1
+ ---
2
+ :postgres:
3
+ :host: 'dwh_url'
4
+ :port: 5432
5
+ :user: 'postgres'
6
+ :pass: 'password'
7
+ :db: db_name
8
+ :sslmode: prefer # Possible values disable|allow|prefer|require
@@ -0,0 +1,87 @@
1
+ ---
2
+ :queries:
3
+ :asset_best_solution:
4
+ #:nsc: Not implemented
5
+ :dwh: SELECT da.ip_address AS key, da.ip_address, da.host_name, ds.nexpose_id
6
+ FROM dim_asset_vulnerability_finding_rollup_solution davfrs
7
+ JOIN dim_asset da ON (da.asset_id = davfrs.asset_id)
8
+ JOIN dim_solution ds ON (ds.solution_id = davfrs.solution_id)
9
+ JOIN dim_vulnerability_category dvc ON (dvc.vulnerability_id = davfrs.vulnerability_id)
10
+ WHERE UPPER(dvc.category_name) = 'MICROSOFT'
11
+ GROUP BY da.ip_address, da.host_name, ds.nexpose_id
12
+ :asset_best_solution_by_site:
13
+ :nsc: SELECT da.ip_address AS key, da.ip_address, da.host_name, ds.nexpose_id, concat('site_id_', dsa.site_id) as site_id
14
+ FROM dim_asset_vulnerability_best_solution davbs
15
+ JOIN dim_asset da ON (da.asset_id = davbs.asset_id)
16
+ JOIN dim_site_asset dsa ON dsa.asset_id = davbs.asset_id AND dsa.site_id = '<site id>'
17
+ JOIN dim_vulnerability_category dvc ON (dvc.vulnerability_id = davbs.vulnerability_id)
18
+ AND UPPER(dvc.category_name) = 'MICROSOFT'
19
+ JOIN dim_solution ds ON (ds.solution_id = davbs.solution_id)
20
+ GROUP BY da.ip_address, da.host_name, ds.nexpose_id, dsa.site_id
21
+ :dwh: SELECT da.ip_address AS key, da.ip_address, da.host_name, ds.nexpose_id
22
+ FROM dim_asset_vulnerability_finding_rollup_solution davfrs
23
+ JOIN dim_asset da ON (da.asset_id = davfrs.asset_id)
24
+ JOIN dim_site_asset dsa ON dsa.asset_id = davfrs.asset_id AND dsa.site_id = '<site id>'
25
+ JOIN dim_vulnerability_category dvc ON (dvc.vulnerability_id = davfrs.vulnerability_id)
26
+ AND UPPER(dvc.category_name) = 'MICROSOFT'
27
+ JOIN dim_solution ds ON (ds.solution_id = davfrs.solution_id)
28
+ GROUP BY da.ip_address, da.host_name, ds.nexpose_id
29
+ :tag_best_solution:
30
+ :nsc: WITH tag_assets AS (
31
+ SELECT da.asset_id, da.ip_address, da.host_name
32
+ FROM dim_tag_asset dat
33
+ JOIN dim_tag dt ON (dt.tag_id = dat.tag_id)
34
+ JOIN dim_asset da ON (da.asset_id = dat.asset_id)
35
+ WHERE dt.tag_name = '<tag name>'
36
+ )
37
+ SELECT ta.ip_address as key, ta.ip_address, ta.host_name, ds.nexpose_id
38
+ FROM dim_asset_vulnerability_solution davfrs
39
+ JOIN tag_assets ta ON (ta.asset_id = davfrs.asset_id)
40
+ JOIN dim_solution ds ON (ds.solution_id = davfrs.solution_id)
41
+ JOIN dim_vulnerability_category dvc ON (dvc.vulnerability_id = davfrs.vulnerability_id)
42
+ WHERE UPPER(dvc.category_name) = 'MICROSOFT'
43
+ GROUP BY ta.ip_address, ta.host_name, ds.nexpose_id
44
+ :dwh: WITH tag_assets AS (
45
+ SELECT da.asset_id, da.ip_address, da.host_name
46
+ FROM dim_asset_tag dat
47
+ JOIN dim_tag dt ON (dt.tag_id = dat.tag_id)
48
+ JOIN dim_asset da ON (da.asset_id = dat.asset_id)
49
+ WHERE dt.name = '<tag name>'
50
+ )
51
+ SELECT ta.ip_address as key, ta.ip_address, ta.host_name, ds.nexpose_id
52
+ FROM dim_asset_vulnerability_finding_rollup_solution davfrs
53
+ JOIN tag_assets ta ON (ta.asset_id = davfrs.asset_id)
54
+ JOIN dim_solution ds ON (ds.solution_id = davfrs.solution_id)
55
+ JOIN dim_vulnerability_category dvc ON (dvc.vulnerability_id = davfrs.vulnerability_id)
56
+ WHERE UPPER(dvc.category_name) = 'MICROSOFT'
57
+ GROUP BY ta.ip_address, ta.host_name, ds.nexpose_id
58
+ :vuln_by_name: #Microsoft CVE-2017-0016: SMBv2/SMBv3 Null Dereference Denial of Service Vulnerability
59
+ :nsc: SELECT dv.nexpose_id AS key, da.ip_address, da.host_name, ds.nexpose_id
60
+ FROM dim_asset_vulnerability_best_solution davbs
61
+ JOIN dim_solution ds ON (ds.solution_id = davbs.solution_id)
62
+ JOIN dim_vulnerability dv ON (dv.vulnerability_id = davbs.vulnerability_id)
63
+ JOIN dim_asset da ON (da.asset_id = davbs.asset_id)
64
+ WHERE dv.title IN ('<vulnerability titles>') AND da.ip_address IN ('<asset_ips>')
65
+ GROUP BY da.ip_address, da.host_name, ds.nexpose_id, dv.nexpose_id
66
+ :dwh: SELECT dv.nexpose_id AS key, da.ip_address, da.host_name, ds.nexpose_id
67
+ FROM dim_asset_vulnerability_finding_solution davfs
68
+ JOIN dim_solution ds ON (ds.solution_id = davfs.solution_id)
69
+ JOIN dim_vulnerability dv ON (dv.vulnerability_id = davfs.vulnerability_id)
70
+ JOIN dim_asset da ON (da.asset_id = davfs.asset_id)
71
+ WHERE dv.title IN ('<vulnerability titles>') AND da.ip_address IN ('<asset ips>')
72
+ GROUP BY da.ip_address, da.host_name, ds.nexpose_id, dv.nexpose_id
73
+ :vuln_by_id: #msft-cve-2017-0016, msft-cve-2017-8527, msft-cve-2017-0280, msft-cve-2017-0267
74
+ :nsc: SELECT dv.nexpose_id AS key, da.ip_address, da.host_name, ds.nexpose_id
75
+ FROM dim_asset_vulnerability_best_solution davbs
76
+ JOIN dim_solution ds ON (ds.solution_id = davbs.solution_id)
77
+ JOIN dim_vulnerability dv ON (dv.vulnerability_id = davbs.vulnerability_id)
78
+ JOIN dim_asset da ON (da.asset_id = davbs.asset_id)
79
+ WHERE dv.nexpose_id IN ('<vulnerability ids>') AND da.ip_address IN ('<asset ips>')
80
+ GROUP BY dv.nexpose_id, da.ip_address, da.host_name, ds.nexpose_id
81
+ :dwh: SELECT dv.nexpose_id AS key, da.ip_address, da.host_name, ds.nexpose_id
82
+ FROM dim_asset_vulnerability_finding_solution davfs
83
+ JOIN dim_solution ds ON (ds.solution_id = davfs.solution_id)
84
+ JOIN dim_vulnerability dv ON (dv.vulnerability_id = davfs.vulnerability_id)
85
+ JOIN dim_asset da ON (da.asset_id = davfs.asset_id)
86
+ WHERE dv.nexpose_id IN ('<vulnerability ids>') AND da.ip_address IN ('<asset ips>')
87
+ GROUP BY dv.nexpose_id, da.ip_address, da.host_name, ds.nexpose_id
@@ -0,0 +1,30 @@
1
+ ---
2
+ :sccm:
3
+ ## Must be NetBIOS name
4
+ :host: 'SCCM_host_or_ip'
5
+ :port: 5986
6
+ :path: wsman
7
+ :protocol: https
8
+ :user: 'Administrator'
9
+ :pass: 'password'
10
+ :no_ssl_peer_verification: false
11
+ ## openssl s_client -showcerts -servername 10.3.23.212 -connect 10.3.23.212:5986 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin | sed -e "s/://g"
12
+ :ssl_peer_fingerprint: 'server_certificate_fingerprint'
13
+ :location: '<site_name>:\'
14
+ :namespace: 'root\sms\site_<site_name>'
15
+ :staging: '<software_download_location>'
16
+ :create_deployment_package: true
17
+ :download_updates: true
18
+ :integration_prefix: 'Rapid7'
19
+ :generate_unknown_device_report: true
20
+ :report_location: '../reports/'
21
+ :software_update_group:
22
+ ## Available scopes are one of the following: [asset_best_solution, tag_best_solution]
23
+ :scope: asset_best_solution_by_site
24
+ ## Create new software update group or update an existing
25
+ ## Available actions include one of the following: [update, create]
26
+ :action: update
27
+ ## data source can be 'nsc' or 'dwh' (Nexpose Security Console or Data Warehouse respectively)
28
+ :data_source: nsc
29
+ :software_update_group_key: key # How to group SUGs, base off of SQL query column name
30
+ :collection_key: site_id # How to group collections, base off of SQL query column name
@@ -0,0 +1,4 @@
1
+ ---
2
+ #:secret:
3
+ # :encrypt_key_dir: ".nxkeys/"
4
+ # :encrypt_cred_file: "../conf/secrets.yml.enc"
@@ -0,0 +1,294 @@
1
+ require 'nexpose_sccm/collection'
2
+ require 'nexpose_sccm/connection'
3
+ require 'nexpose_sccm/deployment_package'
4
+ require 'nexpose_sccm/device'
5
+ require 'nexpose_sccm/software_update_group'
6
+ require 'nexpose_sccm/utilities/utility_config'
7
+ require 'nexpose_sccm/utilities/nx_logger'
8
+ require 'nexpose_sccm/data_source'
9
+ require 'nexpose_sccm/version'
10
+
11
+ module NexposeSCCM
12
+ class << self
13
+
14
+ def setup(settings)
15
+ @logger = NexposeSCCM::NxLogger.instance
16
+ @logger.setup_statistics_collection(NexposeSCCM::VENDOR,
17
+ NexposeSCCM::PRODUCT,
18
+ NexposeSCCM::VERSION)
19
+ @logger.setup_logging(true,
20
+ settings[:logging][:log_level],
21
+ settings[:logging][:log_stdout],
22
+ settings[:logging][:log_file_rotation],
23
+ settings[:logging][:log_file_size])
24
+ NexposeSCCM.logger = @logger
25
+
26
+ ## Setting up SCCM connection
27
+ @logger.debug("Logging into SCCM")
28
+ sccm = settings[:sccm]
29
+ @sccm = NexposeSCCM::Connection.new(sccm[:protocol],
30
+ sccm[:host],
31
+ sccm[:port],
32
+ sccm[:path],
33
+ sccm[:location],
34
+ sccm[:user],
35
+ sccm[:pass],
36
+ sccm[:namespace],
37
+ sccm[:staging],
38
+ sccm[:no_ssl_peer_verification],
39
+ sccm[:ssl_peer_fingerprint])
40
+ ## Logging into SCCM
41
+ @sccm.login
42
+ end
43
+
44
+ def generate_updates(settings, input_file=nil)
45
+ scope = settings[:sccm][:software_update_group][:scope]
46
+ data_source = settings[:sccm][:data_source]
47
+
48
+ if settings[:sccm][:generate_unknown_device_report]
49
+ date = Time.now.strftime('%Y%m%d_%H%M%S')
50
+ report = "devices-not-found-sccm-#{date}.csv"
51
+ report_location = settings[:sccm][:report_location]
52
+ unless File.exist?(File.expand_path(report_location))
53
+ Dir.mkdir(report_location)
54
+ end
55
+
56
+ csv_report_contents = Set.new()
57
+ end
58
+
59
+ # Get known SCCM devices with details
60
+ sccm_devices = @sccm.get_devices
61
+ ## Get current list of collections
62
+ collections = @sccm.get_collections(sccm_devices)
63
+
64
+ ## Initiate the data source connection
65
+ @logger.debug("Logging into Nexpose / DWH")
66
+ @nsc = NexposeSCCM::DataSource::Connection.new(data_source,
67
+ settings[:postgres],
68
+ settings[:nexpose])
69
+
70
+ @logger.info("Successfully connected to Data Source #{data_source}")
71
+
72
+ sup_data = []
73
+ begin
74
+ query = settings[:queries][scope.to_sym][data_source.to_sym]
75
+ match_for_prompt = query.scan(/'<.*?>'/)
76
+
77
+ unless match_for_prompt.nil? || match_for_prompt.empty?
78
+ if !input_file.nil? && File.exist?(input_file)
79
+ inputs = CSV.read(input_file, :headers=>true)
80
+ end
81
+
82
+ match_for_prompt.each do |match|
83
+ if !inputs.nil? && inputs.headers.include?(match.gsub("'<",'').gsub(">'",''))
84
+ input = inputs[match.gsub("'<",'').gsub(">'",'')]
85
+ else
86
+ input = prompt("Please provide #{match} to be processed " \
87
+ "(comma separated for any lists): ").strip.split(',')
88
+ end
89
+ input = input.map{|e| "'#{e}'"}
90
+
91
+ query.gsub!(/#{match}/, "#{input.join(',')}")
92
+ end
93
+ end
94
+
95
+ @logger.debug("Retrieving Data")
96
+ sup_data = @nsc.fetch_data(query)
97
+ rescue => e
98
+ @logger.error("There was an error running the query. " \
99
+ "Check the scope and data source settings for validity: #{e}")
100
+ exit
101
+ end
102
+
103
+ unless sup_data.length > 0
104
+ @logger.debug("No updates returned using query: #{query}")
105
+ @logger.info('No updates to work on. Exiting...')
106
+ exit 0
107
+ end
108
+
109
+ ## SUP scopes are defined by the query used to run them.
110
+ @logger.info("Software Update Group scope: #{scope}")
111
+
112
+ sugs = {}
113
+
114
+ @logger.debug("Getting CI_IDS using scope: #{scope}")
115
+
116
+ @logger.info("Looping through output of length #{sup_data.length}")
117
+ sup_data.each do |o|
118
+ unless o.key?(:key)
119
+ @logger.error("Query to pull data from #{data_source} " \
120
+ "does not have the 'key' field, exiting...")
121
+ exit 1
122
+ end
123
+
124
+ key = o[settings[:sccm][:software_update_group_key].to_sym]
125
+ collection_key = o[settings[:sccm][:collection_key].to_sym]
126
+ key_ip = o[:ip_address].split(':')[0].strip
127
+ key_hostname = o[:host_name]
128
+
129
+ device = sccm_devices.select do |dev|
130
+ (dev.host_name.to_s.casecmp(key_hostname) == 0 unless key_hostname.nil?) ||
131
+ (dev.ip_address.to_a.include?(key_ip) unless key_ip.nil?)
132
+ end.first
133
+
134
+ if device.nil?
135
+ if settings[:sccm][:generate_unknown_device_report]
136
+ csv_report_contents << [key_ip, key_hostname]
137
+ end
138
+ else
139
+ collection_name = "Rapid7_#{collection_key}"
140
+ collection_match = collections.select do |collection|
141
+ collection.name.eql?(collection_name)
142
+ end.first
143
+
144
+ if collection_match.nil?
145
+ collection_match = NexposeSCCM::Collection.new(collection_name)
146
+ collection_match.members.add(device)
147
+ collections << collection_match
148
+ else
149
+ collection_match.members.add(device)
150
+ end
151
+ end
152
+
153
+ unless sugs.key?(key)
154
+ sugs[key] = {
155
+ :ci_ids => [],
156
+ :description => "Scope: #{scope}",
157
+ :name => "Rapid7_#{key}"
158
+ }
159
+ end
160
+
161
+ ## Pesky way of grabbing an UUID for the specific microsoft patch until
162
+ ## Nexpose has a better way of making this data available
163
+ nexpose_id = o[:nexpose_id]
164
+ uuid = nil
165
+ begin
166
+ match = nexpose_id.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/).captures
167
+ uuid = match[0]
168
+ rescue
169
+ @logger.debug('Must be no UUID available, going to next...')
170
+ end
171
+ next if uuid.nil?
172
+ ci_ids = @sccm.get_ci_id(uuid)
173
+ next unless ci_ids.length > 0
174
+
175
+ sugs[key][:ci_ids] += ci_ids
176
+ end
177
+
178
+ ## Get current list of SUGs for update actions
179
+ @logger.debug("Retrieving current Software Update Groups from SCCM")
180
+ groups = @sccm.get_software_update_groups
181
+
182
+ sug_objects=[]
183
+ sug_action_type = settings[:sccm][:software_update_group][:action]
184
+ ## At a minimum, we create Software Update Groups
185
+ sugs.each do |k,v|
186
+ @logger.info("Working on SUP: #{k}")
187
+ match = groups.select { |g| g.name.casecmp(v[:name]) == 0 }
188
+ ## Update if we find matching names
189
+ if sug_action_type.casecmp('update') == 0
190
+ if match.length > 0
191
+ sug = match[0]
192
+ sug.ci_ids = v[:ci_ids]
193
+ sug.description = v[:description]
194
+ res = sug.save(@sccm)
195
+ sug_objects << sug
196
+ if res
197
+ msg = "Successfully updated Software Update Group: #{v[:name]}"
198
+ @logger.debug(msg)
199
+ else
200
+ @logger.error("Error updating Software Update Group: #{v[:name]}")
201
+ end
202
+ end
203
+ end
204
+
205
+ if sug_action_type.casecmp('create') == 0 || match.empty?
206
+ ## Create sug if action is create or update didn't work.
207
+ sug = NexposeSCCM::SoftwareUpdateGroup.new(v[:name],
208
+ v[:description],
209
+ v[:ci_ids])
210
+ sug_objects << sug
211
+ res = sug.save(@sccm)
212
+ if res
213
+ msg = "Successfully created Software Update Group: #{v[:name]}"
214
+ @logger.debug(msg)
215
+ else
216
+ @logger.error("Error creating Software Update Group: #{v[:name]}")
217
+ end
218
+ end
219
+ end
220
+
221
+ # Save collections with devices
222
+ @logger.debug("Saving Collections")
223
+ collections.each do |collection|
224
+ collection.save(@sccm)
225
+ end
226
+
227
+ ## Download/deployment package creation if set to true
228
+ if settings[:sccm].key?(:create_deployment_package) &&
229
+ settings[:sccm][:create_deployment_package]
230
+ @logger.debug("Creating the Deployment Packages")
231
+ sug_objects.each do |sug_object|
232
+ name = sug_object.name
233
+ package_name = "Rapid7-Deployment-Package-#{name}"
234
+ package_path = "#{@sccm.staging}\\#{name}"
235
+
236
+ deployment_package = @sccm.get_deployment_package(package_name)
237
+ package_id =
238
+ if deployment_package.empty?
239
+ nil
240
+ else
241
+ deployment_package.first[:package_id]
242
+ end
243
+ #deployment_package.empty? ? nil : deployment_package.first[:package_id]
244
+ package = NexposeSCCM::DeploymentPackage.new(package_name,
245
+ name,
246
+ package_path,
247
+ package_id)
248
+
249
+ # Don't process if no ci ids for SUG
250
+ unless sug_object.ci_ids.empty?
251
+ @logger.debug("Saving the Deployment Package: #{package_name}")
252
+ # Save deployment package if enabled and doesn't exist already
253
+ package.save(@sccm) if package.id.nil?
254
+
255
+ # Download updates if enabled
256
+ @logger.debug("Downloading the Deployment Package")
257
+ package.download_updates(@sccm) if settings[:sccm][:download_updates]
258
+ end
259
+ end
260
+ end
261
+
262
+ # Generate report if configured and content exists
263
+ if settings[:sccm][:generate_unknown_device_report]
264
+ report_length = csv_report_contents.length
265
+ if report_length > 0
266
+ @logger.debug("Creating Unknown Device report")
267
+ CSV.open(report_location + report, 'ab') do |csv|
268
+ csv << ['IP Address','Hostname']
269
+ csv_report_contents.each do |entry|
270
+ csv << entry
271
+ end
272
+ end
273
+ end
274
+ @logger.debug("Unknown Device report length: #{report_length}")
275
+ end
276
+
277
+ @logger.info("Nexpose SCCM integration has finished.")
278
+ end
279
+
280
+ def prompt(*args)
281
+ print(*args)
282
+ gets
283
+ end
284
+
285
+ attr_accessor :logger
286
+ def logger
287
+ if @logger.nil?
288
+ puts "Someone forgot to set their logger...exiting"
289
+ exit 1
290
+ end
291
+ @logger
292
+ end
293
+ end
294
+ end