jira_scan 0.0.4 → 0.0.6

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 +5 -13
  2. data/bin/jira-scan +59 -15
  3. data/lib/jira_scan.rb +174 -4
  4. metadata +16 -18
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YjNmYTk1OWYwM2VjNzJlZjVmZGFlZGIyNzdlYmUyOGE3Mzg0NTIxNg==
5
- data.tar.gz: !binary |-
6
- NDA1MWUyMjE2ODIwODBhMThjYjU2ZWNlY2VhMjcxY2E0YWIyODYyZQ==
2
+ SHA256:
3
+ metadata.gz: 694f95d2a4df4f67588a35cce083c44568ab6fd6411cad9be7b778f86fdc74f7
4
+ data.tar.gz: a52b797b7810b69b20921a6ae539aebc070df18968ed41fb002319fce71db47b
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MjliM2EyYWE0MzFjNDllMWMyOTljMDYyOGRkYTU3ZDc2NTk2MDc0ZTg0ODJi
10
- Zjg3MTAwOGU5MjkzNmEzNmZkNGZkYWY4YTNhMDE3Mzg0YzEzNjVkOGUzMjMy
11
- YTI1MGQ0NDg1NzA4Y2YzNjE3ODM0MWQ2NTJiOTk1NDUzZDI1ZmU=
12
- data.tar.gz: !binary |-
13
- NDM2MTdiY2ViMzVlYmEwZTg5MTY4NGI0NWY0M2IwZmFjMGFjYmM5MzRhZmFi
14
- NmYxYTkzYWE4ODAzNDYwNzNjZTNmNGE1OGM1OGJjMzAyZDYwYWRkMzg0NWRl
15
- NzVmNTQ2NmY3NTNhZWQ3ODA0MWZhNDVjN2M0YzQ2OTQ5Y2M1MDg=
6
+ metadata.gz: c9f02c01c0b3aff58e99d484a09eef8c30c594706d08bbb0d3197411dc038ff07dfcd6bf0ae3074eec3d7e8ac40d375a51e2ab6f8b20c5bde986e3e63fffe5cf
7
+ data.tar.gz: 39cdb3fa320f6e3dca07bf9bb3b5926eb9a1c2adfbc5ca735a05fbcd89632443d9939c51b49f8657a6f5e37f1e9cfa2b1e4d78901cd10e22b6eec461448398df
data/bin/jira-scan CHANGED
@@ -17,7 +17,7 @@ def banner
17
17
  _ | | | '__/ _` |\\___ \\ / __/ _` | '_ \\
