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 +4 -4
- data/app/controllers/enumeration.rb +0 -9
- data/app/controllers/enumeration/cli_options.rb +10 -2
- data/app/controllers/enumeration/enum_methods.rb +2 -0
- data/app/finders/plugin_version.rb +5 -15
- data/app/finders/plugin_version/readme.rb +1 -1
- data/app/finders/plugins/known_locations.rb +2 -0
- data/app/finders/theme_version.rb +5 -15
- data/app/finders/themes/known_locations.rb +2 -0
- data/app/models/plugin.rb +5 -0
- data/app/models/wp_item.rb +6 -1
- data/lib/wpscan/db/dynamic_finders/base.rb +6 -5
- data/lib/wpscan/db/dynamic_finders/plugin.rb +30 -20
- data/lib/wpscan/db/dynamic_finders/theme.rb +2 -2
- data/lib/wpscan/db/dynamic_finders/wordpress.rb +5 -5
- data/lib/wpscan/errors.rb +1 -0
- data/lib/wpscan/errors/enumeration.rb +21 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +8 -9
- data/lib/wpscan/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6457e8ee0fcab7affd1a038bf6b4414c16f4795df9877e3485448672555a06c5
|
4
|
+
data.tar.gz: 8a589b71ab6ba518e03083cb81486f75295f4b84c1de049126efb5601d2885d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
16
|
+
create_and_load_dynamic_versions_finders(plugin)
|
17
17
|
end
|
18
18
|
|
19
|
-
#
|
19
|
+
# Create the dynamic version finders related to the plugin and register them
|
20
20
|
#
|
21
21
|
# @param [ Model::Plugin ] plugin
|
22
|
-
def
|
23
|
-
|
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
|
-
|
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
|
-
|
19
|
+
create_and_load_dynamic_versions_finders(theme)
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
22
|
+
# Create the dynamic version finders related to the theme and register them
|
23
23
|
#
|
24
24
|
# @param [ Model::Theme ] theme
|
25
|
-
def
|
26
|
-
|
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
|
data/app/models/plugin.rb
CHANGED
data/app/models/wp_item.rb
CHANGED
@@ -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
|
-
|
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.
|
9
|
-
@
|
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.
|
14
|
-
|
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.
|
9
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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 Wordpress < Base
|
7
7
|
# @return [ Hash ]
|
8
|
-
def self.
|
9
|
-
@
|
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
|
-
|
33
|
+
df_data.reject { |_f, c| c['path'].nil? }
|
34
34
|
else
|
35
|
-
|
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 ||=
|
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
|
data/lib/wpscan/errors.rb
CHANGED
@@ -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
|
-
|
109
|
-
|
110
|
-
end
|
107
|
+
# url_pattern is from CMSScanner::Target
|
108
|
+
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
111
109
|
|
112
|
-
|
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
|
data/lib/wpscan/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|