pwn 0.5.418 → 0.5.420

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca843067a55f8abc4c2744c560005ae75c44e0d07bad83839c48759e746ac6bc
4
- data.tar.gz: 6577dab55259135b26811501b909b391f343986ea197294f9439b951430a1f8a
3
+ metadata.gz: 2d5a855c21b43563e86f8f7d578a31990e8839790b19fe10361069a5d0c20047
4
+ data.tar.gz: 629dc874fe7bf352464c17444fb08a1f8bcf7e02707922436b33c3daad121639
5
5
  SHA512:
6
- metadata.gz: 9d52aa68e7783c6e320a0cf860f2dc5bb562edade1aace4605541123895adf7d13cd0981f125aec2f6363fbd8776213ac7f17d44a85d7d81729c050118391263
7
- data.tar.gz: 879351a157b957060cb663b34f1d3364afda5253b4eac0e724383967e06e541e0ab27bb715b99e6c95582fca775dd5492d63397013db6ef2d9ad0cf133a41c2a
6
+ metadata.gz: 830c7ef71de941f149f46cb7c9aedde2403f40e5c95b0cd424ab2d934a64a889239d1f25d1b0ccd7018d6eccfb1687003c422c5301beb99e3d7f4fceb4f22ece
7
+ data.tar.gz: cc6ea4c493558334130b7236da4cb547df6ee63e97b7c6c6597027ff1a7c4c1d69187399180b35e24443f43272a473d8f3338d7ebb89f31883b055d8b77d761e
data/.rubocop.yml CHANGED
@@ -18,7 +18,7 @@ Metrics/CyclomaticComplexity:
18
18
  Metrics/MethodLength:
19
19
  Max: 564
20
20
  Metrics/ModuleLength:
21
- Max: 1000
21
+ Max: 1107
22
22
  Metrics/PerceivedComplexity:
23
23
  Max: 156
24
24
  Style/HashEachMethods:
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ gemspec
11
11
  # In some circumstances custom flags are passed to gems in order
12
12
  # to build appropriately. Defer to ./reinstall_pwn_gemset.sh
13
13
  # to review these custom flags (e.g. pg, serialport, etc).
14
- gem 'activesupport', '8.0.2.1'
14
+ gem 'activesupport', '8.0.3'
15
15
  gem 'anemone', '0.7.2'
16
16
  gem 'authy', '3.0.1'
17
17
  gem 'aws-sdk', '3.3.0'
@@ -33,7 +33,7 @@ gem 'ffi', '1.17.2'
33
33
  gem 'fftw3', '0.3'
34
34
  gem 'gdb', '1.0.0'
35
35
  gem 'gem-wrappers', '1.4.0'
36
- gem 'geocoder', '1.8.5'
36
+ gem 'geocoder', '1.8.6'
37
37
  gem 'gist', '6.0.0'
38
38
  gem 'gruff', '0.29.0'
39
39
  # gem 'hidapi', '0.1.9'
@@ -41,14 +41,14 @@ gem 'htmlentities', '4.3.4'
41
41
  gem 'ipaddress', '0.8.3'
42
42
  gem 'jenkins_api_client2', '1.9.0'
43
43
  gem 'js-beautify', '0.1.8'
44
- gem 'json', '2.14.1'
44
+ gem 'json', '2.15.0'
45
45
  gem 'jsonpath', '1.1.5'
46
46
  gem 'json_schemer', '2.4.0'
47
47
  gem 'jwt', '3.1.2'
48
48
  gem 'libusb', '0.7.2'
49
49
  gem 'luhn', '1.0.2'
50
50
  gem 'mail', '2.8.1'
51
- gem 'meshtastic', '0.0.125'
51
+ gem 'meshtastic', '0.0.126'
52
52
  gem 'metasm', '1.0.5'
53
53
  gem 'mongo', '2.21.3'
54
54
  gem 'msfrpc-client', '1.1.2'
