wpscan 3.8.28 → 4.0.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/README.md +104 -30
- data/app/app.rb +26 -0
- data/app/controllers/aliases.rb +2 -2
- data/app/controllers/authenticated_inventory.rb +43 -0
- data/app/controllers/core/cli_options.rb +151 -0
- data/app/controllers/core.rb +200 -25
- data/app/controllers/custom_directories.rb +1 -1
- data/app/controllers/enumeration/cli_options.rb +21 -31
- data/app/controllers/enumeration/enum_methods.rb +145 -38
- data/app/controllers/enumeration.rb +26 -3
- data/app/controllers/interesting_findings.rb +25 -0
- data/app/controllers/main_theme.rb +1 -1
- data/app/controllers/password_attack.rb +14 -6
- data/app/controllers/vuln_api.rb +9 -3
- data/app/controllers/wp_version.rb +1 -1
- data/app/controllers.rb +1 -0
- data/app/finders/backup_folders/known_locations.rb +66 -0
- data/app/finders/backup_folders.rb +19 -0
- data/app/finders/config_backups/known_filenames.rb +6 -4
- data/app/finders/config_backups.rb +1 -1
- data/app/finders/db_exports/known_locations.rb +16 -14
- data/app/finders/db_exports.rb +1 -1
- data/app/finders/interesting_findings/backup_db.rb +1 -1
- data/app/finders/interesting_findings/debug_log.rb +1 -1
- data/app/finders/interesting_findings/duplicator_installer_log.rb +1 -1
- data/app/finders/interesting_findings/emergency_pwd_reset_script.rb +1 -1
- data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
- data/app/finders/interesting_findings/full_path_disclosure.rb +1 -1
- data/app/finders/interesting_findings/headers.rb +17 -0
- data/app/finders/interesting_findings/mu_plugins.rb +1 -1
- data/app/finders/interesting_findings/multisite.rb +1 -1
- data/app/finders/interesting_findings/php_disabled.rb +2 -2
- data/app/finders/interesting_findings/readme.rb +1 -1
- data/app/finders/interesting_findings/registration.rb +1 -1
- data/app/finders/interesting_findings/robots_txt.rb +20 -0
- data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
- data/app/finders/interesting_findings/tmm_db_migrate.rb +1 -1
- data/app/finders/interesting_findings/upload_directory_listing.rb +1 -1
- data/app/finders/interesting_findings/upload_sql_dump.rb +2 -2
- data/app/finders/interesting_findings/wp_cron.rb +1 -1
- data/app/finders/interesting_findings/xml_rpc.rb +61 -0
- data/app/finders/interesting_findings.rb +13 -4
- data/app/finders/main_theme/css_style_in_homepage.rb +1 -1
- data/app/finders/main_theme/urls_in_homepage.rb +3 -7
- data/app/finders/main_theme/woo_framework_meta_generator.rb +4 -4
- data/app/finders/main_theme.rb +1 -1
- data/app/finders/medias/attachment_brute_forcing.rb +2 -2
- data/app/finders/medias.rb +1 -1
- data/app/finders/passwords/wp_login.rb +2 -2
- data/app/finders/passwords/xml_rpc.rb +2 -2
- data/app/finders/passwords/xml_rpc_multicall.rb +1 -1
- data/app/finders/plugin_version/readme.rb +1 -1
- data/app/finders/plugin_version.rb +1 -1
- data/app/finders/plugins/known_locations.rb +17 -7
- data/app/finders/plugins/urls_in_homepage.rb +3 -7
- data/app/finders/plugins/wp_json_api.rb +85 -0
- data/app/finders/plugins.rb +2 -1
- data/app/finders/theme_version/style.rb +1 -1
- data/app/finders/theme_version/woo_framework_meta_generator.rb +1 -1
- data/app/finders/theme_version.rb +1 -1
- data/app/finders/themes/known_locations.rb +12 -6
- data/app/finders/themes/urls_in_homepage.rb +3 -7
- data/app/finders/themes/wp_json_api.rb +74 -0
- data/app/finders/themes.rb +2 -1
- data/app/finders/timthumb_version/bad_request.rb +1 -1
- data/app/finders/timthumb_version.rb +1 -1
- data/app/finders/timthumbs/known_locations.rb +6 -4
- data/app/finders/timthumbs.rb +1 -1
- data/app/finders/users/author_id_brute_forcing.rb +11 -7
- data/app/finders/users/author_posts.rb +1 -1
- data/app/finders/users/author_sitemap.rb +1 -1
- data/app/finders/users/login_error_messages.rb +1 -1
- data/app/finders/users/oembed_api.rb +3 -1
- data/app/finders/users/wp_json_api.rb +11 -7
- data/app/finders/users.rb +1 -1
- data/app/finders/wp_version/atom_generator.rb +1 -1
- data/app/finders/wp_version/rdf_generator.rb +1 -1
- data/app/finders/wp_version/readme.rb +1 -1
- data/app/finders/wp_version/rss_generator.rb +1 -1
- data/app/finders/wp_version/unique_fingerprinting.rb +2 -2
- data/app/finders/wp_version.rb +1 -1
- data/app/finders.rb +1 -0
- data/app/formatters/cli.rb +79 -0
- data/app/formatters/cli_no_color.rb +9 -0
- data/app/formatters/cli_no_colour.rb +17 -0
- data/app/formatters/json.rb +14 -0
- data/app/formatters/jsonl.rb +29 -0
- data/app/formatters/sarif.rb +311 -0
- data/app/models/backup_folder.rb +39 -0
- data/app/models/fantastico_fileslist.rb +34 -0
- data/app/models/headers.rb +44 -0
- data/app/models/interesting_finding.rb +41 -2
- data/app/models/plugin.rb +8 -2
- data/app/models/robots_txt.rb +31 -0
- data/app/models/search_replace_db_2.rb +17 -0
- data/app/models/theme.rb +9 -2
- data/app/models/timthumb.rb +2 -2
- data/app/models/user.rb +35 -0
- data/app/models/version.rb +49 -0
- data/app/models/wp_item/wordpress_org_data.rb +55 -0
- data/app/models/wp_item.rb +109 -9
- data/app/models/wp_version.rb +2 -2
- data/app/models/xml_rpc.rb +73 -3
- data/app/models.rb +2 -1
- data/app/user_agents.txt +46 -0
- data/app/views/cli/core/banner.erb +3 -3
- data/app/views/cli/core/finished.erb +15 -0
- data/app/views/cli/core/help.erb +4 -0
- data/app/views/cli/core/started.erb +11 -0
- data/app/views/cli/enumeration/backup_folders.erb +11 -0
- data/app/views/cli/enumeration/plugin.erb +13 -0
- data/app/views/cli/enumeration/plugins.erb +1 -12
- data/app/views/cli/enumeration/theme.erb +4 -0
- data/app/views/cli/enumeration/themes.erb +1 -3
- data/app/views/cli/enumeration/user.erb +4 -0
- data/app/views/cli/enumeration/users.erb +1 -3
- data/app/views/cli/finding.erb +1 -1
- data/app/views/cli/interesting_findings/_array.erb +10 -0
- data/app/views/cli/interesting_findings/findings.erb +23 -0
- data/app/views/cli/scan_aborted.erb +5 -0
- data/app/views/cli/update_aborted.erb +5 -0
- data/app/views/cli/vuln_api/status.erb +2 -0
- data/app/views/cli/vulnerability.erb +6 -0
- data/app/views/cli/wp_item.erb +4 -1
- data/app/views/json/core/banner.erb +2 -8
- data/app/views/json/core/finished.erb +13 -0
- data/app/views/json/core/help.erb +4 -0
- data/app/views/json/core/started.erb +10 -0
- data/app/views/json/enumeration/backup_folders.erb +11 -0
- data/app/views/json/enumeration/plugin.erb +15 -0
- data/app/views/json/enumeration/theme.erb +5 -0
- data/app/views/json/enumeration/user.erb +6 -0
- data/app/views/json/finding.erb +8 -2
- data/app/views/json/interesting_findings/findings.erb +24 -0
- data/app/views/json/notice.erb +1 -0
- data/app/views/json/scan_aborted.erb +5 -0
- data/app/views/json/update_aborted.erb +5 -0
- data/app/views/json/vuln_api/status.erb +2 -0
- data/app/views/json/wp_item.erb +4 -1
- data/bin/wpscan +1 -0
- data/lib/opt_parse_validator/config_files_loader_merger/base.rb +26 -0
- data/lib/opt_parse_validator/config_files_loader_merger/json.rb +17 -0
- data/lib/opt_parse_validator/config_files_loader_merger/yml.rb +17 -0
- data/lib/opt_parse_validator/config_files_loader_merger.rb +62 -0
- data/lib/opt_parse_validator/errors.rb +9 -0
- data/lib/opt_parse_validator/hacks.rb +19 -0
- data/lib/opt_parse_validator/opts/alias.rb +28 -0
- data/lib/opt_parse_validator/opts/array.rb +34 -0
- data/lib/opt_parse_validator/opts/base.rb +142 -0
- data/lib/opt_parse_validator/opts/boolean.rb +19 -0
- data/lib/opt_parse_validator/opts/choice.rb +43 -0
- data/lib/opt_parse_validator/opts/credentials.rb +15 -0
- data/lib/opt_parse_validator/opts/directory_path.rb +17 -0
- data/lib/opt_parse_validator/opts/file_path.rb +34 -0
- data/lib/opt_parse_validator/opts/headers.rb +33 -0
- data/lib/opt_parse_validator/opts/integer.rb +15 -0
- data/lib/opt_parse_validator/opts/integer_range.rb +37 -0
- data/lib/opt_parse_validator/opts/multi_choices.rb +135 -0
- data/lib/opt_parse_validator/opts/path.rb +78 -0
- data/lib/opt_parse_validator/opts/positive_integer.rb +16 -0
- data/lib/opt_parse_validator/opts/proxy.rb +7 -0
- data/lib/opt_parse_validator/opts/regexp.rb +14 -0
- data/lib/opt_parse_validator/opts/smart_list.rb +30 -0
- data/lib/opt_parse_validator/opts/string.rb +8 -0
- data/lib/opt_parse_validator/opts/uri.rb +41 -0
- data/lib/opt_parse_validator/opts/url.rb +11 -0
- data/lib/opt_parse_validator/opts.rb +9 -0
- data/lib/opt_parse_validator/version.rb +6 -0
- data/lib/opt_parse_validator.rb +161 -0
- data/lib/wpscan/browser/actions.rb +48 -0
- data/lib/wpscan/browser/options.rb +92 -0
- data/lib/wpscan/browser.rb +87 -2
- data/lib/wpscan/browser_authenticator.rb +64 -0
- data/lib/wpscan/cache/file_store.rb +77 -0
- data/lib/wpscan/cache/typhoeus.rb +25 -0
- data/lib/wpscan/controller.rb +100 -4
- data/lib/wpscan/controllers.rb +78 -3
- data/lib/wpscan/db/dynamic_finders/base.rb +3 -7
- data/lib/wpscan/db/dynamic_finders/plugin.rb +2 -2
- data/lib/wpscan/db/dynamic_finders/wordpress.rb +1 -1
- data/lib/wpscan/db/fingerprints.rb +2 -2
- data/lib/wpscan/db/updater.rb +23 -13
- data/lib/wpscan/db/vuln_api.rb +19 -7
- data/lib/wpscan/db/wp_item.rb +2 -2
- data/lib/wpscan/errors/enumeration.rb +4 -4
- data/lib/wpscan/errors/http.rb +82 -3
- data/lib/wpscan/errors/saml.rb +28 -0
- data/lib/wpscan/errors/scan.rb +14 -0
- data/lib/wpscan/errors/update.rb +11 -3
- data/lib/wpscan/errors/vuln_api.rb +24 -0
- data/lib/wpscan/errors/wordpress.rb +2 -2
- data/lib/wpscan/errors/wp_auth.rb +37 -0
- data/lib/wpscan/errors.rb +4 -3
- data/lib/wpscan/exit_code.rb +25 -0
- data/lib/wpscan/finders/base_finders.rb +45 -0
- data/lib/wpscan/finders/dynamic_finder/finder.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/comment.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +3 -5
- data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +3 -3
- data/lib/wpscan/finders/dynamic_finder/wp_version.rb +1 -1
- data/lib/wpscan/finders/finder/breadth_first_dictionary_attack.rb +257 -0
- data/lib/wpscan/finders/finder/enumerator.rb +77 -0
- data/lib/wpscan/finders/finder/fingerprinter.rb +48 -0
- data/lib/wpscan/finders/finder/smart_url_checker/findings.rb +33 -0
- data/lib/wpscan/finders/finder/smart_url_checker.rb +60 -0
- data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +1 -1
- data/lib/wpscan/finders/finder.rb +78 -0
- data/lib/wpscan/finders/finding.rb +54 -0
- data/lib/wpscan/finders/findings.rb +33 -0
- data/lib/wpscan/finders/independent_finder.rb +33 -0
- data/lib/wpscan/finders/independent_finders.rb +26 -0
- data/lib/wpscan/finders/same_type_finder.rb +19 -0
- data/lib/wpscan/finders/same_type_finders.rb +28 -0
- data/lib/wpscan/finders/unique_finder.rb +19 -0
- data/lib/wpscan/finders/unique_finders.rb +47 -0
- data/lib/wpscan/finders.rb +11 -12
- data/lib/wpscan/formatter/buffer.rb +17 -0
- data/lib/wpscan/formatter.rb +152 -0
- data/lib/wpscan/helper.rb +7 -1
- data/lib/wpscan/http_status_tracking.rb +128 -0
- data/lib/wpscan/numeric.rb +13 -0
- data/lib/wpscan/parsed_cli.rb +31 -2
- data/lib/wpscan/progressbar_null_output.rb +23 -0
- data/lib/wpscan/public_suffix/domain.rb +44 -0
- data/lib/wpscan/references.rb +118 -4
- data/lib/wpscan/scan.rb +127 -0
- data/lib/wpscan/target/hashes.rb +45 -0
- data/lib/wpscan/target/platform/php.rb +124 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +3 -3
- data/lib/wpscan/target/platform/wordpress.rb +7 -8
- data/lib/wpscan/target/platform.rb +3 -0
- data/lib/wpscan/target/scope.rb +103 -0
- data/lib/wpscan/target/server/apache.rb +27 -0
- data/lib/wpscan/target/server/generic.rb +72 -0
- data/lib/wpscan/target/server/iis.rb +29 -0
- data/lib/wpscan/target/server/nginx.rb +27 -0
- data/lib/wpscan/target/server.rb +6 -0
- data/lib/wpscan/target.rb +129 -9
- data/lib/wpscan/typhoeus/hydra.rb +12 -0
- data/lib/wpscan/typhoeus/response.rb +24 -1
- data/lib/wpscan/version.rb +1 -1
- data/lib/wpscan/vulnerability.rb +49 -3
- data/lib/wpscan/vulnerability_filter.rb +68 -0
- data/lib/wpscan/vulnerable.rb +13 -1
- data/lib/wpscan/web_site.rb +152 -0
- data/lib/wpscan.rb +126 -29
- metadata +362 -20
data/lib/wpscan/db/updater.rb
CHANGED
|
@@ -8,7 +8,7 @@ module WPScan
|
|
|
8
8
|
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
|
9
9
|
FILES = %w[
|
|
10
10
|
metadata.json wp_fingerprints.json
|
|
11
|
-
timthumbs-v3.txt config_backups.txt db_exports.txt
|
|
11
|
+
timthumbs-v3.txt config_backups.txt db_exports.txt backup_folders.txt
|
|
12
12
|
dynamic_finders.yml LICENSE sponsor.txt
|
|
13
13
|
].freeze
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ module WPScan
|
|
|
22
22
|
def initialize(repo_directory)
|
|
23
23
|
@repo_directory = Pathname.new(repo_directory).expand_path
|
|
24
24
|
|
|
25
|
-
FileUtils.mkdir_p(repo_directory.to_s)
|
|
25
|
+
FileUtils.mkdir_p(repo_directory.to_s)
|
|
26
26
|
|
|
27
27
|
# When --no-update is passed, return to avoid raising an error if the directory is not writable
|
|
28
28
|
# Mainly there for Homebrew: https://github.com/wpscanteam/wpscan/pull/1455
|
|
@@ -73,13 +73,22 @@ module WPScan
|
|
|
73
73
|
# @return [ Hash ] The params for Typhoeus::Request
|
|
74
74
|
# @note Those params can't be overriden by CLI options
|
|
75
75
|
def request_params
|
|
76
|
-
@request_params ||=
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
@request_params ||= begin
|
|
77
|
+
params = Browser.instance.default_request_params.merge(
|
|
78
|
+
timeout: 600,
|
|
79
|
+
connecttimeout: 300,
|
|
80
|
+
accept_encoding: 'gzip, deflate',
|
|
81
|
+
cache_ttl: 0,
|
|
82
|
+
headers: { 'User-Agent' => Browser.instance.default_user_agent }
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if ParsedCli.proxy_target_only
|
|
86
|
+
params.delete(:proxy)
|
|
87
|
+
params.delete(:proxyuserpwd)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
params
|
|
91
|
+
end
|
|
83
92
|
end
|
|
84
93
|
|
|
85
94
|
# @return [ String ] The raw file URL associated with the given filename
|
|
@@ -127,7 +136,8 @@ module WPScan
|
|
|
127
136
|
FileUtils.rm(backup_file_path(filename))
|
|
128
137
|
end
|
|
129
138
|
|
|
130
|
-
# @return [ String ] The checksum of the downloaded file
|
|
139
|
+
# @return [ Array(String, String) ] The checksum of the downloaded file and the
|
|
140
|
+
# Cloudflare Ray ID of the response (or nil)
|
|
131
141
|
def download(filename)
|
|
132
142
|
file_path = local_file_path(filename)
|
|
133
143
|
file_url = remote_file_url(filename)
|
|
@@ -137,7 +147,7 @@ module WPScan
|
|
|
137
147
|
|
|
138
148
|
File.binwrite(file_path, res.body)
|
|
139
149
|
|
|
140
|
-
local_file_checksum(filename)
|
|
150
|
+
[local_file_checksum(filename), res.headers && res.headers['CF-Ray']]
|
|
141
151
|
end
|
|
142
152
|
|
|
143
153
|
# @return [ Array<String> ] The filenames updated
|
|
@@ -151,9 +161,9 @@ module WPScan
|
|
|
151
161
|
next if File.exist?(local_file_path(filename)) && db_checksum == local_file_checksum(filename)
|
|
152
162
|
|
|
153
163
|
create_backup(filename)
|
|
154
|
-
dl_checksum = download(filename)
|
|
164
|
+
dl_checksum, cf_ray = download(filename)
|
|
155
165
|
|
|
156
|
-
raise Error::ChecksumsMismatch,
|
|
166
|
+
raise Error::ChecksumsMismatch.new(filename, cf_ray: cf_ray) unless dl_checksum == db_checksum
|
|
157
167
|
|
|
158
168
|
updated << filename
|
|
159
169
|
rescue StandardError => e
|
data/lib/wpscan/db/vuln_api.rb
CHANGED
|
@@ -26,7 +26,7 @@ module WPScan
|
|
|
26
26
|
# Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI
|
|
27
27
|
res = Typhoeus.get(uri.join(path), default_request_params.merge(params))
|
|
28
28
|
|
|
29
|
-
return {} if
|
|
29
|
+
return {} if [404, 429].include?(res.code)
|
|
30
30
|
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
|
31
31
|
|
|
32
32
|
raise Error::HTTP, res
|
|
@@ -41,6 +41,9 @@ module WPScan
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
{ 'http_error' => e }
|
|
44
|
+
rescue JSON::ParserError => e
|
|
45
|
+
# API returned non-JSON response (HTML, plain text, etc.)
|
|
46
|
+
{ 'parse_error' => e }
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
# @return [ Hash ]
|
|
@@ -70,12 +73,21 @@ module WPScan
|
|
|
70
73
|
# @return [ Hash ]
|
|
71
74
|
# @note Those params can not be overriden by CLI options
|
|
72
75
|
def self.default_request_params
|
|
73
|
-
@default_request_params ||=
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
@default_request_params ||= begin
|
|
77
|
+
params = Browser.instance.default_request_params.merge(
|
|
78
|
+
headers: {
|
|
79
|
+
'User-Agent' => Browser.instance.default_user_agent,
|
|
80
|
+
'Authorization' => "Token token=#{token}"
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if ParsedCli.proxy_target_only
|
|
85
|
+
params.delete(:proxy)
|
|
86
|
+
params.delete(:proxyuserpwd)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
params
|
|
90
|
+
end
|
|
79
91
|
end
|
|
80
92
|
end
|
|
81
93
|
end
|
data/lib/wpscan/db/wp_item.rb
CHANGED
|
@@ -16,9 +16,9 @@ module WPScan
|
|
|
16
16
|
@metadata ||= read_json_file(metadata_file)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# @return [
|
|
19
|
+
# @return [ Pathname ]
|
|
20
20
|
def self.metadata_file
|
|
21
|
-
@metadata_file ||= DB_DIR.join('metadata.json')
|
|
21
|
+
@metadata_file ||= DB_DIR.join('metadata.json')
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -5,16 +5,16 @@ module WPScan
|
|
|
5
5
|
class PluginsThresholdReached < Standard
|
|
6
6
|
def to_s
|
|
7
7
|
"The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
|
|
8
|
-
'which might indicate False Positive.
|
|
9
|
-
'
|
|
8
|
+
'which might indicate False Positive. You can use --plugins-threshold to increase or disable ' \
|
|
9
|
+
'this limit (set to 0 to disable), or use --exclude-content-based to ignore bad responses.'
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
class ThemesThresholdReached < Standard
|
|
14
14
|
def to_s
|
|
15
15
|
"The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
|
|
16
|
-
'which might indicate False Positive.
|
|
17
|
-
'
|
|
16
|
+
'which might indicate False Positive. You can use --themes-threshold to increase or disable ' \
|
|
17
|
+
'this limit (set to 0 to disable), or use --exclude-content-based to ignore bad responses.'
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
data/lib/wpscan/errors/http.rb
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Error
|
|
5
|
-
# HTTP Error
|
|
5
|
+
# Generic HTTP Error
|
|
6
6
|
class HTTP < Standard
|
|
7
7
|
attr_reader :response
|
|
8
8
|
|
|
9
|
-
# @param [ Typhoeus::Response ]
|
|
9
|
+
# @param [ Typhoeus::Response ] response
|
|
10
10
|
def initialize(response)
|
|
11
11
|
@response = response
|
|
12
12
|
end
|
|
@@ -31,7 +31,86 @@ module WPScan
|
|
|
31
31
|
# Used in the Updater
|
|
32
32
|
class Download < HTTP
|
|
33
33
|
def to_s
|
|
34
|
-
"Unable to get #{failure_details}"
|
|
34
|
+
msg = "Unable to get #{failure_details}"
|
|
35
|
+
|
|
36
|
+
if response.effective_url.to_s.include?('data.wpscan.org')
|
|
37
|
+
cf_ray = response.headers && response.headers['CF-Ray']
|
|
38
|
+
msg += "\nCloudflare Ray ID: #{cf_ray}" if cf_ray
|
|
39
|
+
msg += "\nIf this issue persists, you can:\n " \
|
|
40
|
+
"- Check our status page at https://status.wpscan.com/\n " \
|
|
41
|
+
"- Contact us via https://wpscan.com/contact/ (please include the Ray ID above if any)\n " \
|
|
42
|
+
'- Or open a GitHub issue at https://github.com/wpscanteam/wpscan/issues'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
msg
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Target Down Error
|
|
50
|
+
class TargetDown < Standard
|
|
51
|
+
attr_reader :response
|
|
52
|
+
|
|
53
|
+
def initialize(response)
|
|
54
|
+
@response = response
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_s
|
|
58
|
+
"The url supplied '#{response.request.url}' seems to be down (#{response.return_message})"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# HTTP Authentication Required Error
|
|
63
|
+
class HTTPAuthRequired < Standard
|
|
64
|
+
# :nocov:
|
|
65
|
+
def to_s
|
|
66
|
+
'HTTP authentication required (or was invalid), please provide it with --http-auth'
|
|
67
|
+
end
|
|
68
|
+
# :nocov:
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Proxy Authentication Required Error
|
|
72
|
+
class ProxyAuthRequired < Standard
|
|
73
|
+
# :nocov:
|
|
74
|
+
def to_s
|
|
75
|
+
'Proxy authentication required (or was invalid), please provide it with --proxy-auth'
|
|
76
|
+
end
|
|
77
|
+
# :nocov:
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Access Forbidden Error
|
|
81
|
+
class AccessForbidden < Standard
|
|
82
|
+
attr_reader :random_user_agent_used
|
|
83
|
+
|
|
84
|
+
# @param [ Boolean ] random_user_agent_used
|
|
85
|
+
def initialize(random_user_agent_used)
|
|
86
|
+
@random_user_agent_used = random_user_agent_used
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_s
|
|
90
|
+
msg = if random_user_agent_used
|
|
91
|
+
'Well... --random-user-agent didn\'t work, use --force to skip this check if needed.'
|
|
92
|
+
else
|
|
93
|
+
'Please re-try with --random-user-agent'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
"The target is responding with a 403, this might be due to a WAF. #{msg}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# HTTP Redirect Error
|
|
101
|
+
class HTTPRedirect < Standard
|
|
102
|
+
attr_reader :redirect_uri
|
|
103
|
+
|
|
104
|
+
# @param [ String ] url
|
|
105
|
+
def initialize(url)
|
|
106
|
+
@redirect_uri = Addressable::URI.parse(url).normalize
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_s
|
|
110
|
+
"The URL supplied redirects to #{redirect_uri}. Use the --follow-redirect " \
|
|
111
|
+
'option to automatically scan the redirected URL, the --ignore-main-redirect ' \
|
|
112
|
+
'option to ignore the redirection and scan the target, or change the --url option ' \
|
|
113
|
+
'value to the redirected URL.'
|
|
35
114
|
end
|
|
36
115
|
end
|
|
37
116
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Error
|
|
5
|
+
# SAML Authentication Required Error
|
|
6
|
+
class SAMLAuthenticationRequired < Standard
|
|
7
|
+
def initialize(message = 'SAML authentication is required to access this resource, consider using --expect-saml.')
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# SAML Authentication Failed Error
|
|
13
|
+
class SAMLAuthenticationFailed < Standard
|
|
14
|
+
def initialize(message = 'SAML authentication is required to access this resource. ' \
|
|
15
|
+
'Please ensure correct authentication credentials.')
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Ferrum Browser Error. Callers should pass a context-specific message
|
|
21
|
+
# (missing Chrome binary, non-interactive terminal, browser closed mid-auth, ...).
|
|
22
|
+
class BrowserFailed < Standard
|
|
23
|
+
def initialize(message = 'The browser was closed or failed before SAML authentication could be completed.')
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/wpscan/errors/update.rb
CHANGED
|
@@ -10,14 +10,22 @@ module WPScan
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
class ChecksumsMismatch < Standard
|
|
13
|
-
attr_reader :db_file
|
|
13
|
+
attr_reader :db_file, :cf_ray
|
|
14
14
|
|
|
15
|
-
def initialize(db_file)
|
|
15
|
+
def initialize(db_file, cf_ray: nil)
|
|
16
16
|
@db_file = db_file
|
|
17
|
+
@cf_ray = cf_ray
|
|
18
|
+
super()
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def to_s
|
|
20
|
-
"#{db_file}: checksums do not match. Please try again in a few minutes."
|
|
22
|
+
msg = "#{db_file}: checksums do not match. Please try again in a few minutes."
|
|
23
|
+
msg += "\nCloudflare Ray ID: #{cf_ray}" if cf_ray
|
|
24
|
+
msg += "\nIf this issue persists, you can:\n " \
|
|
25
|
+
"- Check our status page at https://status.wpscan.com/\n " \
|
|
26
|
+
"- Contact us via https://wpscan.com/contact/ (please include the Ray ID above if any)\n " \
|
|
27
|
+
'- Or open a GitHub issue at https://github.com/wpscanteam/wpscan/issues'
|
|
28
|
+
msg
|
|
21
29
|
end
|
|
22
30
|
end
|
|
23
31
|
end
|
|
@@ -16,5 +16,29 @@ module WPScan
|
|
|
16
16
|
'Your API limit has been reached'
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
# Error raised when trying to enumerate vulnerable plugins/themes without an API token
|
|
21
|
+
class ApiTokenRequiredForVulnerableEnumeration < Standard
|
|
22
|
+
def to_s
|
|
23
|
+
'An API token is required for vulnerable plugin/theme enumeration. ' \
|
|
24
|
+
'You can get a free API token with 25 daily requests by registering at https://wpscan.com/register. ' \
|
|
25
|
+
'Provide it via --api-token TOKEN or the WPSCAN_API_TOKEN environment variable.'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Error raised when there's a connection issue with the WPScan API
|
|
30
|
+
class ApiConnectionError < Standard
|
|
31
|
+
attr_reader :original_error
|
|
32
|
+
|
|
33
|
+
def initialize(original_error)
|
|
34
|
+
@original_error = original_error
|
|
35
|
+
super()
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
"Unable to connect to the WPScan API: #{original_error}. " \
|
|
40
|
+
'Please check https://status.wpscan.com/ for service status.'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
19
43
|
end
|
|
20
44
|
end
|
|
@@ -25,8 +25,8 @@ module WPScan
|
|
|
25
25
|
|
|
26
26
|
class WpContentDirNotDetected < Standard
|
|
27
27
|
def to_s
|
|
28
|
-
'Unable to identify the wp-content dir, please supply it with --wp-content-dir,' \
|
|
29
|
-
'
|
|
28
|
+
'Unable to identify the wp-content dir, please supply it with --wp-content-dir, ' \
|
|
29
|
+
'use the --scope option or make sure the --url value given is the correct one'
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Error
|
|
5
|
+
# Raised when authenticated requests to the WP REST API are rejected.
|
|
6
|
+
class WpAuthFailed < Standard
|
|
7
|
+
def initialize(code, url)
|
|
8
|
+
super()
|
|
9
|
+
@code = code
|
|
10
|
+
@url = url
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"Authenticated REST API request to #{@url} returned HTTP #{@code}. " \
|
|
15
|
+
'WordPress core does NOT accept regular account passwords on /wp-json over Basic Auth. ' \
|
|
16
|
+
'The password passed to --wp-auth must be a WordPress Application Password ' \
|
|
17
|
+
'(WP >= 5.6): in wp-admin go to Users -> Profile -> Application Passwords, ' \
|
|
18
|
+
'create one, and use the generated 24-character string (keep the spaces) as the password.'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Raised when the WP REST plugins/themes endpoint is unreachable for reasons
|
|
23
|
+
# other than authentication (404, 5xx, security plugins blocking /wp-json, ...).
|
|
24
|
+
class WpAuthEndpointUnavailable < Standard
|
|
25
|
+
def initialize(code, url)
|
|
26
|
+
super()
|
|
27
|
+
@code = code
|
|
28
|
+
@url = url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
"Authenticated REST API endpoint #{@url} returned HTTP #{@code}. " \
|
|
33
|
+
'The endpoint may be disabled, blocked, or require WordPress 5.5+.'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/wpscan/errors.rb
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Error
|
|
5
|
-
include CMSScanner::Error
|
|
6
|
-
|
|
7
5
|
class Standard < StandardError
|
|
8
6
|
end
|
|
9
7
|
end
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
require_relative 'errors/enumeration'
|
|
13
10
|
require_relative 'errors/http'
|
|
11
|
+
require_relative 'errors/scan'
|
|
12
|
+
require_relative 'errors/enumeration'
|
|
14
13
|
require_relative 'errors/update'
|
|
15
14
|
require_relative 'errors/vuln_api'
|
|
16
15
|
require_relative 'errors/wordpress'
|
|
16
|
+
require_relative 'errors/wp_auth'
|
|
17
17
|
require_relative 'errors/xmlrpc'
|
|
18
|
+
require_relative 'errors/saml'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
# Exit Code Values
|
|
5
|
+
module ExitCode
|
|
6
|
+
# No error, scan finished w/o any vulnerabilies found
|
|
7
|
+
OK = 0
|
|
8
|
+
|
|
9
|
+
# All exceptions raised by OptParseValidator and OptionParser
|
|
10
|
+
CLI_OPTION_ERROR = 1
|
|
11
|
+
|
|
12
|
+
# Interrupt received
|
|
13
|
+
INTERRUPTED = 2
|
|
14
|
+
|
|
15
|
+
# Unhandled/unexpected Exception occured
|
|
16
|
+
EXCEPTION = 3
|
|
17
|
+
|
|
18
|
+
# Error, scan did not finish
|
|
19
|
+
ERROR = 4
|
|
20
|
+
|
|
21
|
+
# The target has at least one vulnerability.
|
|
22
|
+
# Currently, the interesting findings do not count as vulnerable things
|
|
23
|
+
VULNERABLE = 5
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
# Base class container for the Finders (i.e IndependentFinders etc)
|
|
6
|
+
class BaseFinders < Array
|
|
7
|
+
# @return [ Findings ]
|
|
8
|
+
def findings
|
|
9
|
+
@findings ||= WPScan::Finders::Findings.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Should be implemented in child classes
|
|
13
|
+
def run; end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
# @param [ Symbol ] mode :mixed, :passive or :aggressive
|
|
18
|
+
# @return [ Array<Symbol> ] The symbols to call for the mode
|
|
19
|
+
def symbols_from_mode(mode)
|
|
20
|
+
symbols = %i[passive aggressive]
|
|
21
|
+
|
|
22
|
+
return symbols if mode.nil? || mode == :mixed
|
|
23
|
+
|
|
24
|
+
symbols.include?(mode) ? Array(mode) : []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param [ WPScan::Finders::Finder ] finder
|
|
28
|
+
# @param [ Symbol ] symbol See return values of #symbols_from_mode
|
|
29
|
+
# @param [ Hash ] opts
|
|
30
|
+
def run_finder(finder, symbol, opts)
|
|
31
|
+
Array(finder.send(symbol, opts.merge(found: findings))).compact.each do |found|
|
|
32
|
+
findings << found
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Allow child classes to filter the findings, such as return the best one
|
|
37
|
+
# or remove the low confidence ones.
|
|
38
|
+
#
|
|
39
|
+
# @return [ Findings ]
|
|
40
|
+
def filter_findings
|
|
41
|
+
findings
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module DynamicFinder
|
|
6
6
|
# To be used as a base when creating a dynamic finder
|
|
7
|
-
class Finder <
|
|
7
|
+
class Finder < WPScan::Finders::Finder
|
|
8
8
|
# @param [ Array ] args
|
|
9
9
|
def self.child_class_constant(*args)
|
|
10
10
|
args.each do |arg|
|
|
@@ -9,7 +9,7 @@ module WPScan
|
|
|
9
9
|
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
|
10
10
|
# @return [ Hash ]
|
|
11
11
|
def self.child_class_constants
|
|
12
|
-
@child_class_constants ||= super
|
|
12
|
+
@child_class_constants ||= super.merge(PATTERN: nil, CONFIDENCE: 60)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# @param [ Typhoeus::Response ] response
|
|
@@ -9,7 +9,7 @@ module WPScan
|
|
|
9
9
|
class Comment < Finders::DynamicFinder::Version::Xpath
|
|
10
10
|
# @return [ Hash ]
|
|
11
11
|
def self.child_class_constants
|
|
12
|
-
@child_class_constants ||= super
|
|
12
|
+
@child_class_constants ||= super.merge(PATTERN: nil, XPATH: '//comment()')
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -8,7 +8,7 @@ module WPScan
|
|
|
8
8
|
class HeaderPattern < Finders::DynamicFinder::Version::Finder
|
|
9
9
|
# @return [ Hash ]
|
|
10
10
|
def self.child_class_constants
|
|
11
|
-
@child_class_constants ||= super
|
|
11
|
+
@child_class_constants ||= super.merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# @param [ Typhoeus::Response ] response
|
|
@@ -8,7 +8,7 @@ module WPScan
|
|
|
8
8
|
class JavascriptVar < Finders::DynamicFinder::Version::Finder
|
|
9
9
|
# @return [ Hash ]
|
|
10
10
|
def self.child_class_constants
|
|
11
|
-
@child_class_constants ||= super
|
|
11
|
+
@child_class_constants ||= super.merge(
|
|
12
12
|
XPATH: '//script[not(@src)]', VERSION_KEY: nil,
|
|
13
13
|
PATTERN: nil, CONFIDENCE: 60
|
|
14
14
|
)
|
|
@@ -8,7 +8,7 @@ module WPScan
|
|
|
8
8
|
class QueryParameter < Finders::DynamicFinder::Version::Finder
|
|
9
9
|
# @return [ Hash ]
|
|
10
10
|
def self.child_class_constants
|
|
11
|
-
@child_class_constants ||= super
|
|
11
|
+
@child_class_constants ||= super.merge(
|
|
12
12
|
XPATH: nil, FILES: nil, PATTERN: /(?:v|ver|version)=(?<v>\d+\.[.\d]+)/i, CONFIDENCE_PER_OCCURENCE: 10
|
|
13
13
|
)
|
|
14
14
|
end
|
|
@@ -17,10 +17,8 @@ module WPScan
|
|
|
17
17
|
# @param [ Hash ] opts
|
|
18
18
|
# @return [ Array<Version>, nil ]
|
|
19
19
|
def find(response, _opts = {})
|
|
20
|
-
found =
|
|
21
|
-
|
|
22
|
-
scan_response(response).each do |version_number, occurences|
|
|
23
|
-
found << create_version(
|
|
20
|
+
found = scan_response(response).map do |version_number, occurences|
|
|
21
|
+
create_version(
|
|
24
22
|
version_number,
|
|
25
23
|
confidence: self.class::CONFIDENCE_PER_OCCURENCE * occurences.size,
|
|
26
24
|
interesting_entries: occurences
|
|
@@ -8,7 +8,7 @@ module WPScan
|
|
|
8
8
|
class Xpath < Finders::DynamicFinder::Version::Finder
|
|
9
9
|
# @return [ Hash ]
|
|
10
10
|
def self.child_class_constants
|
|
11
|
-
@child_class_constants ||= super
|
|
11
|
+
@child_class_constants ||= super.merge(
|
|
12
12
|
XPATH: nil, PATTERN: /\A(?<v>\d+\.[.\d]+)/, CONFIDENCE: 60
|
|
13
13
|
)
|
|
14
14
|
end
|
|
@@ -11,14 +11,14 @@ module WPScan
|
|
|
11
11
|
# Also used to factorise some code used between such finders.
|
|
12
12
|
# The #process_response should be implemented in each child class, or the
|
|
13
13
|
# #passive and #aggressive overriden
|
|
14
|
-
class Finder <
|
|
14
|
+
class Finder < WPScan::Finders::Finder
|
|
15
15
|
# @return [ Hash ] The related dynamic finder passive configurations
|
|
16
16
|
# for the current class (all its usefullness comes from child classes)
|
|
17
17
|
def passive_configs
|
|
18
18
|
# So far only the Plugins have dynamic finders so using DB:: DynamicFinders::Plugin
|
|
19
19
|
# is ok. However, when Themes have some, will need to create other child classes for them
|
|
20
20
|
|
|
21
|
-
method = "passive_#{self.class.to_s.demodulize.underscore}_finder_configs"
|
|
21
|
+
method = :"passive_#{self.class.to_s.demodulize.underscore}_finder_configs"
|
|
22
22
|
|
|
23
23
|
DB::DynamicFinders::Plugin.public_send(method)
|
|
24
24
|
end
|
|
@@ -51,7 +51,7 @@ module WPScan
|
|
|
51
51
|
# So far only the Plugins have dynamic finders so using DB:: DynamicFinders::Plugin
|
|
52
52
|
# is ok. However, when Themes have some, will need to create other child classes for them
|
|
53
53
|
|
|
54
|
-
method = "aggressive_#{self.class.to_s.demodulize.underscore}_finder_configs"
|
|
54
|
+
method = :"aggressive_#{self.class.to_s.demodulize.underscore}_finder_configs"
|
|
55
55
|
|
|
56
56
|
DB::DynamicFinders::Plugin.public_send(method)
|
|
57
57
|
end
|