18
18
  | |__| | | | | (_| |____) | (_| (_| | | | |
19
19
  \\____/|_|_| \\__,_|_____/ \\___\\__,_|_| |_|
20
- version 0.0.4"
20
+ version #{JiraScan::VERSION}"
21
21
  puts
22
22
  puts '-' * 60
23
23
  end
@@ -108,17 +108,25 @@ 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
- dev_mode = JiraScan::devMode(url)
113
- puts '+ Dev mode is enabled' if dev_mode
120
+ puts '+ Dev mode is enabled' if JiraScan::devMode(url)
114
121
 
115
122
  # User registration enabled
116
- register = JiraScan::userRegistration(url)
117
- puts '+ User registration is enabled' if register
123
+ puts '+ User registration is enabled' if JiraScan::userRegistration(url)
124
+
125
+ # Service Desk user registration enabled
126
+ puts '+ Service Desk user registration is enabled' if JiraScan::userServiceDeskRegistration(url)
118
127
 
119
128
  # Check if User Picker Browser is accessible
120
- user_picker = JiraScan::userPickerBrowser(url)
121
- if user_picker
129
+ if JiraScan::userPickerBrowser(url)
122
130
  puts '+ User Picker Browser is available'
123
131
  # Retrieve list of first 1,000 users
124
132
  users = JiraScan::getUsersFromUserPickerBrowser(url)
@@ -130,20 +138,16 @@ def scan(url, check: true, insecure: false, verbose: false)
130
138
  end
131
139
 
132
140
  # Check if REST User Picker is accessible
133
- rest_user_picker = JiraScan::restUserPicker(url)
134
- puts "+ REST UserPicker is available" if rest_user_picker
141
+ puts "+ REST UserPicker is available" if JiraScan::restUserPicker(url)
135
142
 
136
143
  # Check if REST Group User Picker is accessible
137
- rest_group_user_picker = JiraScan::restGroupUserPicker(url)
138
- puts "+ REST GroupUserPicker is available" if rest_group_user_picker
144
+ puts "+ REST GroupUserPicker is available" if JiraScan::restGroupUserPicker(url)
139
145
 
140
146
  # Check if ViewUserHover.jspa is accessible
141
- view_user_hover = JiraScan::viewUserHover(url)
142
- puts "+ ViewUserHover.jspa is available" if view_user_hover
147
+ puts "+ ViewUserHover.jspa is available" if JiraScan::viewUserHover(url)
143
148
 
144
149
  # Check if META-INF contents are accessible
145
- meta_inf = JiraScan::metaInf(url)
146
- puts '+ META-INF directory contents are accessible' if meta_inf
150
+ puts '+ META-INF directory contents are accessible' if JiraScan::metaInf(url)
147
151
 
148
152
  # Retrieve list of dashboards
149
153
  dashboards = JiraScan::getDashboards(url)
@@ -161,6 +165,46 @@ def scan(url, check: true, insecure: false, verbose: false)
161
165
  puts table
162
166
  end
163
167
 
168
+ # Retrieve list of installed gadgets
169
+ gadgets = JiraScan::getGadgets(url)
170
+ unless gadgets.empty?
171
+ puts "+ Found gadgets (#{gadgets.length}):"
172
+ table = Terminal::Table.new :headings => ['Title', 'Author Name', 'Author Email', 'Description'], :rows => gadgets
173
+ puts table
174
+ end
175
+
176
+ # Retrieve list of resolutions
177
+ resolutions = JiraScan::getResolutions(url)
178
+ unless resolutions.empty?
179
+ puts "+ Found resolutions (#{resolutions.length}):"
180
+ table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => resolutions
181
+ puts table
182
+ end
183
+
184
+ # Retrieve list of projects
185
+ projects = JiraScan::getProjects(url)
186
+ unless projects.empty?
187
+ puts "+ Found projects (#{projects.length}):"
188
+ table = Terminal::Table.new :headings => ['ID', 'Key', 'Name'], :rows => projects
189
+ puts table
190
+ end
191
+
192
+ # Retrieve list of project categories
193
+ project_categories = JiraScan::getProjectCategories(url)
194
+ unless project_categories.empty?
195
+ puts "+ Found project categories (#{project_categories.length}):"
196
+ table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => project_categories
197
+ puts table
198
+ end
199
+
200
+ # Retrieve list of linked applications
201
+ apps = JiraScan::getLinkedApps(url)
202
+ unless apps.empty?
203
+ puts "+ Found linked applications (#{apps.length}):"
204
+ table = Terminal::Table.new :headings => ['Link', 'Label', 'Application Type'], :rows => apps
205
+ puts table
206
+ end
207
+
164
208
  # Retrieve list of field names
165
209
  field_names = JiraScan::getFieldNamesQueryComponentDefault(url)
166
210
  unless field_names.empty?
data/lib/jira_scan.rb CHANGED
@@ -9,9 +9,10 @@ require 'json'
9
9
  require 'logger'
10
10
  require 'net/http'
11
11
  require 'openssl'
12
+ require 'stringio'
12
13
 
13
14
  class JiraScan
14
- VERSION = '0.0.4'.freeze
15
+ VERSION = '0.0.6'.freeze
15
16
 
16
17
  def self.logger
17
18
  @logger
@@ -115,6 +116,26 @@ class JiraScan
115
116
  "#{version}-##{build}"
116
117
  end
117
118
 
119
+ #
120
+ # Retrieve Jira software information
121
+ #
122
+ # @param [String] URL
123
+ #
124
+ # @return [Array] Jira software information
125
+ #
126
+ def self.getServerInfo(url)
127
+ url += '/' unless url.to_s.end_with? '/'
128
+ res = sendHttpRequest("#{url}rest/api/latest/serverInfo")
129
+
130
+ return [] unless res
131
+ return [] unless res.code.to_i == 200
132
+ return [] unless res.body.to_s.start_with?('{"baseUrl"')
133
+
134
+ JSON.parse(res.body.to_s, symbolize_names: true)
135
+ rescue
136
+ []
137
+ end
138
+
118
139
  #
119
140
  # Check if dev mode is enabled
120
141
  #
@@ -134,6 +155,7 @@ class JiraScan
134
155
 
135
156
  #
136
157
  # Check if account registration is enabled
158
+ # https://docs.atlassian.com/jira/jsd-docs-045/Configuring+public+signup
137
159
  #
138
160
  # @param [String] URL
139
161
  #
@@ -149,6 +171,25 @@ class JiraScan
149
171
  res.body.to_s.include?('<h1>Sign up</h1>')
150
172
  end
151
173
 
174
+ #
175
+ # Check if Jira Service Desk (part of Jira Service Management) account registration is enabled
176
+ # https://docs.atlassian.com/jira/jsd-docs-045/Configuring+public+signup
177
+ # https://support.atlassian.com/jira-service-management-cloud/docs/customer-permissions-for-your-service-project-and-jira-site/
178
+ #
179
+ # @param [String] URL
180
+ #
181
+ # @return [Boolean]
182
+ #
183
+ def self.userServiceDeskRegistration(url)
184
+ url += '/' unless url.to_s.end_with? '/'
185
+ res = sendHttpRequest("#{url}servicedesk/customer/user/signup")
186
+
187
+ return false unless res
188
+ return false unless res.code.to_i == 200
189
+
190
+ res.body.to_s.include?('serviceDeskVersion') || res.body.to_s.include?('com.atlassian.servicedesk')
191
+ end
192
+
152
193
  #
153
194
  # Check if unauthenticated access to UserPickerBrowser.jspa is allowed
154
195
  #
@@ -198,6 +239,7 @@ class JiraScan
198
239
 
199
240
  #
200
241
  # Check if unauthenticated access to REST UserPicker is allowed (CVE-2019-3403)
242
+ # https://jira.atlassian.com/browse/JRASERVER-69242
201
243
  #
202
244
  # @param [String] URL
203
245
  #
@@ -205,7 +247,7 @@ class JiraScan
205
247
  #
206
248
  def self.restUserPicker(url)
207
249
  url += '/' unless url.to_s.end_with? '/'
208
- res = sendHttpRequest("#{url}rest/api/latest/user/picker")
250
+ res = sendHttpRequest("#{url}rest/api/2/user/picker")
209
251
 
210
252
  return false unless res
211
253
  return false unless res.code.to_i == 400
@@ -215,6 +257,7 @@ class JiraScan
215
257
 
216
258
  #
217
259
  # Check if unauthenticated access to REST GroupUserPicker is allowed (CVE-2019-8449)
260
+ # https://jira.atlassian.com/browse/JRASERVER-69796
218
261
  #
219
262
  # @param [String] URL
220
263
  #
@@ -222,7 +265,7 @@ class JiraScan
222
265
  #
223
266
  def self.restGroupUserPicker(url)
224
267
  url += '/' unless url.to_s.end_with? '/'
225
- res = sendHttpRequest("#{url}rest/api/latest/groupuserpicker")
268
+ res = sendHttpRequest("#{url}rest/api/2/groupuserpicker")
226
269
 
227
270
  return false unless res
228
271
  return false unless res.code.to_i == 400
@@ -230,8 +273,33 @@ class JiraScan
230
273
  res.body.to_s.include?('The username query parameter was not provided')
231
274
  end
232
275
 
276
+ #
277
+ # Retrieve list of installed gadgets
278
+ # https://jira.atlassian.com/browse/JRASERVER-72613
279
+ #
280
+ # @param [String] URL
281
+ #
282
+ # @return [Array] list of installed gadgets
283
+ #
284
+ def self.getGadgets(url)
285
+ url += '/' unless url.to_s.end_with? '/'
286
+ res = sendHttpRequest("#{url}rest/config/1.0/directory.json")
287
+
288
+ return [] unless res
289
+ return [] unless res.code.to_i == 200
290
+ return [] unless res.body.to_s.start_with?('{"categories"')
291
+
292
+ gadgets = JSON.parse(res.body.to_s)['gadgets']
293
+ return [] if gadgets.empty?
294
+
295
+ JSON.parse(gadgets.to_json, symbolize_names: true).map { |g| [g[:title], g[:authorName], g[:authorEmail], g[:description]] }
296
+ rescue
297
+ []
298
+ end
299
+
233
300
  #
234
301
  # Check if unauthenticated access to ViewUserHover.jspa is allowed (CVE-2020-14181)
302
+ # https://jira.atlassian.com/browse/JRASERVER-71560
235
303
  #
236
304
  # @param [String] URL
237
305
  #
@@ -249,6 +317,7 @@ class JiraScan
249
317
 
250
318
  #
251
319
  # Check if META-INF contents are accessible (CVE-2019-8442)
320
+ # https://jira.atlassian.com/browse/JRASERVER-69241
252
321
  #
253
322
  # @param [String] URL
254
323
  #
@@ -266,6 +335,7 @@ class JiraScan
266
335
 
267
336
  #
268
337
  # Retrieve list of popular filters
338
+ # https://jira.atlassian.com/browse/JRASERVER-23255
269
339
  #
270
340
  # @param [String] URL
271
341
  #
@@ -302,14 +372,113 @@ class JiraScan
302
372
  return [] unless res
303
373
  return [] unless res.code.to_i == 200
304
374
  return [] unless res.body.to_s.start_with?('{"startAt"')
375
+ return [] unless res.body.to_s.include?('id')
376
+ return [] unless res.body.to_s.include?('name')
305
377
 
306
378
  JSON.parse(res.body.to_s, symbolize_names: true)[:dashboards].map { |d| [d[:id], d[:name]] }
307
379
  rescue
308
380
  []
309
381
  end
310
382
 
383
+ #
384
+ # Retrieve list of resolutions
385
+ #
386
+ # @param [String] URL
387
+ #
388
+ # @return [Array] list of resolutions
389
+ #
390
+ def self.getResolutions(url)
391
+ url += '/' unless url.to_s.end_with? '/'
392
+ res = sendHttpRequest("#{url}rest/api/2/resolution")
393
+
394
+ return [] unless res
395
+ return [] unless res.code.to_i == 200
396
+ return [] unless res.body.to_s.start_with?('[{"self"')
397
+ return [] unless res.body.to_s.include?('id')
398
+ return [] unless res.body.to_s.include?('name')
399
+ return [] unless res.body.to_s.include?('description')
400
+
401
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:name], r[:description]] }
402
+ rescue
403
+ []
404
+ end
405
+
406
+ #
407
+ # Retrieve list of projects
408
+ #
409
+ # @param [String] URL
410
+ #
411
+ # @return [Array] list of projects
412
+ #
413
+ def self.getProjects(url)
414
+ url += '/' unless url.to_s.end_with? '/'
415
+ max = 1_000
416
+ res = sendHttpRequest("#{url}rest/api/2/project?maxResults=#{max}")
417
+
418
+ return [] unless res
419
+ return [] unless res.code.to_i == 200
420
+ return [] unless res.body.to_s.start_with?('[{"expand"')
421
+ return [] unless res.body.to_s.include?('id')
422
+ return [] unless res.body.to_s.include?('key')
423
+ return [] unless res.body.to_s.include?('name')
424
+
425
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:key], r[:name]] }
426
+ rescue
427
+ []
428
+ end
429
+
430
+ #
431
+ # Retrieve list of project categories
432
+ #
433
+ # @param [String] URL
434
+ #
435
+ # @return [Array] list of project categories
436
+ #
437
+ def self.getProjectCategories(url)
438
+ url += '/' unless url.to_s.end_with? '/'
439
+ res = sendHttpRequest("#{url}rest/api/2/projectCategory")
440
+
441
+ return [] unless res
442
+ return [] unless res.code.to_i == 200
443
+ return [] unless res.body.to_s.start_with?('[{"self"')
444
+ return [] unless res.body.to_s.include?('id')
445
+ return [] unless res.body.to_s.include?('name')
446
+ return [] unless res.body.to_s.include?('description')
447
+
448
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:id], r[:name], r[:description]] }
449
+ rescue
450
+ []
451
+ end
452
+
453
+ #
454
+ # Retrieve list of linked applications
455
+ # https://jira.atlassian.com/browse/JRASERVER-64963
456
+ # https://jira.atlassian.com/browse/JRACLOUD-64963
457
+ #
458
+ # @param [String] URL
459
+ #
460
+ # @return [Array] list of linked applications
461
+ #
462
+ def self.getLinkedApps(url)
463
+ url += '/' unless url.to_s.end_with? '/'
464
+ res = sendHttpRequest("#{url}rest/menu/latest/admin")
465
+
466
+ return [] unless res
467
+ return [] unless res.code.to_i == 200
468
+ return [] unless res.body.to_s.start_with?('[{"key"')
469
+ return [] unless res.body.to_s.include?('link')
470
+ return [] unless res.body.to_s.include?('label')
471
+ return [] unless res.body.to_s.include?('applicationType')
472
+
473
+ JSON.parse(res.body.to_s, symbolize_names: true).map { |r| [r[:link], r[:label], r[:applicationType]] }
474
+ rescue
475
+ []
476
+ end
477
+
311
478
  #