@@ -93,10 +93,10 @@ gem 'savon', '2.15.1'
93
93
  gem 'selenium-devtools', '0.139.0'
94
94
  # gem 'serialport', '1.3.2'
95
95
  # gem 'sinatra', '4.0.0'
96
- gem 'slack-ruby-client', '2.7.0'
96
+ gem 'slack-ruby-client', '3.0.0'
97
97
  gem 'socksify', '1.8.1'
98
98
  gem 'spreadsheet', '1.3.4'
99
- gem 'sqlite3', '2.7.3'
99
+ gem 'sqlite3', '2.7.4'
100
100
  gem 'thin', '2.0.1'
101
101
  gem 'tty-prompt', '0.23.1'
102
102
  gem 'tty-spinner', '0.9.3'
data/README.md CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
37
37
  $ ./install.sh
38
38
  $ ./install.sh ruby-gem
39
39
  $ pwn
40
- pwn[v0.5.418]:001 >>> PWN.help
40
+ pwn[v0.5.420]:001 >>> PWN.help
41
41
  ```
42
42
 
43
43
  [![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.418]:001 >>> PWN.help
55
+ pwn[v0.5.420]:001 >>> PWN.help
56
56
  ```
57
57
 
58
58
  If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
62
62
  $ rvmsudo gem uninstall --all --executables pwn
63
63
  $ rvmsudo gem install --verbose pwn
64
64
  $ pwn
