wpscan 4.1.0 → 4.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6215ce3d361d0754721dab264b279b43c0064cb2621a14720db0e15167bbc52
4
- data.tar.gz: 6c092df8d32bb9f5e0a750c2801fb7cd086c6f258f404023581dc61c194fb962
3
+ metadata.gz: b66209f9f28c78f30d0d121c6841ffe370dc0109e3dde221a1212187522a0ff1
4
+ data.tar.gz: 12c257dbfc4ec49bfb438cc1abceeebebfc313690251d0e9e059978b76ac940e
5
5
  SHA512:
6
- metadata.gz: b74ca0ee959b570d23d23d51936f121e31a16ef73ec896c5b1f6e97f503d6000c129cb3655fac0b6bca8072f7047c2bfad46711b77fb16f4f4cbdd9069a10311
7
- data.tar.gz: 3067ba3dc843c74014b1bbda40ca2be6549fc17924aa64456128a667f91d20957ef86c1d79b430767fc2cb9a4a4b1b9fbb3bfd1d3f92c5edf94bb6c993ae8b23
6
+ metadata.gz: 8623ccd870f308d9bf7f8ce19dc77c7281b50155ade1e73c14f82644c0e30085dc7aaf4f7ff61513f9b7d757bfb1c3b77dbde0999a1fc59a8d44f9cd2c2b1fb4
7
+ data.tar.gz: 8bc2e65b6320bc40f069495a8359c91bc3edb7ae91242e54df552eb77a5e73c76a4a3ddfdd677a07bed6cdee5df34a5f910835eeb1e13b3f27bee03fdd63eba6
@@ -87,7 +87,12 @@ module WPScan
87
87
  default: 30),
88
88
  OptBoolean.new(['--disable-tls-checks',
89
89
  'Disables SSL/TLS certificate verification, and downgrade to TLS1.0+ ' \
90
- '(requires cURL 7.66 for the latter)'])
90
+ '(requires cURL 7.66 for the latter)']),
91
+ OptPositiveInteger.new(['--max-response-size MiB',
92
+ 'Stop reading any response once it exceeds this size, in MiB. ' \
93
+ 'Guards against memory exhaustion on targets serving very large ' \
94
+ 'or endless responses. At most 2047, as a 2 GiB body cannot be parsed.'],
95
+ default: 16, max: 2047, advanced: true)
91
96
  ] + cli_browser_proxy_options + cli_browser_cookies_options + cli_browser_cache_options
92
97
  end
93
98
 
@@ -46,6 +46,9 @@ module WPScan
46
46
 
47
47
  if json.is_a?(Enumerable)
48
48
  json.each do |user|
49
+ # Some security plugins strip the slug from the API output
50
+ next if user['slug'].blank?
51
+
49
52
  found << Model::User.new(user['slug'],
50
53
  id: user['id'],
51
54
  found_by: found_by,
@@ -96,12 +96,17 @@ module WPScan
96
96
  def components(data)
97
97
  list = []
98
98
  list << core_component(data) if data['version']
99
- list << theme_component(data['main_theme']) if data['main_theme']
100
- (data['themes'] || {}).each_value { |theme| list << theme_component(theme) }
99
+ list.concat(theme_components(data))
101
100
  (data['plugins'] || {}).each { |slug, plugin| list << plugin_component(slug, plugin) }
102
101
  list
103
102
  end
104
103
 
104
+ def theme_components(data)
105
+ themes = [data['main_theme'], *(data['themes'] || {}).values].compact
106
+ themes.concat(themes.flat_map { |theme| Array(theme['parents']) })
107
+ themes.map { |theme| theme_component(theme) }.uniq
108
+ end
109
+
105
110
  def core_component(data)
106
111
  {
107
112
  kind: 'core',
@@ -285,7 +290,13 @@ module WPScan
285
290
  end
286
291
 
287
292
  def invocation(data)
288
- inv = { 'executionSuccessful' => true }
293
+ failure = data['scan_aborted'] || data['update_aborted']
294
+ inv = { 'executionSuccessful' => failure.nil? }
295
+ if failure
296
+ inv['toolExecutionNotifications'] = [
297
+ { 'level' => 'error', 'message' => { 'text' => failure.to_s } }
298
+ ]
299
+ end
289
300
  inv['commandLine'] = "wpscan #{data['command_line']}" if data['command_line']
290
301
  inv['hostname'] = data['hostname'] if data['hostname']
291
302
  inv['startTimeUtc'] = to_iso8601(data['start_time']) if data['start_time']
@@ -8,12 +8,11 @@ class OptionParser
8
8
  class << self
9
9
  alias original_candidate candidate
10
10
 
11
- # rubocop:disable Style/OptionalBooleanParameter
11
+ # rubocop:disable-next Style/OptionalBooleanParameter
12
12
  def candidate(key, icase = false, pat = nil, &)
13
13
  # Maybe also do this for -v/--version ?
14
14
  key == 'h' ? original_candidate('help', icase, pat, &) : []
15
15
  end
16
- # rubocop:enable Style/OptionalBooleanParameter
17
16
  end
18
17
  end
19
18
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module OptParseValidator
4
4
  # Implementation of the Positive Integer Option
5
+ # attrs[:max], when given, is the highest value accepted
5
6
  class OptPositiveInteger < OptInteger
6
7
  # @param [ String ] value
7
8
  #
@@ -9,6 +10,7 @@ module OptParseValidator
9
10
  def validate(value)
10
11
  i = super
11
12
  raise Error, "#{i} is not > 0" unless i.positive?
13
+ raise Error, "#{i} is not <= #{attrs[:max]}" if attrs[:max] && i > attrs[:max]
12
14
 
13
15
  i
14
16
  end
@@ -11,6 +11,7 @@ module WPScan
11
11
  disable_tls_checks
12
12
  headers
13
13
  http_auth
14
+ max_response_size
14
15
  max_threads
15
16
  proxy
16
17
  proxy_auth
@@ -37,7 +37,12 @@ module WPScan
37
37
  #
38
38
  # @return [ Typhoeus::Request ]
39
39
  def forge_request(url, params = {})
40
- Typhoeus::Request.new(url, request_params(params))
40
+ request = Typhoeus::Request.new(url, request_params(params))
41
+ # Detection reads the head of a document rather than its tail, so the cap is well above
42
+ # anything a finder needs. See WPScan::ResponseSizeCap
43
+ request.max_response_size = max_response_size.to_i * 1024 * 1024
44
+
45
+ request
41
46
  end
42
47
 
43
48
  # @return [ Hash ] The request params used to connect to the target as well as other systems (e.g. API).
@@ -46,6 +46,14 @@ module WPScan
46
46
 
47
47
  File.write(entry_path(key), serializer.dump(data_to_store))
48
48
  File.write(entry_expiration_path(key), Time.now.to_i + cache_ttl)
49
+ rescue TypeError => e
50
+ # Marshal raises TypeError("long too big to dump") for a String of 2GiB or more, which a
51
+ # target can produce with a single chunked response. Such an entry is not cacheable, but
52
+ # failing to cache it is not a reason to abort the scan. Any other TypeError (e.g. an
53
+ # object Marshal cannot dump) is a bug and must surface.
54
+ raise unless e.message.include?('too big to dump')
55
+
56
+ nil
49
57
  end
50
58
 
51
59
  # @param [ String ] key
@@ -72,9 +72,8 @@ module WPScan
72
72
  end
73
73
  ensure
74
74
  # The rescue prevents unfinished requests from raising, which would stop reverse_each from running.
75
- # rubocop:disable Style/RescueModifier
75
+ # rubocop:disable-next Style/RescueModifier
76
76
  WPScan::Browser.instance.hydra.abort rescue nil
77
- # rubocop:enable Style/RescueModifier
78
77
 
79
78
  # Reverse order: app/controllers/core#after_scan finishes the output and must be last.
80
79
  # Guarantees stats are output even on error. after_scan runs only if scan was actually running
@@ -25,12 +25,11 @@ module WPScan
25
25
  end
26
26
 
27
27
  # Unknown methods return nil — expected behaviour for option lookups.
28
- # rubocop:disable Style/MissingRespondToMissing
28
+ # rubocop:disable-next Style/MissingRespondToMissing
29
29
  def self.method_missing(method_name, *_args, &)
30
30
  super if method_name == :new
31
31
 
32
32
  options[method_name.to_sym]
33
33
  end
34
- # rubocop:enable Style/MissingRespondToMissing
35
34
  end
36
35
  end
@@ -10,7 +10,7 @@ module PublicSuffix
10
10
 
11
11
  # @return [ Boolean ]
12
12
  #
13
- # rubocop:disable Naming/PredicateMethod
13
+ # rubocop:disable-next Naming/PredicateMethod
14
14
  def match(pattern)
15
15
  pattern = PublicSuffix.parse(pattern) unless pattern.is_a?(PublicSuffix::Domain)
16
16
 
@@ -19,7 +19,6 @@ module PublicSuffix
19
19
 
20
20
  matching_pattern?(pattern)
21
21
  end
22
- # rubocop:enable Naming/PredicateMethod
23
22
 
24
23
  protected
25
24
 
@@ -46,7 +46,7 @@ module WPScan
46
46
  #
47
47
  # @return [ Regexp ] The pattern related to the target url and in scope domains,
48
48
  # it also matches escaped /, such as in JSON JS data: http:\/\/t.com\/
49
- # rubocop:disable Metrics/AbcSize
49
+ # rubocop:disable-next Metrics/AbcSize
50
50
  def scope_url_pattern
51
51
  return @scope_url_pattern if @scope_url_pattern
52
52
 
@@ -64,7 +64,6 @@ module WPScan
64
64
 
65
65
  @scope_url_pattern = %r{https?:\\?/\\?/(?:#{domains.join('|')})\\?/?}i
66
66
  end
67
- # rubocop:enable Metrics/AbcSize
68
67
 
69
68
  # Scope Implementation
70
69
  class Scope
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ # Bounds how much of a response is kept in memory, for requests carrying a #max_response_size.
5
+ #
6
+ # libcurl's maxfilesize is not enough on its own: it acts on the advertised Content-Length, so it
7
+ # is a no-op against the chunked responses a dynamic site serves. Only an on_body callback can
8
+ # stop a transfer by how much has actually arrived.
9
+ #
10
+ # The callback is installed on the Ethon easy rather than through Typhoeus::Request#on_body: the
11
+ # latter puts the request in streaming mode, where Response#body is left empty, and Typhoeus
12
+ # caches the response before any on_complete could put the body back. Returning :unyielded
13
+ # tells Ethon to accumulate the chunk as it would with no callback, so the body is intact when
14
+ # the response is cached, just shorter.
15
+ module ResponseSizeCap
16
+ # Adds the per request cap, in bytes. nil or 0 disables it.
17
+ module Request
18
+ attr_accessor :max_response_size
19
+
20
+ # A capped response may be truncated, so it must not be served from the cache to a request
21
+ # allowing a larger one. Uncapped requests keep the stock key.
22
+ def cache_key
23
+ max_size = max_response_size.to_i
24
+
25
+ max_size.zero? ? super : "#{super}-max_response_size:#{max_size}"
26
+ end
27
+ end
28
+
29
+ # Installs the cap when the easy is built, i.e. after any on_body a caller has added.
30
+ module EasyFactory
31
+ def set_callback
32
+ super
33
+
34
+ max_size = request.max_response_size.to_i
35
+ # A caller doing its own streaming (e.g. Target#log_file?) has its own handling
36
+ return if max_size.zero? || request.streaming?
37
+
38
+ easy.on_body do |_chunk, easy_handle|
39
+ easy_handle.response_body.bytesize >= max_size ? :abort : :unyielded
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Typhoeus::Request.prepend(WPScan::ResponseSizeCap::Request)
47
+ Typhoeus::EasyFactory.prepend(WPScan::ResponseSizeCap::EasyFactory)
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version
4
4
  module WPScan
5
- VERSION = '4.1.0'
5
+ VERSION = '4.2.0'
6
6
  end
data/lib/wpscan.rb CHANGED
@@ -27,6 +27,7 @@ require 'xmlrpc/client'
27
27
  # Monkey Patches/Fixes
28
28
  require 'wpscan/typhoeus/response' # Adds Response#html and from_vuln_api?
29
29
  require 'wpscan/typhoeus/hydra' # https://github.com/typhoeus/typhoeus/issues/439
30
+ require 'wpscan/typhoeus/response_size_cap' # Adds Request#max_response_size
30
31
  require 'wpscan/public_suffix/domain' # Adds Domain#match
31
32
  require 'wpscan/numeric' # Adds Numeric#bytes_to_human
32
33
  # Custom Libs
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wpscan
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WPScanTeam
@@ -323,14 +323,14 @@ dependencies:
323
323
  requirements:
324
324
  - - "~>"
325
325
  - !ruby/object:Gem::Version
326
- version: '1.82'
326
+ version: 1.92.0
327
327
  type: :development
328
328
  prerelease: false
329
329
  version_requirements: !ruby/object:Gem::Requirement
330
330
  requirements:
331
331
  - - "~>"
332
332
  - !ruby/object:Gem::Version
333
- version: '1.82'
333
+ version: 1.92.0
334
334
  - !ruby/object:Gem::Dependency
335
335
  name: rubocop-performance
336
336
  requirement: !ruby/object:Gem::Requirement
@@ -351,14 +351,14 @@ dependencies:
351
351
  requirements:
352
352
  - - "~>"
353
353
  - !ruby/object:Gem::Version
354
- version: 1.0.0
354
+ version: 1.3.0
355
355
  type: :development
356
356
  prerelease: false
357
357
  version_requirements: !ruby/object:Gem::Requirement
358
358
  requirements:
359
359
  - - "~>"
360
360
  - !ruby/object:Gem::Version
361
- version: 1.0.0
361
+ version: 1.3.0
362
362
  - !ruby/object:Gem::Dependency
363
363
  name: simplecov-lcov
364
364
  requirement: !ruby/object:Gem::Requirement
@@ -393,14 +393,14 @@ dependencies:
393
393
  requirements:
394
394
  - - "~>"
395
395
  - !ruby/object:Gem::Version
396
- version: 3.26.2
396
+ version: 3.34.0
397
397
  type: :development
398
398
  prerelease: false
399
399
  version_requirements: !ruby/object:Gem::Requirement
400
400
  requirements:
401
401
  - - "~>"
402
402
  - !ruby/object:Gem::Version
403
- version: 3.26.2
403
+ version: 3.34.0
404
404
  description: WPScan is a black box WordPress vulnerability scanner.
405
405
  email:
406
406
  - contact@wpscan.com
@@ -717,6 +717,7 @@ files:
717
717
  - lib/wpscan/target/server/nginx.rb
718
718
  - lib/wpscan/typhoeus/hydra.rb
719
719
  - lib/wpscan/typhoeus/response.rb
720
+ - lib/wpscan/typhoeus/response_size_cap.rb
720
721
  - lib/wpscan/version.rb
721
722
  - lib/wpscan/vulnerability.rb
722
723
  - lib/wpscan/vulnerability_filter.rb