nexpose_sccm 0.4.0

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