312
479
  # Retrieve list of field names from QueryComponent!Default.jspa (CVE-2020-14179)
480
+ # https://jira.atlassian.com/browse/JRASERVER-71536
481
+ # https://jira.atlassian.com/browse/JRACLOUD-75661
313
482
  #
314
483
  # @param [String] URL
315
484
  #
@@ -342,7 +511,8 @@ class JiraScan
342
511
  end
343
512
 
344
513
  #
345
- # Retrieve list of field names from QueryComponent!Jql.jspa (EDB-49924)
514
+ # Retrieve list of field names from QueryComponent!Jql.jspa (CVE-2020-14179)
515
+ # https://jira.atlassian.com/browse/JRASERVER-71536
346
516
  #
347
517
  # @param [String] URL
348
518
  #
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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brendan Coles
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-12 00:00:00.000000000 Z
11
+ date: 2023-01-30 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:
@@ -51,25 +51,23 @@ homepage: https://github.com/bcoles/jira_scan
51
51
  licenses:
52
52
  - MIT
53
53
  metadata: {}
54
- post_install_message:
54
+ post_install_message:
55
55
  rdoc_options: []
56
56
  require_paths:
57
57
  - lib
58
58
  required_ruby_version: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - ! '>='
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  version: 2.0.0
63
63
  required_rubygems_version: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ! '>='
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  requirements: []
69
- rubyforge_project:
70
- rubygems_version: 2.2.2
71
- signing_key:
69
+ rubygems_version: 3.3.15
70
+ signing_key:
72
71
  specification_version: 4
73
72
  summary: Jira scanner
74
73
  test_files: []
75
- has_rdoc: