wpscan 3.5.5 → 3.6.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: 7598b5dd35df74f49ca06db60fc8c40b636c9539e8b1543d5d649052520a48d3
4
- data.tar.gz: a50e8653b39a843f2e9b02cb9506d1249b2a34c5d469aaa25e4774b7133ce1a6
3
+ metadata.gz: 6457e8ee0fcab7affd1a038bf6b4414c16f4795df9877e3485448672555a06c5
4
+ data.tar.gz: 8a589b71ab6ba518e03083cb81486f75295f4b84c1de049126efb5601d2885d7
5
5
  SHA512:
6
- metadata.gz: 6ed1bdc24f4ab7147a0c558564f7ce9d32e0310dcd6d44590f9e2e2c936dd0c5c0932b9a0bd82b12460ce5027cdbe81aaa90e9069d904c534b76e78c771b09da
7
- data.tar.gz: b0f0ca51823c56afbed135324657602dc137f7089f34555df5977aa3ec3b0406544cde065a5ee2d1c58a36a776acb134b7760f8f5f5304e39a2b187391d3c680
6
+ metadata.gz: 97aa688074b2226d393c4e21134c7f36e4fbe392a70ba88f354d893cf7625ac2c27cc6d3b5a94fa6fd185961aa4388a941ed999ebbf107768f925f5b73e37b11
7
+ data.tar.gz: d720860b215fbc38b1abce7814921127f71f360f9ef2f67ce5e85168f4113b9eba041d8dd43761b3809062e1c2036f50b7e3938189f14d1d754a67f69ac53758
@@ -7,15 +7,6 @@ module WPScan
7
7
  module Controller
8
8
  # Enumeration Controller
9
9
  class Enumeration < CMSScanner::Controller::Base
10
- def before_scan
11
- DB::DynamicFinders::Plugin.create_versions_finders
12
- DB::DynamicFinders::Theme.create_versions_finders
13
-
14
- # Force the Garbage Collector to run due to the above method being
15
- # quite heavy in objects allocation
16
- GC.start
17
- end
18
-
19
10
  def run
20
11
  enum = ParsedCli.enumerate || {}
21
12
 
@@ -11,7 +11,6 @@ module WPScan
11
11
  end
12
12
 
13
13
  # @return [ Array<OptParseValidator::OptBase> ]
14
- # rubocop:disable Metrics/MethodLength
15
14
  def cli_enum_choices
16
15
  [
17
16
  OptMultiChoices.new(
@@ -45,7 +44,6 @@ module WPScan
45
44
  )
46
45
  ]
47
46
  end
48
- # rubocop:enable Metrics/MethodLength
49
47
 
50
48
  # @return [ Array<OptParseValidator::OptBase> ]
51
49
  def cli_plugins_opts
@@ -67,6 +65,11 @@ module WPScan
67
65
  'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
68
66
  'or --plugins-detection modes.'],
69
67
  choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
68
+ ),
69
+ OptInteger.new(
70
+ ['--plugins-threshold THRESHOLD',
71
+ 'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
72
+ 'Set to 0 to ignore the threshold.'], default: 100
70
73
  )
71
74
  ]
72
75
  end
@@ -91,6 +94,11 @@ module WPScan
91
94
  'Use the supplied mode to check themes versions instead of the --detection-mode ' \
92
95
  'or --themes-detection modes.'],
93
96
  choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
97
+ ),
98
+ OptInteger.new(
99
+ ['--themes-threshold THRESHOLD',
100
+ 'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
101
+ 'Set to 0 to ignore the threshold.'], default: 20
94
102
  )
95
103
  ]
96
104
  end
@@ -62,6 +62,7 @@ module WPScan
62
62
  def enum_plugins
63
63
  opts = default_opts('plugins').merge(
64
64
  list: plugins_list_from_opts(ParsedCli.options),
65
+ threshold: ParsedCli.plugins_threshold,
65
66
  sort: true
66
67
  )
67
68
 
@@ -108,6 +109,7 @@ module WPScan
108
109
  def enum_themes
109
110
  opts = default_opts('themes').merge(
110
111
  list: themes_list_from_opts(ParsedCli.options),
112
+ threshold: ParsedCli.themes_threshold,
111
113
  sort: true
112
114
  )