65
- pwn[v0.5.418]:001 >>> PWN.help
65
+ pwn[v0.5.420]:001 >>> PWN.help
66
66
  ```
67
67
 
68
68
  PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
@@ -360,7 +360,8 @@ module PWN
360
360
  # Supported Method Parameters::
361
361
  # json_sitemap = PWN::Plugins::BurpSuite.get_sitemap(
362
362
  # burp_obj: 'required - burp_obj returned by #start method',
363
- # keyword: 'optional - keyword to filter sitemap entries (default: nil)'
363
+ # keyword: 'optional - keyword to filter sitemap entries (default: nil)',
364
+ # return_as: 'optional - :base64 or :har (defaults to :base64)'
364
365
  # )
365
366
 
366
367
  public_class_method def self.get_sitemap(opts = {})
@@ -368,6 +369,7 @@ module PWN
368
369
  rest_browser = burp_obj[:rest_browser]
369
370
  mitm_rest_api = burp_obj[:mitm_rest_api]
370
371
  keyword = opts[:keyword]
372
+ return_as = opts[:return_as] ||= :base64
371
373
 
372
374
  rest_call = "http://#{mitm_rest_api}/sitemap"
373
375
 
@@ -385,6 +387,151 @@ module PWN
385
387
  end
386
388
  end
387
389
 
390
+ if return_as == :har
391
+ # Convert to HAR format
392
+ har_entries = sitemap_arr.map do |site|
393
+ decoded_request = Base64.strict_decode64(site[:request])
394
+
395
+ # Parse request head and body
396
+ if decoded_request.include?("\r\n\r\n")
397
+ request_head, request_body = decoded_request.split("\r\n\r\n", 2)
398
+ else
399
+ request_head = decoded_request
400
+ request_body = ''
401
+ end
402
+ request_lines = request_head.split("\r\n")
403
+ request_line = request_lines.shift
404
+ method, full_path, http_version = request_line.split(' ', 3)
405
+ headers = {}
406
+ request_lines.each do |line|
407
+ next if line.empty?
408
+
409
+ key, value = line.split(': ', 2)
410
+ headers[key] = value if key && value
411
+ end
412
+
413
+ host = headers['Host'] || raise('No Host header found in request')
414
+ scheme = 'http' # Hardcoded as protocol is not available; consider enhancing if available in site
415
+ url = "#{scheme}://#{host}#{full_path}"
416
+ uri = URI.parse(url)
417
+ query_string = uri.query ? URI.decode_www_form(uri.query).map { |k, v| { name: k, value: v.to_s } } : []
418
+
419
+ request_headers_size = request_head.bytesize + 4 # Account for \r\n\r\n
420
+ request_body_size = request_body.bytesize
421
+
422
+ request_obj = {
423
+ method: method,
424
+ url: uri.to_s,
425
+ httpVersion: http_version,
426
+ headers: headers.map { |k, v| { name: k, value: v } },
427
+ queryString: query_string,
428
+ headersSize: request_headers_size,
429
+ bodySize: request_body_size
430
+ }
431
+
432
+ if request_body_size.positive?
433
+ mime_type = headers['Content-Type'] || 'application/octet-stream'
434
+ post_data = {
435
+ mimeType: mime_type,
436
+ text: request_body
437
+ }
438
+ post_data[:params] = URI.decode_www_form(request_body).map { |k, v| { name: k, value: v.to_s } } if mime_type.include?('x-www-form-urlencoded')
439
+ request_obj[:postData] = post_data
440
+ end
441
+
442
+ if site[:response]
443
+ decoded_response = Base64.strict_decode64(site[:response])
444
+
445
+ # Parse response head and body
446
+ if decoded_response.include?("\r\n\r\n")
447
+ response_head, response_body = decoded_response.split("\r\n\r\n", 2)
448
+ else
449
+ response_head = decoded_response
450
+ response_body = ''
451
+ end
452
+ response_lines = response_head.split("\r\n")
453
+ status_line = response_lines.shift
454
+ version, status_str, status_text = status_line.split(' ', 3)
455
+ status = status_str.to_i
456
+ status_text ||= ''
457
+ response_headers = {}
458
+ response_lines.each do |line|
459
+ next if line.empty?
460
+
461
+ key, value = line.split(': ', 2)
462
+ response_headers[key] = value if key && value
463
+ end
464
+
465
+ response_headers_size = response_head.bytesize + 4 # Account for \r\n\r\n
466
+ response_body_size = response_body.bytesize
467
+ mime_type = response_headers['Content-Type'] || 'text/plain'
468
+
469
+ response_obj = {
470
+ status: status,
471
+ statusText: status_text,
472
+ httpVersion: version,
473
+ headers: response_headers.map { |k, v| { name: k, value: v } },
474
+ content: {
475
+ size: response_body_size,
476
+ mimeType: mime_type,
477
+ text: response_body
478
+ },
479
+ redirectURL: response_headers['Location'] || '',
480
+ headersSize: response_headers_size,
481
+ bodySize: response_body_size
482
+ }
483
+ else
484
+ response_obj = {
485
+ status: 0,
486
+ statusText: 'No response',
487
+ httpVersion: 'unknown',
488
+ headers: [],
489
+ content: {
490
+ size: 0,
491
+ mimeType: 'text/plain',
492
+ text: ''
493
+ },
494
+ redirectURL: '',
495
+ headersSize: -1,
496
+ bodySize: 0
497
+ }
498
+ end
499
+
500
+ {
501
+ startedDateTime: Time.now.iso8601,
502
+ time: 0,
503
+ request: request_obj,
504
+ response: response_obj,
505
+ cache: {},
506
+ timings: {
507
+ send: 0,
508
+ wait: 0,
509
+ receive: 0
510
+ },
511
+ pageref: 'page_1'
512
+ }
513
+ end
514
+
515
+ har_log = {
516
+ log: {
517
+ version: '1.2',
518
+ creator: {
519
+ name: 'BurpSuite via PWN::Plugins::BurpSuite',
520
+ version: '1.0'
521
+ },
522
+ pages: [{
523
+ startedDateTime: Time.now.iso8601,
524
+ id: 'page_1',
525
+ title: 'Sitemap Export',
526
+ pageTimings: {}
527
+ }],
528
+ entries: har_entries
529
+ }
530
+ }
531
+
532
+ sitemap_arr = har_log
533
+ end
534
+
388
535
  sitemap_arr.uniq
389
536
  rescue StandardError => e
390
537
  stop(burp_obj: burp_obj) unless burp_obj.nil?
@@ -1357,7 +1504,8 @@ module PWN
1357
1504
 
1358
1505
  json_sitemap = #{self}.get_sitemap(
1359
1506
  burp_obj: 'required - burp_obj returned by #start method',
1360
- keyword: 'optional - keyword to filter sitemap results (default: nil)'
1507
+ keyword: 'optional - keyword to filter sitemap results (default: nil)',
1508
+ return_as: 'optional - :base64 or :har (defaults to :base64)'
1361
1509
  )
1362
1510
 
1363
1511
  json_sitemap = #{self}.add_to_sitemap(
@@ -206,6 +206,7 @@ module PWN
206
206
  api_key = zap_obj[:api_key].to_s.scrub
207
207
  keyword = opts[:keyword]
208
208
  return_as = opts[:return_as] ||= :base64
209
+ raise 'ERROR: return_as must be :base64 or :har' unless %i[base64 har].include?(return_as)
209
210
 
210
211
  entries = []
211
212
  start = 0
@@ -229,16 +230,16 @@ module PWN
229
230
  start += count
230
231
  end
231
232
 
232
- case return_as
233
- when :har
234
- if keyword
235
- entries = har_sitemap.select do |site|
236
- json_request = site[:request].to_json
237
- json_request.include?(keyword)
238
- end
233
+ if keyword
234
+ entries = har_sitemap.select do |site|
235
+ json_request = site[:request].to_json
236
+ json_request.include?(keyword)
239
237
  end
240
- when :base64
238
+ end
239
+
240
+ if return_as == :base64
241
241
  # Deduplicate entries based on method + url
242
+ base64_entries = []
242
243
  entries.each do |entry|
243
244
  entry_hash = {}
244
245
  req = entry[:request]
@@ -279,17 +280,9 @@ module PWN
279
280
  entry_hash[:request] = encoded_req
280
281
  entry_hash[:response] = encoded_res
281
282
  entry_hash[:http_service] = http_service
282
- entries.push(entry_hash)
283
+ base64_entries.push(entry_hash)
283
284
  end
284
-
285
- if keyword
286
- entries = entries.select do |site|
287
- deccoded_request = Base64.strict_decode64(site[:request])
288
- deccoded_request.include?(keyword)
289
- end
290
- end
291
- else
292
- raise "ERROR: Invalid return_as option #{return_as}. Valid options are :base64 or :har"
285
+ entries = base64_entries
293
286
  end
294
287
 
295
288
  entries.uniq
@@ -20,7 +20,7 @@ module PWN
20
20
  # ai_key: 'optional - AI Key/Token for Respective AI Engine',
21
21
  # ai_fqdn: 'optional - AI FQDN (Only Required for "ollama" AI Engine)',
22
22
  # ai_system_role_content: 'optional - AI System Role Content (Defaults to "Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.")',
23
- # ai_temp: 'optional - AI Temperature (Defaults to 0.9)'
23
+ # ai_temp: 'optional - AI Temperature (Defaults to 0.1)'
24
24
  # )
25
25
 
26
26
  public_class_method def self.generate(opts = {})
@@ -45,7 +45,7 @@ module PWN
45
45
 
46
46
  ai_key = opts[:ai_key] ||= PWN::Plugins::AuthenticationHelper.mask_password(prompt: "#{ai_engine} Token")
47
47
  ai_system_role_content = opts[:ai_system_role_content] ||= 'Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.'
48
- ai_temp = opts[:ai_temp] ||= 0.9
48
+ ai_temp = opts[:ai_temp] ||= 0.1
49
49
 
50
50
  puts "Analyzing source code using AI engine: #{ai_engine}\nModel: #{ai_model}\nSystem Role Content: #{ai_system_role_content}\nTemperature: #{ai_temp}"
51
51
  end
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.418'
4
+ VERSION = '0.5.420'
5
5
  end
@@ -1069,7 +1069,7 @@
1069
1069
  {"messages":[{"role":"user","content":"PWN::Reports::Phone.generate Usage"},{"role":"assistant","content":"`PWN::Reports::Phone.generate`: Supported Method Parameters\n\nPWN::Reports::Phone.generate(\n\ndir_path: dir_path,\nresults_hash: results_hash\n\n)\n"}]}
1070
1070
  {"messages":[{"role":"user","content":"PWN::Reports::Phone.help Usage"},{"role":"assistant","content":"`PWN::Reports::Phone.help`: "}]}
1071
1071
  {"messages":[{"role":"user","content":"PWN::Reports::SAST.authors Usage"},{"role":"assistant","content":"`PWN::Reports::SAST.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1072
