nexpose 0.0.98 → 0.1.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.
- data/README.markdown +17 -7
- data/Rakefile +17 -22
- data/lib/README.md +5 -0
- data/lib/nexpose.rb +104 -130
- data/lib/nexpose/api_request.rb +133 -144
- data/lib/nexpose/common.rb +138 -0
- data/lib/nexpose/connection.rb +117 -106
- data/lib/nexpose/creds.rb +292 -279
- data/lib/nexpose/error.rb +21 -21
- data/lib/nexpose/manage.rb +83 -0
- data/lib/nexpose/misc.rb +85 -122
- data/lib/nexpose/report.rb +783 -603
- data/lib/nexpose/role.rb +27 -0
- data/lib/nexpose/scan.rb +264 -285
- data/lib/nexpose/scan_engine.rb +344 -350
- data/lib/nexpose/silo.rb +348 -347
- data/lib/nexpose/site.rb +826 -898
- data/lib/nexpose/ticket.rb +108 -108
- data/lib/nexpose/user.rb +223 -221
- data/lib/nexpose/util.rb +36 -36
- data/lib/nexpose/vuln.rb +510 -520
- metadata +37 -23
- data/README +0 -0
- data/nexpose.gemspec +0 -20
data/lib/nexpose/error.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
module Nexpose
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
1
|
+
module Nexpose
|
2
|
+
class APIError < ::RuntimeError
|
3
|
+
attr_accessor :req, :reason
|
4
|
+
|
5
|
+
def initialize(req, reason = '')
|
6
|
+
@req = req
|
7
|
+
@reason = reason
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"NexposeAPI: #{@reason}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class AuthenticationFailed < APIError
|
16
|
+
def initialize(req)
|
17
|
+
@req = req
|
18
|
+
@reason = "Login Failed"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# General management and diagnostic functions.
|
2
|
+
module Nexpose
|
3
|
+
module NexposeAPI
|
4
|
+
include XMLUtils
|
5
|
+
|
6
|
+
# Execute an arbitrary console command that is supplied as text via the
|
7
|
+
# supplied parameter. Console commands are documented in the
|
8
|
+
# administrator's guide. If you use a command that is not listed in the
|
9
|
+
# administrator's guide, the application will return the XMLResponse.
|
10
|
+
def console_command(cmd_string)
|
11
|
+
xml = make_xml('ConsoleCommandRequest', {})
|
12
|
+
cmd = REXML::Element.new('Command')
|
13
|
+
cmd.text = cmd_string
|
14
|
+
xml << cmd
|
15
|
+
|
16
|
+
r = execute(xml)
|
17
|
+
if (r.success)
|
18
|
+
res = ''
|
19
|
+
r.res.elements.each('//Output') do |out|
|
20
|
+
out.text.to_s
|
21
|
+
end
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Obtain system data, such as total RAM, free RAM, total disk space,
|
28
|
+
# free disk space, CPU speed, number of CPU cores, and other vital
|
29
|
+
# information.
|
30
|
+
def system_information
|
31
|
+
r = execute(make_xml('SystemInformationRequest', {}))
|
32
|
+
|
33
|
+
if (r.success)
|
34
|
+
res = {}
|
35
|
+
r.res.elements.each("//Statistic") do |stat|
|
36
|
+
res[stat.attributes['name'].to_s] = stat.text.to_s
|
37
|
+
end
|
38
|
+
res
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Induce the application to retrieve required updates and restart
|
45
|
+
# if necessary.
|
46
|
+
def start_update
|
47
|
+
execute(make_xml('StartUpdateRequest', {})).success
|
48
|
+
end
|
49
|
+
|
50
|
+
# Restart the application.
|
51
|
+
#
|
52
|
+
# There is no response to a RestartRequest. When the application
|
53
|
+
# shuts down as part of the restart process, it terminates any active
|
54
|
+
# connections. Therefore, the application cannot issue a response when it
|
55
|
+
# restarts.
|
56
|
+
def restart
|
57
|
+
execute(make_xml('RestartRequest', {})).success
|
58
|
+
end
|
59
|
+
|
60
|
+
# --
|
61
|
+
# TODO This is not yet implemented correctly.
|
62
|
+
#
|
63
|
+
# Output diagnostic information into log files, zip the files, and encrypt
|
64
|
+
# the archive with a PGP public key that is provided as a parameter for the
|
65
|
+
# API call. Then, either e-mail this archive to an address that is
|
66
|
+
# specified as an API parameter, or upload the archive using HTTP or HTTPS
|
67
|
+
# to a URL that is specified as an API parameter.
|
68
|
+
#
|
69
|
+
# If you do not specify a key, the SendLogRequest uses a default key.
|
70
|
+
#
|
71
|
+
# @param protocol should be one of: smtp, http, https.
|
72
|
+
# ++
|
73
|
+
def send_log(key_id, protocol, transport)
|
74
|
+
xml = make_xml('ConsoleCommandRequest', {'keyid' => key_id})
|
75
|
+
tpt = REXML::Element.new('Transport')
|
76
|
+
tpt.add_attribute('protocol', protocol)
|
77
|
+
tpt.text = transport
|
78
|
+
xml << tpt
|
79
|
+
|
80
|
+
# execute(xml)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/nexpose/misc.rb
CHANGED
@@ -1,122 +1,85 @@
|
|
1
|
-
module Nexpose
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def console_command(cmd_string)
|
87
|
-
xml = make_xml('ConsoleCommandRequest', {})
|
88
|
-
cmd = REXML::Element.new('Command')
|
89
|
-
cmd.text = cmd_string
|
90
|
-
xml << cmd
|
91
|
-
|
92
|
-
r = execute(xml)
|
93
|
-
|
94
|
-
if (r.success)
|
95
|
-
res = ""
|
96
|
-
r.res.elements.each("//Output") do |out|
|
97
|
-
res << out.text.to_s
|
98
|
-
end
|
99
|
-
|
100
|
-
res
|
101
|
-
else
|
102
|
-
false
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def system_information
|
107
|
-
r = execute(make_xml('SystemInformationRequest', {}))
|
108
|
-
|
109
|
-
if (r.success)
|
110
|
-
res = {}
|
111
|
-
r.res.elements.each("//Statistic") do |stat|
|
112
|
-
res[stat.attributes['name'].to_s] = stat.text.to_s
|
113
|
-
end
|
114
|
-
|
115
|
-
res
|
116
|
-
else
|
117
|
-
false
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
end
|
122
|
-
end
|
1
|
+
module Nexpose
|
2
|
+
module NexposeAPI
|
3
|
+
include XMLUtils
|
4
|
+
|
5
|
+
def device_delete(param)
|
6
|
+
r = execute(make_xml('DeviceDeleteRequest', {'device-id' => param}))
|
7
|
+
r.success
|
8
|
+
end
|
9
|
+
|
10
|
+
def asset_group_delete(connection, id, debug = false)
|
11
|
+
r = execute(make_xml('AssetGroupDeleteRequest', {'group-id' => param}))
|
12
|
+
r.success
|
13
|
+
end
|
14
|
+
|
15
|
+
#-------------------------------------------------------------------------
|
16
|
+
# Returns all asset group information
|
17
|
+
#-------------------------------------------------------------------------
|
18
|
+
def asset_groups_listing()
|
19
|
+
r = execute(make_xml('AssetGroupListingRequest'))
|
20
|
+
|
21
|
+
if r.success
|
22
|
+
res = []
|
23
|
+
r.res.elements.each('//AssetGroupSummary') do |group|
|
24
|
+
res << {
|
25
|
+
:asset_group_id => group.attributes['id'].to_i,
|
26
|
+
:name => group.attributes['name'].to_s,
|
27
|
+
:description => group.attributes['description'].to_s,
|
28
|
+
:risk_score => group.attributes['riskscore'].to_f,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
res
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#-------------------------------------------------------------------------
|
38
|
+
# Returns an asset group configuration information for a specific group ID
|
39
|
+
#-------------------------------------------------------------------------
|
40
|
+
def asset_group_config(group_id)
|
41
|
+
r = execute(make_xml('AssetGroupConfigRequest', {'group-id' => group_id}))
|
42
|
+
|
43
|
+
if r.success
|
44
|
+
res = []
|
45
|
+
r.res.elements.each('//Devices/device') do |device_info|
|
46
|
+
res << {
|
47
|
+
:device_id => device_info.attributes['id'].to_i,
|
48
|
+
:site_id => device_info.attributes['site-id'].to_i,
|
49
|
+
:address => device_info.attributes['address'].to_s,
|
50
|
+
:riskfactor => device_info.attributes['riskfactor'].to_f,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
res
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Lists all the users for the NSC along with the user details.
|
61
|
+
#
|
62
|
+
def list_users
|
63
|
+
r = execute(make_xml('UserListingRequest'))
|
64
|
+
if r.success
|
65
|
+
res = []
|
66
|
+
r.res.elements.each('//UserSummary') do |user_summary|
|
67
|
+
res << {
|
68
|
+
:auth_source => user_summary.attributes['authSource'],
|
69
|
+
:auth_module => user_summary.attributes['authModule'],
|
70
|
+
:user_name => user_summary.attributes['userName'],
|
71
|
+
:full_name => user_summary.attributes['fullName'],
|
72
|
+
:email => user_summary.attributes['email'],
|
73
|
+
:is_admin => user_summary.attributes['isAdmin'].to_s.chomp.eql?('1'),
|
74
|
+
:is_disabled => user_summary.attributes['disabled'].to_s.chomp.eql?('1'),
|
75
|
+
:site_count => user_summary.attributes['siteCount'],
|
76
|
+
:group_count => user_summary.attributes['groupCount']
|
77
|
+
}
|
78
|
+
end
|
79
|
+
res
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/nexpose/report.rb
CHANGED
@@ -1,603 +1,783 @@
|
|
1
|
-
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
@
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
end
|
1
|
+
module Nexpose
|
2
|
+
module NexposeAPI
|
3
|
+
include XMLUtils
|
4
|
+
|
5
|
+
# Generate a new report using the specified report definition.
|
6
|
+
def generate_report(report_id, wait = false)
|
7
|
+
xml = make_xml('ReportGenerateRequest', {'report-id' => report_id})
|
8
|
+
response = execute(xml)
|
9
|
+
summary = nil
|
10
|
+
if response.success
|
11
|
+
response.res.elements.each('//ReportSummary') do |summary|
|
12
|
+
summary = ReportSummary.parse(summary)
|
13
|
+
# If not waiting or the report is finished, return now.
|
14
|
+
return summary unless wait and summary.status == 'Started'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
so_far = 0
|
18
|
+
while wait
|
19
|
+
summary = last_report(report_id)
|
20
|
+
return summary unless summary.status == 'Started'
|
21
|
+
sleep 5
|
22
|
+
so_far += 5
|
23
|
+
if so_far % 60 == 0
|
24
|
+
puts "Still waiting. Current status: #{summary.status}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Provide a history of all reports generated with the specified report
|
31
|
+
# definition.
|
32
|
+
def report_history(report_config_id)
|
33
|
+
xml = make_xml('ReportHistoryRequest', {'reportcfg-id' => report_config_id})
|
34
|
+
ReportSummary.parse_all(execute(xml))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the details of the last report generated with the specified report id.
|
38
|
+
def last_report(report_config_id)
|
39
|
+
history = report_history(report_config_id)
|
40
|
+
history.sort { |a, b| b.generated_on <=> a.generated_on }.first
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delete a previously generated report definition.
|
44
|
+
# Also deletes any reports generated from that configuration.
|
45
|
+
def delete_report_config(report_config_id)
|
46
|
+
xml = make_xml('ReportDeleteRequest', {'reportcfg-id' => report_config_id})
|
47
|
+
execute(xml).success
|
48
|
+
end
|
49
|
+
|
50
|
+
# Delete a previously generated report.
|
51
|
+
def delete_report(report_id)
|
52
|
+
xml = make_xml('ReportDeleteRequest', {'report-id' => report_id})
|
53
|
+
execute(xml).success
|
54
|
+
end
|
55
|
+
|
56
|
+
# Provide a list of all report templates the user can access on the
|
57
|
+
# Security Console.
|
58
|
+
def report_template_listing
|
59
|
+
r = execute(make_xml('ReportTemplateListingRequest', {}))
|
60
|
+
templates = []
|
61
|
+
if (r.success)
|
62
|
+
r.res.elements.each('//ReportTemplateSummary') do |template|
|
63
|
+
templates << ReportTemplateSummary.parse(template)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
templates
|
67
|
+
end
|
68
|
+
|
69
|
+
# Retrieve the configuration for a report template.
|
70
|
+
def get_report_template(template_id)
|
71
|
+
xml = make_xml('ReportTemplateConfigRequest', {'template-id' => template_id})
|
72
|
+
ReportTemplate.parse(execute(xml))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Provide a listing of all report definitions the user can access on the
|
76
|
+
# Security Console.
|
77
|
+
def report_listing
|
78
|
+
r = execute(make_xml('ReportListingRequest', {}))
|
79
|
+
reports = []
|
80
|
+
if (r.success)
|
81
|
+
r.res.elements.each('//ReportConfigSummary') do |report|
|
82
|
+
reports << ReportConfigSummary.parse(report)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
reports
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieve the configuration for a report definition.
|
89
|
+
def get_report_config(report_config_id)
|
90
|
+
xml = make_xml('ReportConfigRequest', {'reportcfg-id' => report_config_id})
|
91
|
+
ReportConfig.parse(execute(xml))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Data object for report configuration information.
|
96
|
+
# Not meant for use in creating new configurations.
|
97
|
+
class ReportConfigSummary
|
98
|
+
# The report definition (config) ID.
|
99
|
+
attr_reader :config_id
|
100
|
+
# The ID of the report template.
|
101
|
+
attr_reader :template_id
|
102
|
+
# The current status of the report.
|
103
|
+
# One of: Started|Generated|Failed|Aborted|Unknown
|
104
|
+
attr_reader :status
|
105
|
+
# The date and time the report was generated, in ISO 8601 format.
|
106
|
+
attr_reader :generated_on
|
107
|
+
# The URL to use to access the report (not set for database exports).
|
108
|
+
attr_reader :uri
|
109
|
+
# The visibility (scope) of the report definition.
|
110
|
+
# One of: (global|silo).
|
111
|
+
attr_reader :scope
|
112
|
+
|
113
|
+
def initialize(config_id, template_id, status, generated_on, uri, scope)
|
114
|
+
@config_id = config_id
|
115
|
+
@template_id = template_id
|
116
|
+
@status = status
|
117
|
+
@generated_on = generated_on
|
118
|
+
@uri = uri
|
119
|
+
@scope = scope
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.parse(xml)
|
123
|
+
ReportConfigSummary.new(xml.attributes['cfg-id'],
|
124
|
+
xml.attributes['template-id'],
|
125
|
+
xml.attributes['status'],
|
126
|
+
xml.attributes['generated-on'],
|
127
|
+
xml.attributes['report-URI'],
|
128
|
+
xml.attributes['scope'])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Summary of a single report.
|
133
|
+
class ReportSummary
|
134
|
+
# The id of the generated report.
|
135
|
+
attr_reader :id
|
136
|
+
# The report definition (configuration) ID.
|
137
|
+
attr_reader :config_id
|
138
|
+
# The current status of the report.
|
139
|
+
# One of: Started|Generated|Failed|Aborted|Unknown
|
140
|
+
attr_reader :status
|
141
|
+
# The date and time the report was generated, in ISO 8601 format.
|
142
|
+
attr_reader :generated_on
|
143
|
+
# The relative URI to use to access the report.
|
144
|
+
attr_reader :uri
|
145
|
+
|
146
|
+
def initialize(id, config_id, status, generated_on, uri)
|
147
|
+
@id = id
|
148
|
+
@config_id = config_id
|
149
|
+
@status = status
|
150
|
+
@generated_on = generated_on
|
151
|
+
@uri = uri
|
152
|
+
end
|
153
|
+
|
154
|
+
# Delete this report.
|
155
|
+
def delete(connection)
|
156
|
+
connection.delete_report(@id)
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.parse(xml)
|
160
|
+
ReportSummary.new(xml.attributes['id'], xml.attributes['cfg-id'], xml.attributes['status'], xml.attributes['generated-on'], xml.attributes['report-URI'])
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.parse_all(response)
|
164
|
+
summaries = []
|
165
|
+
if (response.success)
|
166
|
+
response.res.elements.each('//ReportSummary') do |summary|
|
167
|
+
summaries << ReportSummary.parse(summary)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
summaries
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Definition object for an adhoc report configuration.
|
175
|
+
#
|
176
|
+
# NOTE: Only text, pdf, and csv currently work reliably.
|
177
|
+
class AdhocReportConfig
|
178
|
+
# The ID of the report template used.
|
179
|
+
attr_accessor :template_id
|
180
|
+
# Format. One of: pdf|html|rtf|xml|text|csv|db|raw-xml|raw-xml-v2|ns-xml|qualys-xml
|
181
|
+
attr_accessor :format
|
182
|
+
|
183
|
+
# Array of filters associated with this report.
|
184
|
+
attr_accessor :filters
|
185
|
+
# Baseline comparison highlights the changes between two scans, including
|
186
|
+
# newly discovered assets, services and vulnerabilities, assets and services
|
187
|
+
# that are no longer available and vulnerabilities that were mitigated or
|
188
|
+
# fixed. The current scan results can be compared against the results of the
|
189
|
+
# first scan, the most recent (previous) scan, or the scan results from a
|
190
|
+
# particular date.
|
191
|
+
attr_accessor :baseline
|
192
|
+
|
193
|
+
def initialize(template_id, format, site_id = nil)
|
194
|
+
@template_id = template_id
|
195
|
+
@format = format
|
196
|
+
|
197
|
+
@filters = []
|
198
|
+
@filters << Filter.new('site', site_id) if site_id
|
199
|
+
end
|
200
|
+
|
201
|
+
# Add a new filter to this report configuration.
|
202
|
+
def add_filter(type, id)
|
203
|
+
filters << Filter.new(type, id)
|
204
|
+
end
|
205
|
+
|
206
|
+
def to_xml
|
207
|
+
xml = %Q{<AdhocReportConfig format='#{@format}' template-id='#{@template_id}'>}
|
208
|
+
|
209
|
+
xml << '<Filters>'
|
210
|
+
@filters.each { |filter| xml << filter.to_xml }
|
211
|
+
xml << '</Filters>'
|
212
|
+
|
213
|
+
xml << %Q{<Baseline compareTo='#{@baseline}' />} if @baseline
|
214
|
+
|
215
|
+
xml << '</AdhocReportConfig>'
|
216
|
+
end
|
217
|
+
|
218
|
+
include XMLUtils
|
219
|
+
|
220
|
+
# Generate a report once using a simple configuration, and send it back
|
221
|
+
# in a multi-part mime response.
|
222
|
+
def generate(connection)
|
223
|
+
xml = %Q{<ReportAdhocGenerateRequest session-id='#{connection.session_id}'>}
|
224
|
+
xml << to_xml
|
225
|
+
xml << '</ReportAdhocGenerateRequest>'
|
226
|
+
response = connection.execute(xml)
|
227
|
+
if response.success
|
228
|
+
content_type_response = response.raw_response.header['Content-Type']
|
229
|
+
if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
|
230
|
+
# Nexpose sends an incorrect boundary format which breaks parsing
|
231
|
+
# e.g., boundary=XXX; charset=XXX
|
232
|
+
# Fix by removing everything from the last semi-colon onward.
|
233
|
+
last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
|
234
|
+
content_type_response = content_type_response[0, last_semi_colon_index]
|
235
|
+
|
236
|
+
data = 'Content-Type: ' + content_type_response + "\r\n\r\n" + response.raw_response_data
|
237
|
+
doc = Rex::MIME::Message.new(data)
|
238
|
+
doc.parts.each do |part|
|
239
|
+
if /.*base64.*/ =~ part.header.to_s
|
240
|
+
if (@format == 'text') or (@format == 'pdf') or (@format == 'csv')
|
241
|
+
return part.content.unpack('m*')[0]
|
242
|
+
else
|
243
|
+
# FIXME This isn't working.
|
244
|
+
return parse_xml(part.content.unpack("m*")[0])
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Definition object for a report configuration.
|
254
|
+
class ReportConfig < AdhocReportConfig
|
255
|
+
# The ID of the report definition (config).
|
256
|
+
# Use -1 to create a new definition.
|
257
|
+
attr_accessor :id
|
258
|
+
# The unique name assigned to the report definition.
|
259
|
+
attr_accessor :name
|
260
|
+
attr_accessor :owner
|
261
|
+
attr_accessor :time_zone
|
262
|
+
|
263
|
+
# Description associated with this report.
|
264
|
+
attr_accessor :description
|
265
|
+
# Array of user IDs which have access to resulting reports.
|
266
|
+
attr_accessor :users
|
267
|
+
# Configuration of when a report is generated.
|
268
|
+
attr_accessor :generate
|
269
|
+
# Report delivery configuration.
|
270
|
+
attr_accessor :delivery
|
271
|
+
# Database export configuration.
|
272
|
+
attr_accessor :db_export
|
273
|
+
|
274
|
+
# Construct a basic ReportConfig object.
|
275
|
+
def initialize(name, template_id, format, id = -1, owner = nil, time_zone = nil)
|
276
|
+
@name = name
|
277
|
+
@template_id = template_id
|
278
|
+
@format = format
|
279
|
+
@id = id
|
280
|
+
@owner = owner
|
281
|
+
@time_zone = time_zone
|
282
|
+
|
283
|
+
@filters = []
|
284
|
+
@users = []
|
285
|
+
end
|
286
|
+
|
287
|
+
# Retrieve the configuration for an existing report definition.
|
288
|
+
def self.get(connection, report_config_id)
|
289
|
+
connection.get_report_config(report_config_id)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Build and save a report configuration against the specified site using
|
293
|
+
# the supplied type and format.
|
294
|
+
#
|
295
|
+
# Returns the new configuration.
|
296
|
+
def self.build(connection, site_id, site_name, type, format, generate_now = false)
|
297
|
+
name = %Q{#{site_name} #{type} report in #{format}}
|
298
|
+
config = ReportConfig.new(name, type, format)
|
299
|
+
config.generate = Generate.new(true, false)
|
300
|
+
config.filters << Filter.new('site', site_id)
|
301
|
+
config.save(connection, generate_now)
|
302
|
+
config
|
303
|
+
end
|
304
|
+
|
305
|
+
# Save the configuration of this report definition.
|
306
|
+
def save(connection, generate_now = false)
|
307
|
+
xml = %Q{<ReportSaveRequest session-id='#{connection.session_id}' generate-now='#{generate_now ? 1 : 0}'>}
|
308
|
+
xml << to_xml
|
309
|
+
xml << '</ReportSaveRequest>'
|
310
|
+
response = connection.execute(xml)
|
311
|
+
if response.success
|
312
|
+
@id = response.attributes['reportcfg-id']
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Generate a new report using this report definition.
|
317
|
+
def generate(connection, wait = false)
|
318
|
+
connection.generate_report(@id, wait)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Delete this report definition from the Security Console.
|
322
|
+
# Deletion will also remove all reports previously generated from the
|
323
|
+
# configuration.
|
324
|
+
def delete(connection)
|
325
|
+
connection.delete_report_config(@id)
|
326
|
+
end
|
327
|
+
|
328
|
+
def to_xml
|
329
|
+
xml = %Q{<ReportConfig format='#{@format}' id='#{@id}' name='#{@name}' owner='#{@owner}' template-id='#{@template_id}' timezone='#{@time_zone}'>}
|
330
|
+
xml << %Q{<description>#{@description}</description>} if @description
|
331
|
+
|
332
|
+
xml << '<Filters>'
|
333
|
+
@filters.each { |filter| xml << filter.to_xml }
|
334
|
+
xml << '</Filters>'
|
335
|
+
|
336
|
+
xml << '<Users>'
|
337
|
+
@users.each { |user| xml << %Q{<user id='#{user}' />} }
|
338
|
+
xml << '</Users>'
|
339
|
+
|
340
|
+
xml << %Q{<Baseline compareTo='#{@baseline}' />} if @baseline
|
341
|
+
xml << @generate.to_xml if @generate
|
342
|
+
xml << @delivery.to_xml if @delivery
|
343
|
+
xml << @db_export.to_xml if @db_export
|
344
|
+
|
345
|
+
xml << '</ReportConfig>'
|
346
|
+
end
|
347
|
+
|
348
|
+
def self.parse(xml)
|
349
|
+
xml.res.elements.each('//ReportConfig') do |cfg|
|
350
|
+
config = ReportConfig.new(cfg.attributes['name'],
|
351
|
+
cfg.attributes['template-id'],
|
352
|
+
cfg.attributes['format'],
|
353
|
+
cfg.attributes['id'],
|
354
|
+
cfg.attributes['owner'],
|
355
|
+
cfg.attributes['timezone'])
|
356
|
+
|
357
|
+
cfg.elements.each('//description') do |desc|
|
358
|
+
config.description = desc.text
|
359
|
+
end
|
360
|
+
|
361
|
+
config.filters = Filter.parse(xml)
|
362
|
+
|
363
|
+
cfg.elements.each('//user') do |user|
|
364
|
+
config.users << user.attributes['id'].to_i
|
365
|
+
end
|
366
|
+
|
367
|
+
cfg.elements.each('//Baseline') do |baseline|
|
368
|
+
config.baseline = baseline.attributes['compareTo']
|
369
|
+
end
|
370
|
+
|
371
|
+
config.generate = Generate.parse(cfg)
|
372
|
+
config.delivery = Delivery.parse(cfg)
|
373
|
+
config.db_export = DBExport.parse(cfg)
|
374
|
+
|
375
|
+
return config
|
376
|
+
end
|
377
|
+
nil
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Object that represents a report filter which determines which sites, asset
|
382
|
+
# groups, and/or devices that a report is run against.
|
383
|
+
#
|
384
|
+
# The configuration must include at least one of device (asset), site,
|
385
|
+
# group (asset group) or scan filter to define the scope of report.
|
386
|
+
# The vuln-status filter can be used only with raw report formats: csv
|
387
|
+
# or raw_xml. If the vuln-status filter is not included in the configuration,
|
388
|
+
# all the vulnerability test results (including invulnerable instances) are
|
389
|
+
# exported by default in csv and raw_xml reports.
|
390
|
+
class Filter
|
391
|
+
# The ID of the specific site, group, device, or scan.
|
392
|
+
# For scan, this can also be "last" for the most recently run scan.
|
393
|
+
# For vuln-status, the ID can have one of the following values:
|
394
|
+
# 1. vulnerable-exploited (The check was positive. An exploit verified the vulnerability.)
|
395
|
+
# 2. vulnerable-version (The check was positive. The version of the scanned service or software is associated with known vulnerabilities.)
|
396
|
+
# 3. potential (The check for a potential vulnerability was positive.)
|
397
|
+
# These values are supported for CSV and XML formats.
|
398
|
+
attr_reader :id
|
399
|
+
# One of: site|group|device|scan|vuln-categories|vuln-severity|vuln-status|cyberscope-component|cyberscope-bureau|cyberscope-enclave
|
400
|
+
attr_reader :type
|
401
|
+
|
402
|
+
def initialize(type, id)
|
403
|
+
@type = type
|
404
|
+
@id = id
|
405
|
+
end
|
406
|
+
|
407
|
+
def to_xml
|
408
|
+
%Q{<filter id='#{@id}' type='#{@type}' />}
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.parse(xml)
|
412
|
+
filters = []
|
413
|
+
xml.res.elements.each('//Filters/filter') do |filter|
|
414
|
+
filters << Filter.new(filter.attributes['type'], filter.attributes['id'])
|
415
|
+
end
|
416
|
+
filters
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Data object associated with when a report is generated.
|
421
|
+
class Generate
|
422
|
+
# Will the report be generated after a scan completes (true),
|
423
|
+
# or is it ad-hoc/scheduled (false).
|
424
|
+
attr_accessor :after_scan
|
425
|
+
# Whether or not a scan is scheduled.
|
426
|
+
attr_accessor :scheduled
|
427
|
+
# Schedule associated with the report.
|
428
|
+
attr_accessor :schedule
|
429
|
+
|
430
|
+
def initialize(after_scan, scheduled, schedule = nil)
|
431
|
+
@after_scan = after_scan
|
432
|
+
@scheduled = scheduled
|
433
|
+
@schedule = schedule
|
434
|
+
end
|
435
|
+
|
436
|
+
def to_xml
|
437
|
+
xml = %Q{<Generate after-scan='#{@after_scan ? 1 : 0}' schedule='#{@scheduled ? 1 : 0}'>}
|
438
|
+
xml << @schedule.to_xml if @schedule
|
439
|
+
xml << '</Generate>'
|
440
|
+
end
|
441
|
+
|
442
|
+
def self.parse(xml)
|
443
|
+
xml.elements.each('//Generate') do |generate|
|
444
|
+
if generate.attributes['after-scan'] == '1'
|
445
|
+
return Generate.new(true, false)
|
446
|
+
else
|
447
|
+
if generate.attributes['schedule'] == '1'
|
448
|
+
schedule = Schedule.parse(xml)
|
449
|
+
return Generate.new(false, true, schedule)
|
450
|
+
end
|
451
|
+
return Generate.new(false, false)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
nil
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# Data object for configuration of where a report is stored or delivered.
|
459
|
+
class Delivery
|
460
|
+
# Whether to store report on server.
|
461
|
+
attr_accessor :store_on_server
|
462
|
+
# Directory location to store report in (for non-default storage).
|
463
|
+
attr_accessor :location
|
464
|
+
# E-mail configuration.
|
465
|
+
attr_accessor :email
|
466
|
+
|
467
|
+
def initialize(store_on_server, location = nil, email = nil)
|
468
|
+
@store_on_server = store_on_server
|
469
|
+
@location = location
|
470
|
+
@email = email
|
471
|
+
end
|
472
|
+
|
473
|
+
def to_xml
|
474
|
+
xml = '<Delivery>'
|
475
|
+
xml << %Q{<Storage storeOnServer='#{@store_on_server ? 1 : 0}'>}
|
476
|
+
xml << %Q{<location>#{@location}</location>} if @location
|
477
|
+
xml << '</Storage>'
|
478
|
+
xml << @email.to_xml if @email
|
479
|
+
xml << '</Delivery>'
|
480
|
+
end
|
481
|
+
|
482
|
+
def self.parse(xml)
|
483
|
+
xml.elements.each('//Delivery') do |delivery|
|
484
|
+
on_server = false
|
485
|
+
location = nil
|
486
|
+
xml.elements.each('//Storage') do |storage|
|
487
|
+
on_server = true if storage.attributes['storeOnServer'] == '1'
|
488
|
+
xml.elements.each('//location') do |loc|
|
489
|
+
location = loc.text
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
email = Email.parse(xml)
|
494
|
+
|
495
|
+
return Delivery.new(on_server, location, email)
|
496
|
+
end
|
497
|
+
nil
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# Configuration structure for database exporting of reports.
|
502
|
+
class DBExport
|
503
|
+
# The DB type to export to.
|
504
|
+
attr_accessor :type
|
505
|
+
# Credentials needed to export to the specified database.
|
506
|
+
attr_accessor :credentials
|
507
|
+
# Map of parameters for this DB export configuration.
|
508
|
+
attr_accessor :parameters
|
509
|
+
|
510
|
+
def initialize(type)
|
511
|
+
@type = type
|
512
|
+
@parameters = {}
|
513
|
+
end
|
514
|
+
|
515
|
+
def to_xml
|
516
|
+
xml = %Q{<DBExport type='#{@type}'>}
|
517
|
+
xml << @credentials.to_xml if @credentials
|
518
|
+
@parameters.each_pair do |name, value|
|
519
|
+
xml << %Q{<param name='#{name}'>#{value}</param>}
|
520
|
+
end
|
521
|
+
xml << '</DBExport>'
|
522
|
+
end
|
523
|
+
|
524
|
+
def self.parse(xml)
|
525
|
+
xml.elements.each('//DBExport') do |dbexport|
|
526
|
+
config = DBExport.new(dbexport.attributes['type'])
|
527
|
+
config.credentials = ExportCredential.parse(xml)
|
528
|
+
xml.elements.each('//param') do |param|
|
529
|
+
config.parameters[param.attributes['name']] = param.text
|
530
|
+
end
|
531
|
+
return config
|
532
|
+
end
|
533
|
+
nil
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
# DBExport credentials configuration object.
|
538
|
+
#
|
539
|
+
# The user_id, password and realm attributes should ONLY be used
|
540
|
+
# if a security blob cannot be generated and the data is being
|
541
|
+
# transmitted/stored using external encryption (e.g., HTTPS).
|
542
|
+
class ExportCredential
|
543
|
+
# Security blob for exporting to a database.
|
544
|
+
attr_accessor :credential
|
545
|
+
attr_accessor :user_id
|
546
|
+
attr_accessor :password
|
547
|
+
# DB specific, usually the database name.
|
548
|
+
attr_accessor :realm
|
549
|
+
|
550
|
+
def initialize(credential)
|
551
|
+
@credential = credential
|
552
|
+
end
|
553
|
+
|
554
|
+
def to_xml
|
555
|
+
xml = '<credentials'
|
556
|
+
xml << %Q{ userid='#{@user_id}'} if @user_id
|
557
|
+
xml << %Q{ password='#{@password}'} if @password
|
558
|
+
xml << %Q{ realm='#{@realm}'} if @realm
|
559
|
+
xml << '>'
|
560
|
+
xml << @credential if @credential
|
561
|
+
xml << '</credentials>'
|
562
|
+
end
|
563
|
+
|
564
|
+
def self.parse(xml)
|
565
|
+
xml.elements.each('//credentials') do |creds|
|
566
|
+
credential = ExportCredential.new(creds.text)
|
567
|
+
# The following attributes may not exist.
|
568
|
+
credential.user_id = creds.attributes['userid']
|
569
|
+
credential.password = creds.attributes['password']
|
570
|
+
credential.realm = creds.attributes['realm']
|
571
|
+
return credential
|
572
|
+
end
|
573
|
+
nil
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
# Data object for report template summary information.
|
578
|
+
# Not meant for use in creating new templates.
|
579
|
+
class ReportTemplateSummary
|
580
|
+
# The ID of the report template.
|
581
|
+
attr_reader :id
|
582
|
+
# The name of the report template.
|
583
|
+
attr_reader :name
|
584
|
+
# One of: data|document. With a data template, you can export
|
585
|
+
# comma-separated value (CSV) files with vulnerability-based data.
|
586
|
+
# With a document template, you can create PDF, RTF, HTML, or XML reports
|
587
|
+
# with asset-based information.
|
588
|
+
attr_reader :type
|
589
|
+
# The visibility (scope) of the report template. One of: global|silo
|
590
|
+
attr_reader :scope
|
591
|
+
# Whether the report template is built-in, and therefore cannot be modified.
|
592
|
+
attr_reader :built_in
|
593
|
+
# Description of the report template.
|
594
|
+
attr_reader :description
|
595
|
+
|
596
|
+
def initialize(id, name, type, scope, built_in, description)
|
597
|
+
@id = id
|
598
|
+
@name = name
|
599
|
+
@type = type
|
600
|
+
@scope = scope
|
601
|
+
@built_in = built_in
|
602
|
+
@description = description
|
603
|
+
end
|
604
|
+
|
605
|
+
def self.parse(xml)
|
606
|
+
description = nil
|
607
|
+
xml.elements.each('description') { |desc| description = desc.text }
|
608
|
+
ReportTemplateSummary.new(xml.attributes['id'],
|
609
|
+
xml.attributes['name'],
|
610
|
+
xml.attributes['type'],
|
611
|
+
xml.attributes['scope'],
|
612
|
+
xml.attributes['builtin'] == '1',
|
613
|
+
description)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
# Definition object for a report template.
|
618
|
+
class ReportTemplate
|
619
|
+
# The ID of the report template.
|
620
|
+
attr_accessor :id
|
621
|
+
# The name of the report template.
|
622
|
+
attr_accessor :name
|
623
|
+
# With a data template, you can export comma-separated value (CSV) files
|
624
|
+
# with vulnerability-based data. With a document template, you can create
|
625
|
+
# PDF, RTF, HTML, or XML reports with asset-based information. When you
|
626
|
+
# retrieve a report template, the type will always be visible even though
|
627
|
+
# type is implied. When ReportTemplate is sent as a request, and the type
|
628
|
+
# attribute is not provided, the type attribute defaults to document,
|
629
|
+
# allowing for backward compatibility with existing API clients.
|
630
|
+
attr_accessor :type
|
631
|
+
# The visibility (scope) of the report template.
|
632
|
+
# One of: global|silo
|
633
|
+
attr_accessor :scope
|
634
|
+
# The report template is built-in, and cannot be modified.
|
635
|
+
attr_accessor :built_in
|
636
|
+
# Description of this report template.
|
637
|
+
attr_accessor :description
|
638
|
+
|
639
|
+
# Array of report sections.
|
640
|
+
attr_accessor :sections
|
641
|
+
# Map of report properties.
|
642
|
+
attr_accessor :properties
|
643
|
+
# Array of report attributes, in the order they will be present in a report.
|
644
|
+
attr_accessor :attributes
|
645
|
+
# Display asset names with IPs.
|
646
|
+
attr_accessor :show_device_names
|
647
|
+
|
648
|
+
def initialize(name, type = 'document', id = -1, scope = 'silo', built_in = false)
|
649
|
+
@name = name
|
650
|
+
@type = type
|
651
|
+
@id = id
|
652
|
+
@scope = scope
|
653
|
+
@built_in = built_in
|
654
|
+
|
655
|
+
@sections = []
|
656
|
+
@properties = {}
|
657
|
+
@attributes = []
|
658
|
+
@show_device_names = false
|
659
|
+
end
|
660
|
+
|
661
|
+
# Save the configuration for a report template.
|
662
|
+
def save(connection)
|
663
|
+
xml = %Q{<ReportTemplateSaveRequest session-id='#{connection.session_id}' scope='#{@scope}'>}
|
664
|
+
xml << to_xml
|
665
|
+
xml << '</ReportTemplateSaveRequest>'
|
666
|
+
response = connection.execute(xml)
|
667
|
+
if response.success
|
668
|
+
@id = response.attributes['template-id']
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
def delete(connection)
|
673
|
+
xml = %Q{<ReportTemplateDeleteRequest session-id='#{connection.session_id}' template-id='#{@id}'>}
|
674
|
+
xml << '</ReportTemplateDeleteRequest>'
|
675
|
+
response = connection.execute(xml)
|
676
|
+
if response.success
|
677
|
+
@id = response.attributes['template-id']
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
# Retrieve the configuration for a report template.
|
682
|
+
def self.get(connection, template_id)
|
683
|
+
connection.get_report_template(template_id)
|
684
|
+
end
|
685
|
+
|
686
|
+
include Sanitize
|
687
|
+
|
688
|
+
def to_xml
|
689
|
+
xml = %Q{<ReportTemplate id='#{@id}' name='#{@name}' type='#{@type}'}
|
690
|
+
xml << %Q{ scope='#{@scope}'} if @scope
|
691
|
+
xml << %Q{ builtin='#{@built_in}'} if @built_in
|
692
|
+
xml << '>'
|
693
|
+
xml << %Q{<description>#{@description}</description>} if @description
|
694
|
+
|
695
|
+
unless @attributes.empty?
|
696
|
+
xml << '<ReportAttributes>'
|
697
|
+
@attributes.each do |attr|
|
698
|
+
xml << %Q(<ReportAttribute name='#{attr}'/>)
|
699
|
+
end
|
700
|
+
xml << '</ReportAttributes>'
|
701
|
+
end
|
702
|
+
|
703
|
+
unless @sections.empty?
|
704
|
+
xml << '<ReportSections>'
|
705
|
+
properties.each_pair do |name, value|
|
706
|
+
xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
|
707
|
+
end
|
708
|
+
@sections.each { |section| xml << section.to_xml }
|
709
|
+
xml << '</ReportSections>'
|
710
|
+
end
|
711
|
+
|
712
|
+
xml << %Q{<Settings><showDeviceNames enabled='#{@show_device_names ? 1 : 0}' /></Settings>}
|
713
|
+
xml << '</ReportTemplate>'
|
714
|
+
end
|
715
|
+
|
716
|
+
def self.parse(xml)
|
717
|
+
xml.res.elements.each('//ReportTemplate') do |tmp|
|
718
|
+
template = ReportTemplate.new(tmp.attributes['name'],
|
719
|
+
tmp.attributes['type'],
|
720
|
+
tmp.attributes['id'],
|
721
|
+
tmp.attributes['scope'] || 'silo',
|
722
|
+
tmp.attributes['builtin'])
|
723
|
+
tmp.elements.each('//description') do |desc|
|
724
|
+
template.description = desc.text
|
725
|
+
end
|
726
|
+
|
727
|
+
tmp.elements.each('//ReportAttributes/ReportAttribute') do |attr|
|
728
|
+
template.attributes << attr.attributes['name']
|
729
|
+
end
|
730
|
+
|
731
|
+
tmp.elements.each('//ReportSections/property') do |property|
|
732
|
+
template.properties[property.attributes['name']] = property.text
|
733
|
+
end
|
734
|
+
|
735
|
+
tmp.elements.each('//ReportSection') do |section|
|
736
|
+
template.sections << Section.parse(section)
|
737
|
+
end
|
738
|
+
|
739
|
+
tmp.elements.each('//showDeviceNames') do |show|
|
740
|
+
template.show_device_names = show.attributes['enabled'] == '1'
|
741
|
+
end
|
742
|
+
|
743
|
+
return template
|
744
|
+
end
|
745
|
+
nil
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
# Section specific content to include in a report template.
|
750
|
+
class Section
|
751
|
+
# Name of the report section.
|
752
|
+
attr_accessor :name
|
753
|
+
# Map of properties specific to the report section.
|
754
|
+
attr_accessor :properties
|
755
|
+
|
756
|
+
def initialize(name)
|
757
|
+
@name = name
|
758
|
+
@properties = {}
|
759
|
+
end
|
760
|
+
|
761
|
+
include Sanitize
|
762
|
+
|
763
|
+
def to_xml
|
764
|
+
xml = %Q{<ReportSection name='#{@name}'>}
|
765
|
+
properties.each_pair do |name, value|
|
766
|
+
xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
|
767
|
+
end
|
768
|
+
xml << '</ReportSection>'
|
769
|
+
end
|
770
|
+
|
771
|
+
def self.parse(xml)
|
772
|
+
name = xml.attributes['name']
|
773
|
+
xml.elements.each("//ReportSection[@name='#{name}']") do |elem|
|
774
|
+
section = Section.new(name)
|
775
|
+
elem.elements.each("//ReportSection[@name='#{name}']/property") do |property|
|
776
|
+
section.properties[property.attributes['name']] = property.text
|
777
|
+
end
|
778
|
+
return section
|
779
|
+
end
|
780
|
+
nil
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|