113
115
 
@@ -13,25 +13,15 @@ module WPScan
13
13
  def initialize(plugin)
14
14
  finders << PluginVersion::Readme.new(plugin)
15
15
 
16
- load_specific_finders(plugin)
16
+ create_and_load_dynamic_versions_finders(plugin)
17
17
  end
18
18
 
19
- # Load the finders associated with the plugin
19
+ # Create the dynamic version finders related to the plugin and register them
20
20
  #
21
21
  # @param [ Model::Plugin ] plugin
22
- def load_specific_finders(plugin)
23
- module_name = plugin.classify
24
-
25
- return unless Finders::PluginVersion.constants.include?(module_name)
26
-
27
- mod = Finders::PluginVersion.const_get(module_name)
28
-
29
- mod.constants.each do |constant|
30
- c = mod.const_get(constant)
31
-
32
- next unless c.is_a?(Class)
33
-
34
- finders << c.new(plugin)
22
+ def create_and_load_dynamic_versions_finders(plugin)
23
+ DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
24
+ finders << finder.new(plugin)
35
25
  end
36
26
  end
37
27
  end
@@ -11,7 +11,7 @@ module WPScan
11
11
 
12
12
  # The target(plugin)#readme_url can't be used directly here
13
13
  # as if the --detection-mode is passive, it will always return nil
14
- Model::WpItem::READMES.each do |file|
14
+ target.potential_readme_filenames.each do |file|
15
15
  res = target.head_and_get(file)
16
16
 
17
17
  next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
@@ -21,6 +21,8 @@ module WPScan
21
21
 
22
22
  enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
23
23
  found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
24
+
25
+ raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
24
26
  end
25
27
 
26
28
  found
@@ -16,25 +16,15 @@ module WPScan
16
16
  ThemeVersion::Style.new(theme) <<
17
17
  ThemeVersion::WooFrameworkMetaGenerator.new(theme)
18
18
 
19
- load_specific_finders(theme)
19
+ create_and_load_dynamic_versions_finders(theme)
20
20
  end
21
21
 
22
- # Load the finders associated with the theme
22
+ # Create the dynamic version finders related to the theme and register them
23
23
  #
24
24
  # @param [ Model::Theme ] theme
25
- def load_specific_finders(theme)
26
- module_name = theme.classify
27
-
28
- return unless Finders::ThemeVersion.constants.include?(module_name)
29
-
30
- mod = Finders::ThemeVersion.const_get(module_name)
31
-
32
- mod.constants.each do |constant|
33
- c = mod.const_get(constant)
34
-
35
- next unless c.is_a?(Class)
36
-
37
- finders << c.new(theme)
25
+ def create_and_load_dynamic_versions_finders(theme)
26
+ DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
27
+ finders << finder.new(theme)
38
28
  end
39
29
  end
40
30
  end
@@ -21,6 +21,8 @@ module WPScan
21
21
 
22
22
  enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
23
23
  found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
24
+
25
+ raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
24
26
  end
25
27
 
26
28
  found
@@ -28,6 +28,11 @@ module WPScan
28
28
 
29
29
  @version
30
30
  end
31
+
32
+ # @return [ Array<String> ]
33
+ def potential_readme_filenames
34
+ @potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
35
+ end
31
36
  end
32
37
  end
33
38
  end
@@ -9,6 +9,7 @@ module WPScan
9
9
  include CMSScanner::Target::Platform::PHP
10
10
  include CMSScanner::Target::Server::Generic
11
11
 
12
+ # Most common readme filenames, based on checking all public plugins and themes.
12
13
  READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
13
14
 
14
15
  attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
@@ -117,7 +118,7 @@ module WPScan
117
118
 
118
119
  return @readme_url unless @readme_url.nil?
119
120
 
120
- READMES.each do |path|
121
+ potential_readme_filenames.each do |path|
121
122
  t_url = url(path)
122
123
 
123
124
  return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
@@ -126,6 +127,10 @@ module WPScan
126
127
  @readme_url = false
127
128
  end
128
129
 
