wpscan 3.5.5 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
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