- {"messages":[{"role":"user","content":"PWN::Reports::SAST.generate Usage"},{"role":"assistant","content":"`PWN::Reports::SAST.generate`: Supported Method Parameters\n\nPWN::Reports::SAST.generate(\n\ndir_path: 'optional - Directory path to save the report (defaults to .)',\nresults_hash: 'optional - Hash containing the results of the SAST analysis (defaults to empty hash structure)',\nreport_name: 'optional - Name of the report file (defaults to current directory name)',\nai_engine: 'optional - AI engine to use for analysis (:grok, :ollama, or :openai)',\nai_model: 'optionnal - AI Model to Use for Respective AI Engine (e.g., grok-4i-0709, chargpt-4o-latest, llama-3.1, etc.)',\nai_key: 'optional - AI Key/Token for Respective AI Engine',\nai_fqdn: 'optional - AI FQDN (Only Required for \"ollama\" AI Engine)',\nai_system_role_content: 'optional - AI System Role Content (Defaults to \"Is this code vulnerable or a false positive? Valid responses are only: \"VULNERABLE\" or \"FALSE+\". DO NOT PROVIDE ANY OTHER TEXT OR EXPLANATIONS.\")',\nai_temp: 'optional - AI Temperature (Defaults to 0.9)'\n\n)\n"}]}
1072
+ {"messages":[{"role":"user","content":"PWN::Reports::SAST.generate Usage"},{"role":"assistant","content":"`PWN::Reports::SAST.generate`: Supported Method Parameters\n\nPWN::Reports::SAST.generate(\n\ndir_path: 'optional - Directory path to save the report (defaults to .)',\nresults_hash: 'optional - Hash containing the results of the SAST analysis (defaults to empty hash structure)',\nreport_name: 'optional - Name of the report file (defaults to current directory name)',\nai_engine: 'optional - AI engine to use for analysis (:grok, :ollama, or :openai)',\nai_model: 'optionnal - AI Model to Use for Respective AI Engine (e.g., grok-4i-0709, chargpt-4o-latest, llama-3.1, etc.)',\nai_key: 'optional - AI Key/Token for Respective AI Engine',\nai_fqdn: 'optional - AI FQDN (Only Required for \"ollama\" AI Engine)',\nai_system_role_content: 'optional - AI System Role Content (Defaults to \"Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.\")',\nai_temp: 'optional - AI Temperature (Defaults to 0.9)'\n\n)\n"}]}
1073
1073
  {"messages":[{"role":"user","content":"PWN::Reports::SAST.help Usage"},{"role":"assistant","content":"`PWN::Reports::SAST.help`: "}]}
1074
1074
  {"messages":[{"role":"user","content":"PWN::Reports::URIBuster.authors Usage"},{"role":"assistant","content":"`PWN::Reports::URIBuster.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1075
1075
  {"messages":[{"role":"user","content":"PWN::Reports::URIBuster.generate Usage"},{"role":"assistant","content":"`PWN::Reports::URIBuster.generate`: Supported Method Parameters\n\nPWN::Reports::URIBuster.generate(\n\ndir_path: dir_path,\nresults_hash: results_hash\n\n)\n"}]}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.418
4
+ version: 0.5.420
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.0.2.1
18
+ version: 8.0.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 8.0.2.1
25
+ version: 8.0.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: anemone
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -309,14 +309,14 @@ dependencies:
309
309
  requirements:
310
310
  - - '='
311
311
  - !ruby/object:Gem::Version
312
- version: 1.8.5
312
+ version: 1.8.6
313
313
  type: :runtime
314
314
  prerelease: false
315
315
  version_requirements: !ruby/object:Gem::Requirement
316
316
  requirements:
317
317
  - - '='
318
318
  - !ruby/object:Gem::Version
319
- version: 1.8.5
319
+ version: 1.8.6
320
320
  - !ruby/object:Gem::Dependency
321
321
  name: gist
322
322
  requirement: !ruby/object:Gem::Requirement
@@ -407,14 +407,14 @@ dependencies:
407
407
  requirements:
408
408
  - - '='
409
409
  - !ruby/object:Gem::Version
410
- version: 2.14.1
410
+ version: 2.15.0
411
411
  type: :runtime
412
412
  prerelease: false
413
413
  version_requirements: !ruby/object:Gem::Requirement
414
414
  requirements:
415
415
  - - '='
416
416
  - !ruby/object:Gem::Version
417
- version: 2.14.1
417
+ version: 2.15.0
418
418
  - !ruby/object:Gem::Dependency
419
419
  name: jsonpath
420
420
  requirement: !ruby/object:Gem::Requirement
@@ -505,14 +505,14 @@ dependencies:
505
505
  requirements:
506
506
  - - '='
507
507
  - !ruby/object:Gem::Version
508
- version: 0.0.125
508
+ version: 0.0.126
509
509
  type: :runtime
510
510
  prerelease: false
511
511
  version_requirements: !ruby/object:Gem::Requirement
512
512
  requirements:
513
513
  - - '='
514
514
  - !ruby/object:Gem::Version
515
- version: 0.0.125
515
+ version: 0.0.126
516
516
  - !ruby/object:Gem::Dependency
517
517
  name: metasm
518
518
  requirement: !ruby/object:Gem::Requirement
@@ -1065,14 +1065,14 @@ dependencies:
1065
1065
  requirements:
1066
1066
  - - '='
1067
1067
  - !ruby/object:Gem::Version
1068
- version: 2.7.0
1068
+ version: 3.0.0
1069
1069
  type: :runtime
1070
1070
  prerelease: false
1071
1071
  version_requirements: !ruby/object:Gem::Requirement
1072
1072
  requirements:
1073
1073
  - - '='
1074
1074
  - !ruby/object:Gem::Version
1075
- version: 2.7.0
1075
+ version: 3.0.0
1076
1076
  - !ruby/object:Gem::Dependency
1077
1077
  name: socksify
1078
1078
  requirement: !ruby/object:Gem::Requirement
@@ -1107,14 +1107,14 @@ dependencies:
1107
1107
  requirements:
1108
1108
  - - '='
1109
1109
  - !ruby/object:Gem::Version
1110
- version: 2.7.3
1110
+ version: 2.7.4
1111
1111
  type: :runtime
1112
1112
  prerelease: false
1113
1113
  version_requirements: !ruby/object:Gem::Requirement
1114
1114
  requirements:
1115
1115
  - - '='
1116
1116
  - !ruby/object:Gem::Version
1117
- version: 2.7.3
1117
+ version: 2.7.4
1118
1118
  - !ruby/object:Gem::Dependency
1119
1119
  name: thin
1120
1120
  requirement: !ruby/object:Gem::Requirement