130
+ def potential_readme_filenames
131
+ @potential_readme_filenames ||= READMES
132
+ end
133
+
129
134
  # @param [ String ] path
130
135
  # @param [ Hash ] params The request params
131
136
  #
@@ -5,18 +5,19 @@ module WPScan
5
5
  module DynamicFinders
6
6
  class Base
7
7
  # @return [ String ]
8
- def self.db_file
9
- @db_file ||= DB_DIR.join('dynamic_finders.yml').to_s
8
+ def self.df_file
9
+ @df_file ||= DB_DIR.join('dynamic_finders.yml').to_s
10
10
  end
11
11
 
12
12
  # @return [ Hash ]
13
- def self.db_data
14
- # true allows aliases to be loaded
15
- @db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
13
+ def self.all_df_data
14
+ @all_df_data ||= YAML.safe_load(File.read(df_file), [Regexp])
16
15
  end
17
16
 
18
17
  # @return [ Array<Symbol> ]
19
18
  def self.allowed_classes
19
+ # The Readme is not put in there as it's not a Real DF, but rather using the DF system
20
+ # to get the list of potential filenames for a given slug
20
21
  @allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
21
22
  end
22
23
 
@@ -5,8 +5,8 @@ module WPScan
5
5
  module DynamicFinders
6
6
  class Plugin < Base
7
7
  # @return [ Hash ]
8
- def self.db_data
9
- @db_data ||= super['plugins'] || {}
8
+ def self.df_data
9
+ @df_data ||= all_df_data['plugins'] || {}
10
10
  end
11
11
 
12
12
  def self.version_finder_module
@@ -21,7 +21,7 @@ module WPScan
21
21
 
22
22
  return configs unless allowed_classes.include?(finder_class)
23
23
 
24
- db_data.each do |slug, finders|
24
+ df_data.each do |slug, finders|
25
25
  # Quite sure better can be done with some kind of logic statement in the select
26
26
  fs = if aggressive
27
27
  finders.reject { |_f, c| c['path'].nil? }
@@ -48,7 +48,7 @@ module WPScan
48
48
 
49
49
  @versions_finders_configs = {}
50
50
 
51
- db_data.each do |slug, finders|
51
+ df_data.each do |slug, finders|
52
52
  finders.each do |finder_name, config|
53
53
  next unless config.key?('version')
54
54
 
@@ -73,23 +73,33 @@ module WPScan
73
73
  version_finder_module.const_get(constant_name)
74
74
  end
75
75
 
76
- def self.create_versions_finders
77
- versions_finders_configs.each do |slug, finders|
78
- mod = maybe_create_module(slug)
79
-
80
- finders.each do |finder_class, config|
81
- klass = config['class'] || finder_class
82
-
83
- # Instead of raising exceptions, skip unallowed/already defined finders
84
- # So that, when new DF configs are put in the .yml
85
- # users with old version of WPScan will still be able to scan blogs
86
- # when updating the DB but not the tool
87
- next if mod.constants.include?(finder_class.to_sym) ||
88
- !allowed_classes.include?(klass.to_sym)
89
-
90
- version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
91
- end
76
+ # Create the dynamic finders related to the given slug, and return the created classes
77
+ #
78
+ # @param [ String ] slug
79
+ #
80
+ # @return [ Array<Class> ] The created classes
81
+ def self.create_versions_finders(slug)
82
+ created = []
83
+ mod = maybe_create_module(slug)
84
+
85
+ versions_finders_configs[slug]&.each do |finder_class, config|
86
+ klass = config['class'] || finder_class
87
+
88
+ # Instead of raising exceptions, skip unallowed/already defined finders
89
+ # So that, when new DF configs are put in the .yml
90
+ # users with old version of WPScan will still be able to scan blogs
91
+ # when updating the DB but not the tool
92
+
93
+ next unless allowed_classes.include?(klass.to_sym)
94
+
95
+ created << if mod.constants.include?(finder_class.to_sym)
96
+ mod.const_get(finder_class.to_sym)
97
+ else
98
+ version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
99
+ end
92
100
  end
101
+
102
+ created
93
103
  end
94
104
 
95
105
  # The idea here would be to check if the class exist in
@@ -5,8 +5,8 @@ module WPScan
5
5
  module DynamicFinders
6
6
  class Theme < Plugin
7
7
  # @return [ Hash ]
8
- def self.db_data
9
- @db_data ||= super['themes'] || {}
8
+ def self.df_data
9
+ @df_data ||= all_df_data['themes'] || {}
10
10
  end
11
11
 
12
12
  def self.version_finder_module
@@ -5,8 +5,8 @@ module WPScan
5
5
  module DynamicFinders
6
6
  class Wordpress < Base
7
7
  # @return [ Hash ]
8
- def self.db_data
9
- @db_data ||= super['wordpress'] || {}
8
+ def self.df_data
9
+ @df_data ||= all_df_data['wordpress'] || {}
10
10
  end
11
11
 
12
12
  # @return [ Constant ]
@@ -30,9 +30,9 @@ module WPScan
30
30
  return configs unless allowed_classes.include?(finder_class)
31
31
 
32
32
  finders = if aggressive
33
- db_data.reject { |_f, c| c['path'].nil? }
33
+ df_data.reject { |_f, c| c['path'].nil? }
34
34
  else
35
- db_data.select { |_f, c| c['path'].nil? }
35
+ df_data.select { |_f, c| c['path'].nil? }
36
36
  end
37
37
 
38
38
  finders.each do |finder_name, config|
@@ -48,7 +48,7 @@ module WPScan
48
48
 
49
49
  # @return [ Hash ]
50
50
  def self.versions_finders_configs
51
- @versions_finders_configs ||= db_data.select { |_finder_name, config| config.key?('version') }
51
+ @versions_finders_configs ||= df_data.select { |_finder_name, config| config.key?('version') }
52
52
  end
53
53
 
54
54
  def self.create_versions_finders
@@ -9,6 +9,7 @@ module WPScan
9
9
  end
10
10
  end
11
11
 
12
+ require_relative 'errors/enumeration'
12
13
  require_relative 'errors/http'
13
14
  require_relative 'errors/update'
14
15
  require_relative 'errors/wordpress'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Error
5
+ class PluginsThresholdReached < Standard
6
+ def to_s
7
+ "The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
8
+ 'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
9
+ 'option to ignore the bad responses.'
10
+ end
11
+ end
12
+
13
+ class ThemesThresholdReached < Standard
14
+ def to_s
15
+ "The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
16
+ 'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
17
+ 'option to ignore the bad responses.'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -99,20 +99,19 @@ module WPScan
99
99
 
100
100
  # @return [ String, False ] String of the sub_dir found, false otherwise
101
101
  # @note: nil can not be returned here, otherwise if there is no sub_dir
102
- # the check would be done each time
102
+ # the check would be done each time, which would make enumeration of
103
+ # long list of items very slow to generate
103
104
  def sub_dir
104
- unless @sub_dir
105
- # url_pattern is from CMSScanner::Target
106
- pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
105
+ return @sub_dir unless @sub_dir.nil?
107
106
 
108
- in_scope_uris(homepage_res) do |uri|
109
- return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
110
- end
107
+ # url_pattern is from CMSScanner::Target
108
+ pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
111
109
 
112
- @sub_dir = false
110
+ in_scope_uris(homepage_res) do |uri|
111
+ return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
113
112
  end
114
113
 
115
- @sub_dir
114
+ @sub_dir = false
116
115
  end
117
116
 
118
117
  # Override of the WebSite#url to consider the custom WP directories
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version
4
4
  module WPScan
5
- VERSION = '3.5.5'
5
+ VERSION = '3.6.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wpscan
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.5
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WPScanTeam
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-04 00:00:00.000000000 Z
11
+ date: 2019-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cms_scanner
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.5.3
19
+ version: 0.5.4
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.5.3
26
+ version: 0.5.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -337,6 +337,7 @@ files:
337
337
  - lib/wpscan/db/wp_items.rb
338
338
  - lib/wpscan/db/wp_version.rb
339
339
  - lib/wpscan/errors.rb
340
+ - lib/wpscan/errors/enumeration.rb
340
341
  - lib/wpscan/errors/http.rb
341
342
  - lib/wpscan/errors/update.rb
342
343
  - lib/wpscan/errors/wordpress.rb