jira_scan 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +8 -8
  2. data/bin/jira-scan +49 -1
  3. data/lib/jira_scan.rb +153 -4
  4. metadata +10 -10
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjNmYTk1OWYwM2VjNzJlZjVmZGFlZGIyNzdlYmUyOGE3Mzg0NTIxNg==
4
+ YzE1OTJiNTg5NzIxMzdhYjgwZGU1NzQzZDY2ZTJhOWYzNjA1NTgzZA==
5
5
  data.tar.gz: !binary |-
6
- NDA1MWUyMjE2ODIwODBhMThjYjU2ZWNlY2VhMjcxY2E0YWIyODYyZQ==
6
+ YzBkOGJlMzkwNzlkMjU5OWJhN2RmYTIwNzA2YTNlMzQ4OTI3NGE2Ng==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MjliM2EyYWE0MzFjNDllMWMyOTljMDYyOGRkYTU3ZDc2NTk2MDc0ZTg0ODJi
10
- Zjg3MTAwOGU5MjkzNmEzNmZkNGZkYWY4YTNhMDE3Mzg0YzEzNjVkOGUzMjMy
11
- YTI1MGQ0NDg1NzA4Y2YzNjE3ODM0MWQ2NTJiOTk1NDUzZDI1ZmU=
9
+ OTA2NjY0NjdiYzY0YjQzNGE0OTgzZDNkYjM4MzA0OGFhY2VkNGIzNmYzN2Y1
10
+ YjNmZGE2MjRhNzZhZDUzMTZmMjA3YjkwMDEyZThiYWRlYzRhOTA5MGEyMTRj
11
+ Y2ZkN2U0NWM0YjFlNDRhMDI5NzJmNTJiNGVhNGU3MGMxZDA4Yzc=
12
12
  data.tar.gz: !binary |-
13
- NDM2MTdiY2ViMzVlYmEwZTg5MTY4NGI0NWY0M2IwZmFjMGFjYmM5MzRhZmFi
14
- NmYxYTkzYWE4ODAzNDYwNzNjZTNmNGE1OGM1OGJjMzAyZDYwYWRkMzg0NWRl
15
- NzVmNTQ2NmY3NTNhZWQ3ODA0MWZhNDVjN2M0YzQ2OTQ5Y2M1MDg=
13
+ MTQxMzE2ODI2MzAxZDA5MjA3N2QxNzkyNGNjMWI1NGE0ZWQ5Y2EwN2I2NzUy
14
+ ZmIxYzZmZTgyYmQ4MTcxYjNmZDY1YTQ2ZjA0ZDNmYjkwMzU0ZTRlM2FjNWJk
15
+ ODRjMTM0ZmVhMzg2Yjk4MDJkOTRiMzBhYmYwYmU0OGEyYjNkZWQ=
data/bin/jira-scan CHANGED
@@ -17,7 +17,7 @@ def banner
17
17
  _ | | | '__/ _` |\\___ \\ / __/ _` | '_ \\
