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 +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
|