18
18
  | |__| | | | | (_| |____) | (_| (_| | | | |
19
19
  \\____/|_|_| \\__,_|_____/ \\___\\__,_|_| |_|
20
- version 0.0.4"
20
+ version 0.0.5"
21
21
  puts
22
22
  puts '-' * 60
23
23
  end
@@ -108,6 +108,14 @@ def scan(url, check: true, insecure: false, verbose: false)
108
108
  version = JiraScan::getVersionFromLogin(url) unless version
109
109
  puts "+ Version: #{version}" if version
110
110
 
111
+ # Retrieve Jira software information
112
+ info = JiraScan::getServerInfo(url)
113
+ unless info.empty?
114
+ puts "+ Server info:"
115
+ table = Terminal::Table.new :rows => info
116
+ puts table
117
+ end
118
+
111
119
  # Dev mode enabled
112
120
  dev_mode = JiraScan::devMode(url)
113
121
  puts '+ Dev mode is enabled' if dev_mode
@@ -161,6 +169,46 @@ def scan(url, check: true, insecure: false, verbose: false)
161
169
  puts table
162
170
  end
163
171
 
172
+ # Retrieve list of installed gadgets
173
+ gadgets = JiraScan::getGadgets(url)
174
+ unless gadgets.empty?
175
+ puts "+ Found gadgets (#{gadgets.length}):"
176
+ table = Terminal::Table.new :headings => ['Title', 'Author Name', 'Author Email', 'Description'], :rows => gadgets
177
+ puts table
178
+ end
179
+
180
+ # Retrieve list of resolutions
181
+ resolutions = JiraScan::getResolutions(url)
182
+ unless resolutions.empty?
183
+ puts "+ Found resolutions (#{resolutions.length}):"
184
+ table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => resolutions
185
+ puts table
186
+ end
187
+
188
+ # Retrieve list of projects
189
+ projects = JiraScan::getProjects(url)
190
+ unless projects.empty?
191
+ puts "+ Found projects (#{projects.length}):"
192
+ table = Terminal::Table.new :headings => ['ID', 'Key', 'Name'], :rows => projects
193
+ puts table
194
+ end
195
+
196
+ # Retrieve list of project categories
197
+ project_categories = JiraScan::getProjectCategories(url)
198
+ unless project_categories.empty?
199
+ puts "+ Found project categories (#{project_categories.length}):"
200
+ table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => project_categories
201
+ puts table
202
+ end
203
+
204
+ # Retrieve list of linked applications
205
+ apps = JiraScan::getLinkedApps(url)
206
+ unless apps.empty?
207
+ puts "+ Found linked applications (#{apps.length}):"
208
+ table = Terminal::Table.new :headings => ['Link', 'Label', 'Application Type'], :rows => apps
209
+ puts table
210
+ end
211
+
164
212
  # Retrieve list of field names
165
213
  field_names = JiraScan::getFieldNamesQueryComponentDefault(url)
166
214
  unless field_names.empty?
data/lib/jira_scan.rb CHANGED
@@ -11,7 +11,7 @@ require 'net/http'
11
11
  require 'openssl'
12
12
 
13
13
  class JiraScan
14
- VERSION = '0.0.4'.freeze
14
+ VERSION = '0.0.5'.freeze
15
15
 
16
16
  def self.logger
17
17
  @logger
@@ -115,6 +115,26 @@ class JiraScan
115
115
  "#{version}-##{build}"
116
116
  end
117
117
 
118
+ #
119
+ # Retrieve Jira software information
120
+ #
121
+ # @param [String] URL
122
+ #
123
+ # @return [Array] Jira software information
124
+ #
125
+ def self.getServerInfo(url)
126
+ url += '/' unless url.to_s.end_with? '/'
127
+ res = sendHttpRequest("#{url}rest/api/latest/serverInfo")
128
+
129
+ return [] unless res
130
+ return [] unless res.code.to_i == 200
131
+ return [] unless res.body.to_s.start_with?('{"baseUrl"')
132
+
133
+ JSON.parse(res.body.to_s, symbolize_names: true)
134
+ rescue
135
+ []
136
+ end
137
+
118
138
  #
119
139
  # Check if dev mode is enabled
120
140
  #
@@ -198,6 +218,7 @@ class JiraScan
198
218
 
199
219
  #
200
220
  # Check if unauthenticated access to REST UserPicker is allowed (CVE-2019-3403)
221
+ # https://jira.atlassian.com/browse/JRASERVER-69242
201
222
  #
202
223
  # @param [String] URL
203
224
  #
@@ -205,7 +226,7 @@ class JiraScan
205
226
  #
206
227
  def self.restUserPicker(url)
207
228
  url += '/' unless url.to_s.end_with? '/'
208
- res = sendHttpRequest("#{url}rest/api/latest/user/picker")
229
+ res = sendHttpRequest("#{url}rest/api/2/user/picker")
209
230
 
210
231
  return false unless res
211
232
  return false unless res.code.to_i == 400
@@ -215,6 +236,7 @@ class JiraScan
215
236
 
216
237
  #
217
238
  # Check if unauthenticated access to REST GroupUserPicker is allowed (CVE-2019-8449)
239
+ # https://jira.atlassian.com/browse/JRASERVER-69796
218
240
  #
219
241
  # @param [String] URL
220
242
  #
@@ -222,7 +244,7 @@ class JiraScan
222
244
  #
223
245
  def self.restGroupUserPicker(url)
224
246
  url += '/' unless url.to_s.end_with? '/'
225
- res = sendHttpRequest("#{url}rest/api/latest/groupuserpicker")
247
+ res = sendHttpRequest("#{url}rest/api/2/groupuserpicker")
226
248
 
227
249
  return false unless res
228
250
  return false unless res.code.to_i == 400
@@ -230,8 +252,33 @@ class JiraScan
230
252
  res.body.to_s.include?('The username query parameter was not provided')
231
253
  end
232
254
 
255
+ #
256
+ # Retrieve list of installed gadgets
257
+ # https://jira.atlassian.com/browse/JRASERVER-72613
258
+ #
259
+ # @param [String] URL
260
+ #
261
+ # @return [Array] list of installed gadgets
262
+ #
263
+ def self.getGadgets(url)
264
+ url += '/' unless url.to_s.end_with? '/'
265
+ res = sendHttpRequest("#{url}rest/config/1.0/directory.json")
266
+
267
+ return [] unless res
268
+ return [] unless res.code.to_i == 200
269
+ return [] unless res.body.to_s.start_with?('{"categories"')
270
+
271
+ gadgets = JSON.parse(res.body.to_s)['gadgets']
272
+ return [] if gadgets.empty?
273
+
274
+ JSON.parse(gadgets.to_json, symbolize_names: true).map { |g| [g[:title], g[:authorName], g[:authorEmail], g[:description]] }
275
+ rescue
276
+ []
277
+ end
278
+
233
279
  #
234
280
  # Check if unauthenticated access to ViewUserHover.jspa is allowed (CVE-2020-14181)
281
+ # https://jira.atlassian.com/browse/JRASERVER-71560
235
282
  #
236
283
  # @param [String] URL
237
284
  #
@@ -249,6 +296,7 @@ class JiraScan
249
296
 
250
297
  #
251
298
  # Check if META-INF contents are accessible (CVE-2019-8442)
299
+ # https://jira.atlassian.com/browse/JRASERVER-69241
252
300
  #
253
301
  # @param [String] URL
254
302
  #
@@ -266,6 +314,7 @@ class JiraScan
266
314
 
267
315
  #
268
316
  # Retrieve list of popular filters
317
+ # https://jira.atlassian.com/browse/JRASERVER-23255
269
318
  #
270
319
  # @param [String] URL
271
320
  #
@@ -302,14 +351,113 @@ class JiraScan
302
351
  return [] unless res
303
352
  return [] unless res.code.to_i == 200
304
353
  return [] unless res.body.to_s.start_with?('{"startAt"')
354
+ return [] unless res.body.to_s.include?('id')
355
+ return [] unless res.body.to_s.include?('name')
305
356
 
306
357
  JSON.parse(res.body.to_s, symbolize_names: true)[:dashboards].map { |d| [d[:id], d[:name]] }
307
358
  rescue
308
359
  []
309
360
  end
310
361
 
362
+ #
363
+ # Retrieve list of resolutions
364
+ #
365
+ # @param [String] URL
366
+ #
367
+ # @return [Array] list of resolutions
368
+ #
369
+ def self.getResolutions(url)
370
+ url += '/' unless url.to_s.end_with? '/'
371
+ res = sendHttpRequest("#{url}rest/api/2/resolution")
372
+
373
+ return [] unless res
374
+ return [] unless res.code.to_i == 200
375
+ return [] unless res.body.to_s.start_with?('[{"self"')
376
+ return [] unless res.body.to_s.include?('id')
377
+ return [] unless res.body.to_s.include?('name')
378
+ return [] unless res.body.to_s.include?('description')
379
+
380
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:name], r[:description]] }
381
+ rescue
382
+ []
383
+ end
384
+
385
+ #
386
+ # Retrieve list of projects
387
+ #
388
+ # @param [String] URL
389
+ #
390
+ # @return [Array] list of projects
391
+ #
392
+ def self.getProjects(url)
393
+ url += '/' unless url.to_s.end_with? '/'
394
+ max = 1_000
395
+ res = sendHttpRequest("#{url}rest/api/2/project?maxResults=#{max}")
396
+
397
+ return [] unless res
398
+ return [] unless res.code.to_i == 200
399
+ return [] unless res.body.to_s.start_with?('[{"expand"')
400
+ return [] unless res.body.to_s.include?('id')
401
+ return [] unless res.body.to_s.include?('key')
402
+ return [] unless res.body.to_s.include?('name')
403
+
404
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:key], r[:name]] }
405
+ rescue
406
+ []
407
+ end
408
+
409
+ #
410
+ # Retrieve list of project categories
411
+ #
412
+ # @param [String] URL
413
+ #
414
+ # @return [Array] list of project categories
415
+ #
416
+ def self.getProjectCategories(url)
417
+ url += '/' unless url.to_s.end_with? '/'
418
+ res = sendHttpRequest("#{url}rest/api/2/projectCategory")
419
+
420
+ return [] unless res
421
+ return [] unless res.code.to_i == 200
422
+ return [] unless res.body.to_s.start_with?('[{"self"')
423
+ return [] unless res.body.to_s.include?('id')
424
+ return [] unless res.body.to_s.include?('name')
425
+ return [] unless res.body.to_s.include?('description')
426
+
427
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:name], r[:description]] }
428
+ rescue
429
+ []
430
+ end
431
+
432
+ #
433
+ # Retrieve list of linked applications
434
+ # https://jira.atlassian.com/browse/JRASERVER-64963
435
+ # https://jira.atlassian.com/browse/JRACLOUD-64963
436
+ #
437
+ # @param [String] URL
438
+ #
439
+ # @return [Array] list of linked applications
440
+ #
441
+ def self.getLinkedApps(url)
442
+ url += '/' unless url.to_s.end_with? '/'
443
+ res = sendHttpRequest("#{url}rest/menu/latest/admin")
444
+
445
+ return [] unless res
446
+ return [] unless res.code.to_i == 200
447
+ return [] unless res.body.to_s.start_with?('[{"key"')
448
+ return [] unless res.body.to_s.include?('link')
449
+ return [] unless res.body.to_s.include?('label')
450
+ return [] unless res.body.to_s.include?('applicationType')
451
+
452
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:link], r[:label], r[:applicationType]] }
453
+ rescue
454
+ []
455
+ end
456
+
311
457
  #
312
458
  # Retrieve list of field names from QueryComponent!Default.jspa (CVE-2020-14179)
459
+ # https://jira.atlassian.com/browse/JRASERVER-71536
460
+ # https://jira.atlassian.com/browse/JRACLOUD-75661
313
461
  #
314
462
  # @param [String] URL
315
463
  #
@@ -342,7 +490,8 @@ class JiraScan
342
490
  end
343
491
 
344
492
  #
345
- # Retrieve list of field names from QueryComponent!Jql.jspa (EDB-49924)
493
+ # Retrieve list of field names from QueryComponent!Jql.jspa (CVE-2020-14179)
494
+ # https://jira.atlassian.com/browse/JRASERVER-71536
346
495
  #
347
496
  # @param [String] URL
348
497
  #
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jira_scan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brendan Coles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-12 00:00:00.000000000 Z
11
+ date: 2021-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: terminal-table
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '3.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '3.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: logger
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.4'
41
41
  description: A simple remote scanner for Atlassian Jira
42
42
  email: bcoles@gmail.com
43
43
  executables: