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
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Controller
|
|
5
5
|
# Enumeration Methods
|
|
6
|
-
class Enumeration <
|
|
6
|
+
class Enumeration < WPScan::Controller::Base
|
|
7
|
+
# @return [ Boolean ] Whether enumeration findings should be streamed
|
|
8
|
+
# as they are discovered rather than batched at end of step. Streaming
|
|
9
|
+
# requires both a streaming-capable formatter (cli, cli_no_color, jsonl)
|
|
10
|
+
# and the user not having opted out via --no-stream.
|
|
11
|
+
def stream_findings?
|
|
12
|
+
formatter.streams? && ParsedCli.stream != false
|
|
13
|
+
end
|
|
14
|
+
|
|
7
15
|
# @param [ String ] type (plugins or themes)
|
|
8
16
|
# @param [ Symbol ] detection_mode
|
|
9
17
|
#
|
|
@@ -11,9 +19,10 @@ module WPScan
|
|
|
11
19
|
def enum_message(type, detection_mode)
|
|
12
20
|
return unless %w[plugins themes].include?(type)
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
enumerate = ParsedCli.enumerate || {}
|
|
23
|
+
details = if enumerate[:"vulnerable_#{type}"]
|
|
15
24
|
'Vulnerable'
|
|
16
|
-
elsif
|
|
25
|
+
elsif enumerate[:"all_#{type}"]
|
|
17
26
|
'All'
|
|
18
27
|
else
|
|
19
28
|
'Most Popular'
|
|
@@ -52,11 +61,59 @@ module WPScan
|
|
|
52
61
|
}
|
|
53
62
|
end
|
|
54
63
|
|
|
64
|
+
# Resolves collisions between --plugins-list/--themes-list and the
|
|
65
|
+
# corresponding --enumerate choices. The list options take precedence;
|
|
66
|
+
# colliding enumerate keys are removed from the supplied hash and a
|
|
67
|
+
# notice is emitted for each ignored choice.
|
|
68
|
+
#
|
|
69
|
+
# @param [ Hash ] enum The ParsedCli.enumerate hash (mutated in place)
|
|
70
|
+
def resolve_list_enumerate_collisions(enum)
|
|
71
|
+
{
|
|
72
|
+
plugins_list: %i[vulnerable_plugins all_plugins popular_plugins],
|
|
73
|
+
themes_list: %i[vulnerable_themes all_themes popular_themes]
|
|
74
|
+
}.each do |list_opt, enum_keys|
|
|
75
|
+
next unless ParsedCli.send(list_opt)
|
|
76
|
+
|
|
77
|
+
ignored = enum_keys.select { |k| enum.key?(k) }
|
|
78
|
+
next if ignored.empty?
|
|
79
|
+
|
|
80
|
+
ignored.each { |k| enum.delete(k) }
|
|
81
|
+
|
|
82
|
+
output(
|
|
83
|
+
'@notice',
|
|
84
|
+
msg: "--#{list_opt.to_s.tr('_', '-')} provided; " \
|
|
85
|
+
"ignoring colliding --enumerate choice(s): #{ignored.join(', ')}"
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Suppresses plugin/theme --enumerate choices and --plugins-list / --themes-list
|
|
91
|
+
# when --wp-auth was supplied, since AuthenticatedInventory already populated
|
|
92
|
+
# the target with authoritative data.
|
|
93
|
+
#
|
|
94
|
+
# @param [ Hash ] enum The enumeration hash, mutated in place.
|
|
95
|
+
def suppress_plugin_theme_choices_when_authenticated(enum)
|
|
96
|
+
return unless ParsedCli.wp_auth
|
|
97
|
+
|
|
98
|
+
suppressed = enum.keys & WP_AUTH_SUPPRESSED_CHOICES
|
|
99
|
+
suppressed.each { |k| enum.delete(k) }
|
|
100
|
+
|
|
101
|
+
lists_suppressed = %i[plugins_list themes_list].select { |opt| ParsedCli.send(opt) }
|
|
102
|
+
return if suppressed.empty? && lists_suppressed.empty?
|
|
103
|
+
|
|
104
|
+
ignored = (suppressed + lists_suppressed.map { |o| "--#{o.to_s.tr('_', '-')}" }).join(', ')
|
|
105
|
+
output('@notice',
|
|
106
|
+
msg: "--wp-auth provided; ignoring plugin/theme enumeration option(s): #{ignored} " \
|
|
107
|
+
'(authoritative inventory already retrieved via the WP REST API).')
|
|
108
|
+
end
|
|
109
|
+
|
|
55
110
|
# @param [ Hash ] opts
|
|
56
111
|
#
|
|
57
112
|
# @return [ Boolean ] Wether or not to enumerate the plugins
|
|
58
113
|
def enum_plugins?(opts)
|
|
59
|
-
|
|
114
|
+
return false if ParsedCli.wp_auth
|
|
115
|
+
|
|
116
|
+
ParsedCli.plugins_list || opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
|
60
117
|
end
|
|
61
118
|
|
|
62
119
|
def enum_plugins
|
|
@@ -67,20 +124,11 @@ module WPScan
|
|
|
67
124
|
)
|
|
68
125
|
|
|
69
126
|
output('@info', msg: enum_message('plugins', opts[:mode])) if user_interaction?
|
|
70
|
-
# Enumerate the plugins & find their versions to avoid doing that when #version
|
|
71
|
-
# is called in the view
|
|
72
|
-
plugins = target.plugins(opts)
|
|
73
|
-
|
|
74
|
-
if user_interaction? && !plugins.empty?
|
|
75
|
-
output('@info',
|
|
76
|
-
msg: "Checking Plugin Versions #{enum_detection_message(opts[:version_detection][:mode])}")
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
plugins.each(&:version)
|
|
80
127
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
128
|
+
enum_wp_items(
|
|
129
|
+
'plugin', target_method: :plugins, opts: opts,
|
|
130
|
+
only_vulnerable: ParsedCli.enumerate&.dig(:vulnerable_plugins)
|
|
131
|
+
)
|
|
84
132
|
end
|
|
85
133
|
|
|
86
134
|
# @param [ Hash ] opts
|
|
@@ -103,7 +151,9 @@ module WPScan
|
|
|
103
151
|
#
|
|
104
152
|
# @return [ Boolean ] Wether or not to enumerate the themes
|
|
105
153
|
def enum_themes?(opts)
|
|
106
|
-
|
|
154
|
+
return false if ParsedCli.wp_auth
|
|
155
|
+
|
|
156
|
+
ParsedCli.themes_list || opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
|
107
157
|
end
|
|
108
158
|
|
|
109
159
|
def enum_themes
|
|
@@ -114,20 +164,56 @@ module WPScan
|
|
|
114
164
|
)
|
|
115
165
|
|
|
116
166
|
output('@info', msg: enum_message('themes', opts[:mode])) if user_interaction?
|
|
117
|
-
# Enumerate the themes & find their versions to avoid doing that when #version
|
|
118
|
-
# is called in the view
|
|
119
|
-
themes = target.themes(opts)
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
168
|
+
enum_wp_items(
|
|
169
|
+
'theme', target_method: :themes, opts: opts,
|
|
170
|
+
only_vulnerable: ParsedCli.enumerate&.dig(:vulnerable_themes)
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Shared plugins/themes enumeration body. Streams per-item output when
|
|
175
|
+
# the active formatter supports it (and --no-stream wasn't passed),
|
|
176
|
+
# otherwise batches the result and renders the plural view.
|
|
177
|
+
#
|
|
178
|
+
# @param [ String ] singular 'plugin' or 'theme' (view name)
|
|
179
|
+
# @param [ Symbol ] target_method :plugins or :themes
|
|
180
|
+
# @param [ Hash ] opts Options forwarded to the target call
|
|
181
|
+
# @param [ Boolean ] only_vulnerable Filter to vulnerable items only
|
|
182
|
+
def enum_wp_items(singular, target_method:, opts:, only_vulnerable:)
|
|
183
|
+
stream = stream_findings?
|
|
184
|
+
|
|
185
|
+
items = target.send(target_method, opts) do |item|
|
|
186
|
+
stream_wp_item(item, singular: singular, only_vulnerable: only_vulnerable) if stream
|
|
124
187
|
end
|
|
125
188
|
|
|
126
|
-
|
|
189
|
+
finalize_wp_items_output(items, singular: singular, opts: opts, stream: stream,
|
|
190
|
+
only_vulnerable: only_vulnerable)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def finalize_wp_items_output(items, singular:, opts:, stream:, only_vulnerable:)
|
|
194
|
+
plural = "#{singular}s"
|
|
195
|
+
|
|
196
|
+
if !stream && user_interaction? && !items.empty?
|
|
197
|
+
mode_msg = enum_detection_message(opts[:version_detection][:mode])
|
|
198
|
+
output('@info', msg: "Checking #{singular.capitalize} Versions #{mode_msg}")
|
|
199
|
+
end
|
|
127
200
|
|
|
128
|
-
|
|
201
|
+
items.each(&:version) unless stream
|
|
202
|
+
items.select!(&:vulnerable?) if only_vulnerable
|
|
129
203
|
|
|
130
|
-
|
|
204
|
+
if stream
|
|
205
|
+
summary = items.empty? ? "No #{plural} Found." : "#{items.size} #{singular}(s) Identified."
|
|
206
|
+
output('@notice', msg: summary)
|
|
207
|
+
else
|
|
208
|
+
output(plural, plural.to_sym => items)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def stream_wp_item(item, singular:, only_vulnerable:)
|
|
213
|
+
item.version
|
|
214
|
+
return if only_vulnerable && !item.vulnerable?
|
|
215
|
+
|
|
216
|
+
output(singular, singular.to_sym => item)
|
|
131
217
|
end
|
|
132
218
|
|
|
133
219
|
# @param [ Hash ] opts
|
|
@@ -147,33 +233,39 @@ module WPScan
|
|
|
147
233
|
end
|
|
148
234
|
|
|
149
235
|
def enum_timthumbs
|
|
150
|
-
opts =
|
|
236
|
+
opts = { list: ParsedCli.timthumbs_list, show_progression: user_interaction? }
|
|
151
237
|
|
|
152
|
-
output('@info', msg:
|
|
238
|
+
output('@info', msg: 'Enumerating Timthumbs') if user_interaction?
|
|
153
239
|
output('timthumbs', timthumbs: target.timthumbs(opts))
|
|
154
240
|
end
|
|
155
241
|
|
|
156
242
|
def enum_config_backups
|
|
157
|
-
opts =
|
|
243
|
+
opts = { list: ParsedCli.config_backups_list, show_progression: user_interaction? }
|
|
158
244
|
|
|
159
|
-
output('@info', msg:
|
|
245
|
+
output('@info', msg: 'Enumerating Config Backups') if user_interaction?
|
|
160
246
|
output('config_backups', config_backups: target.config_backups(opts))
|
|
161
247
|
end
|
|
162
248
|
|
|
163
249
|
def enum_db_exports
|
|
164
|
-
opts =
|
|
250
|
+
opts = { list: ParsedCli.db_exports_list, show_progression: user_interaction? }
|
|
165
251
|
|
|
166
|
-
output('@info', msg:
|
|
252
|
+
output('@info', msg: 'Enumerating DB Exports') if user_interaction?
|
|
167
253
|
output('db_exports', db_exports: target.db_exports(opts))
|
|
168
254
|
end
|
|
169
255
|
|
|
256
|
+
def enum_backup_folders
|
|
257
|
+
opts = { list: ParsedCli.backup_folders_list, show_progression: user_interaction? }
|
|
258
|
+
|
|
259
|
+
output('@info', msg: 'Enumerating Backup Folders') if user_interaction?
|
|
260
|
+
output('backup_folders', backup_folders: target.backup_folders(opts))
|
|
261
|
+
end
|
|
262
|
+
|
|
170
263
|
def enum_medias
|
|
171
|
-
opts =
|
|
264
|
+
opts = { range: ParsedCli.enumerate[:medias], show_progression: user_interaction? }
|
|
172
265
|
|
|
173
266
|
if user_interaction?
|
|
174
267
|
output('@info',
|
|
175
|
-
msg:
|
|
176
|
-
'(Permalink setting must be set to "Plain" for those to be detected)')
|
|
268
|
+
msg: 'Enumerating Medias (Permalink setting must be set to "Plain" for those to be detected)')
|
|
177
269
|
end
|
|
178
270
|
|
|
179
271
|
output('medias', medias: target.medias(opts))
|
|
@@ -193,14 +285,29 @@ module WPScan
|
|
|
193
285
|
)
|
|
194
286
|
|
|
195
287
|
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
|
|
196
|
-
|
|
288
|
+
|
|
289
|
+
stream = stream_findings?
|
|
290
|
+
exclude = ParsedCli.exclude_usernames
|
|
291
|
+
|
|
292
|
+
users = target.users(opts) do |user|
|
|
293
|
+
next unless stream
|
|
294
|
+
next if exclude&.match?(user.username)
|
|
295
|
+
|
|
296
|
+
output('user', user: user)
|
|
297
|
+
end || []
|
|
298
|
+
|
|
299
|
+
if stream
|
|
300
|
+
output('@notice', msg: users.empty? ? 'No Users Found.' : "#{users.size} user(s) Identified.")
|
|
301
|
+
else
|
|
302
|
+
output('users', users: users)
|
|
303
|
+
end
|
|
197
304
|
end
|
|
198
305
|
|
|
199
306
|
# @return [ Range ] The user ids range to enumerate
|
|
200
307
|
# If the --enumerate is used, the default value is handled by the Option
|
|
201
308
|
# However, when using --passwords alone, the default has to be set by the code below
|
|
202
309
|
def enum_users_range
|
|
203
|
-
ParsedCli.enumerate
|
|
310
|
+
ParsedCli.enumerate&.dig(:users) || cli_enum_choices[0].choices[:u].validate(nil)
|
|
204
311
|
end
|
|
205
312
|
end
|
|
206
313
|
end
|
|
@@ -6,15 +6,38 @@ require_relative 'enumeration/enum_methods'
|
|
|
6
6
|
module WPScan
|
|
7
7
|
module Controller
|
|
8
8
|
# Enumeration Controller
|
|
9
|
-
class Enumeration <
|
|
9
|
+
class Enumeration < WPScan::Controller::Base
|
|
10
|
+
def before_scan
|
|
11
|
+
enum = ParsedCli.enumerate || {}
|
|
12
|
+
|
|
13
|
+
# Plugin/theme enumeration is bypassed when --wp-auth is set, so the API token
|
|
14
|
+
# requirement for `vp`/`vt` is irrelevant in that case.
|
|
15
|
+
return if ParsedCli.wp_auth
|
|
16
|
+
|
|
17
|
+
# Check if vulnerable plugin/theme enumeration is requested without an API token
|
|
18
|
+
return unless (enum[:vulnerable_plugins] || enum[:vulnerable_themes]) && DB::VulnApi.token.nil?
|
|
19
|
+
|
|
20
|
+
raise Error::ApiTokenRequiredForVulnerableEnumeration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Plugin/theme enumeration choices skipped when --wp-auth is supplied
|
|
24
|
+
# (authoritative inventory already fetched by AuthenticatedInventory).
|
|
25
|
+
WP_AUTH_SUPPRESSED_CHOICES = %i[
|
|
26
|
+
vulnerable_plugins all_plugins popular_plugins
|
|
27
|
+
vulnerable_themes all_themes popular_themes
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
10
30
|
def run
|
|
11
31
|
enum = ParsedCli.enumerate || {}
|
|
12
32
|
|
|
33
|
+
resolve_list_enumerate_collisions(enum)
|
|
34
|
+
suppress_plugin_theme_choices_when_authenticated(enum)
|
|
35
|
+
|
|
13
36
|
enum_plugins if enum_plugins?(enum)
|
|
14
37
|
enum_themes if enum_themes?(enum)
|
|
15
38
|
|
|
16
|
-
%i[timthumbs config_backups db_exports medias].each do |key|
|
|
17
|
-
send("enum_#{key}"
|
|
39
|
+
%i[timthumbs config_backups db_exports backup_folders medias].each do |key|
|
|
40
|
+
send(:"enum_#{key}") if enum.key?(key)
|
|
18
41
|
end
|
|
19
42
|
|
|
20
43
|
enum_users if enum_users?(enum)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Controller
|
|
5
|
+
# InterestingFindings Controller
|
|
6
|
+
class InterestingFindings < Base
|
|
7
|
+
def cli_options
|
|
8
|
+
[
|
|
9
|
+
OptChoice.new(
|
|
10
|
+
['--interesting-findings-detection MODE',
|
|
11
|
+
'Use the supplied mode for the interesting findings detection. '],
|
|
12
|
+
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
|
13
|
+
)
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
mode = WPScan::ParsedCli.interesting_findings_detection || WPScan::ParsedCli.detection_mode
|
|
19
|
+
findings = target.interesting_findings(mode: mode)
|
|
20
|
+
|
|
21
|
+
output('findings', findings: findings) unless findings.empty?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Controller
|
|
5
5
|
# Password Attack Controller
|
|
6
|
-
class PasswordAttack <
|
|
6
|
+
class PasswordAttack < WPScan::Controller::Base
|
|
7
7
|
def cli_options
|
|
8
8
|
[
|
|
9
9
|
OptFilePath.new(
|
|
@@ -21,14 +21,22 @@ module WPScan
|
|
|
21
21
|
'Multicall will only work against WP < 4.4'],
|
|
22
22
|
choices: %w[wp-login xmlrpc xmlrpc-multicall],
|
|
23
23
|
normalize: %i[downcase underscore to_sym]),
|
|
24
|
-
OptString.new(['--login-uri URI', 'The URI of the login page if different from /wp-login.php'])
|
|
24
|
+
OptString.new(['--login-uri URI', 'The URI of the login page if different from /wp-login.php']),
|
|
25
|
+
OptInteger.new(['--wordlist-skip N',
|
|
26
|
+
'Skip the first N passwords in the wordlist (resume from line N+1)'],
|
|
27
|
+
default: 0),
|
|
28
|
+
OptInteger.new(['--max-retries N',
|
|
29
|
+
'Maximum retry attempts for failed requests due to network/proxy errors'],
|
|
30
|
+
default: 0)
|
|
25
31
|
]
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def attack_opts
|
|
29
35
|
@attack_opts ||= {
|
|
30
36
|
show_progression: user_interaction?,
|
|
31
|
-
multicall_max_passwords: ParsedCli.multicall_max_passwords
|
|
37
|
+
multicall_max_passwords: ParsedCli.multicall_max_passwords,
|
|
38
|
+
wordlist_skip: ParsedCli.wordlist_skip,
|
|
39
|
+
max_retries: ParsedCli.max_retries
|
|
32
40
|
}
|
|
33
41
|
end
|
|
34
42
|
|
|
@@ -56,7 +64,7 @@ module WPScan
|
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
|
|
59
|
-
# @return [
|
|
67
|
+
# @return [ WPScan::Finders::Finder ] The finder used to perform the attack
|
|
60
68
|
def attacker
|
|
61
69
|
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
|
62
70
|
end
|
|
@@ -66,7 +74,7 @@ module WPScan
|
|
|
66
74
|
@xmlrpc ||= target.xmlrpc
|
|
67
75
|
end
|
|
68
76
|
|
|
69
|
-
# @return [
|
|
77
|
+
# @return [ WPScan::Finders::Finder ]
|
|
70
78
|
def attacker_from_cli_options
|
|
71
79
|
return unless ParsedCli.password_attack
|
|
72
80
|
|
|
@@ -99,7 +107,7 @@ module WPScan
|
|
|
99
107
|
end
|
|
100
108
|
end
|
|
101
109
|
|
|
102
|
-
# @return [
|
|
110
|
+
# @return [ WPScan::Finders::Finder ]
|
|
103
111
|
def attacker_from_automatic_detection
|
|
104
112
|
if xmlrpc_get_users_blogs_enabled?
|
|
105
113
|
wp_version = target.wp_version
|
data/app/controllers/vuln_api.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Controller
|
|
5
5
|
# Controller to handle the API token
|
|
6
|
-
class VulnApi <
|
|
6
|
+
class VulnApi < WPScan::Controller::Base
|
|
7
7
|
ENV_KEY = 'WPSCAN_API_TOKEN'
|
|
8
8
|
|
|
9
9
|
def cli_options
|
|
@@ -11,6 +11,12 @@ module WPScan
|
|
|
11
11
|
OptString.new(
|
|
12
12
|
['--api-token TOKEN',
|
|
13
13
|
'The WPScan API Token to display vulnerability data, available at https://wpscan.com/profile']
|
|
14
|
+
),
|
|
15
|
+
OptBoolean.new(
|
|
16
|
+
['--proxy-target-only',
|
|
17
|
+
'When used with --proxy, the proxy is only applied to requests made to the target, ' \
|
|
18
|
+
'not to requests made to the WPScan API or database repository (data.wpscan.org). ' \
|
|
19
|
+
'Has no effect unless --proxy is also set.']
|
|
14
20
|
)
|
|
15
21
|
]
|
|
16
22
|
end
|
|
@@ -18,13 +24,13 @@ module WPScan
|
|
|
18
24
|
def before_scan
|
|
19
25
|
return unless ParsedCli.api_token || ENV.key?(ENV_KEY)
|
|
20
26
|
|
|
21
|
-
DB::VulnApi.token = ParsedCli.api_token || ENV
|
|
27
|
+
DB::VulnApi.token = ParsedCli.api_token || ENV.fetch(ENV_KEY, nil)
|
|
22
28
|
|
|
23
29
|
api_status = DB::VulnApi.status
|
|
24
30
|
|
|
25
31
|
raise Error::InvalidApiToken if api_status['status'] == 'forbidden'
|
|
26
32
|
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
|
|
27
|
-
raise api_status['http_error'] if api_status['http_error']
|
|
33
|
+
raise Error::ApiConnectionError, api_status['http_error'] if api_status['http_error']
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def after_scan
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module Controller
|
|
5
5
|
# Wp Version Controller
|
|
6
|
-
class WpVersion <
|
|
6
|
+
class WpVersion < WPScan::Controller::Base
|
|
7
7
|
def cli_options
|
|
8
8
|
[
|
|
9
9
|
OptBoolean.new(['--wp-version-all', 'Check all the version locations'], advanced: true),
|
data/app/controllers.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'controllers/core'
|
|
|
4
4
|
require_relative 'controllers/vuln_api'
|
|
5
5
|
require_relative 'controllers/custom_directories'
|
|
6
6
|
require_relative 'controllers/wp_version'
|
|
7
|
+
require_relative 'controllers/authenticated_inventory'
|
|
7
8
|
require_relative 'controllers/main_theme'
|
|
8
9
|
require_relative 'controllers/enumeration'
|
|
9
10
|
require_relative 'controllers/password_attack'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module BackupFolders
|
|
6
|
+
# Backup Folders finder
|
|
7
|
+
class KnownLocations < Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::Enumerator
|
|
9
|
+
|
|
10
|
+
# @return [ Array<Integer> ]
|
|
11
|
+
def valid_response_codes
|
|
12
|
+
@valid_response_codes ||= [200].freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param [ Hash ] opts
|
|
16
|
+
# @option opts [ String ] :list
|
|
17
|
+
# @option opts [ Boolean ] :show_progression
|
|
18
|
+
#
|
|
19
|
+
# @return [ Array<BackupFolder> ]
|
|
20
|
+
def aggressive(opts = {})
|
|
21
|
+
found = []
|
|
22
|
+
|
|
23
|
+
enumerate(potential_urls(opts), opts.merge(check_full_response: valid_response_codes)) do |res|
|
|
24
|
+
next if target.homepage_or_404?(res)
|
|
25
|
+
|
|
26
|
+
# Only report if directory listing is enabled (makes finding actionable)
|
|
27
|
+
next unless target.directory_listing?(res.request.url)
|
|
28
|
+
|
|
29
|
+
found << Model::BackupFolder.new(
|
|
30
|
+
res.request.url,
|
|
31
|
+
confidence: 100, # Directory listing enabled - definite finding
|
|
32
|
+
found_by: DIRECT_ACCESS,
|
|
33
|
+
interesting_entries: target.directory_listing_entries(res.request.url)
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
found
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param [ Hash ] opts
|
|
41
|
+
# @option opts [ String ] :list Mandatory
|
|
42
|
+
#
|
|
43
|
+
# @return [ Hash ]
|
|
44
|
+
def potential_urls(opts = {})
|
|
45
|
+
urls = {}
|
|
46
|
+
content_base = target.content_dir || 'wp-content'
|
|
47
|
+
|
|
48
|
+
File.open(opts[:list]) do |f|
|
|
49
|
+
f.each_with_index do |line, index|
|
|
50
|
+
path = line.chomp.strip
|
|
51
|
+
next if path.empty? || path.start_with?('#')
|
|
52
|
+
|
|
53
|
+
urls[target.url("#{content_base}/#{path}")] = index
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
urls
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_progress_bar(opts = {})
|
|
61
|
+
super(opts.merge(title: ' Checking Backup Folders -'))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'backup_folders/known_locations'
|
|
4
|
+
|
|
5
|
+
module WPScan
|
|
6
|
+
module Finders
|
|
7
|
+
module BackupFolders
|
|
8
|
+
# Backup Folders Finder
|
|
9
|
+
class Base
|
|
10
|
+
include SameTypeFinder
|
|
11
|
+
|
|
12
|
+
# @param [ WPScan::Target ] target
|
|
13
|
+
def initialize(target)
|
|
14
|
+
finders << BackupFolders::KnownLocations.new(target)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module ConfigBackups
|
|
6
6
|
# Config Backup finder
|
|
7
|
-
class KnownFilenames <
|
|
8
|
-
include
|
|
7
|
+
class KnownFilenames < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::Enumerator
|
|
9
9
|
|
|
10
10
|
# @param [ Hash ] opts
|
|
11
11
|
# @option opts [ String ] :list
|
|
@@ -31,8 +31,10 @@ module WPScan
|
|
|
31
31
|
def potential_urls(opts = {})
|
|
32
32
|
urls = {}
|
|
33
33
|
|
|
34
|
-
File.open(opts[:list])
|
|
35
|
-
|
|
34
|
+
File.open(opts[:list]) do |f|
|
|
35
|
+
f.each_with_index do |file, index|
|
|
36
|
+
urls[target.url(file.chomp)] = index
|
|
37
|
+
end
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
urls
|
|
@@ -4,14 +4,14 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module DbExports
|
|
6
6
|
# DB Exports finder
|
|
7
|
-
class KnownLocations <
|
|
8
|
-
include
|
|
7
|
+
class KnownLocations < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::Enumerator
|
|
9
9
|
|
|
10
10
|
def valid_response_codes
|
|
11
11
|
@valid_response_codes ||= [200, 206].freeze
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE|ALTER) (?:TABLE|DATABASE)|INSERT INTO
|
|
14
|
+
SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE|ALTER) (?:TABLE|DATABASE)|INSERT INTO/
|
|
15
15
|
|
|
16
16
|
# @param [ Hash ] opts
|
|
17
17
|
# @option opts [ String ] :list
|
|
@@ -46,22 +46,24 @@ module WPScan
|
|
|
46
46
|
urls = {}
|
|
47
47
|
index = 0
|
|
48
48
|
|
|
49
|
-
File.open(opts[:list])
|
|
50
|
-
path
|
|
49
|
+
File.open(opts[:list]) do |f|
|
|
50
|
+
f.each do |path|
|
|
51
|
+
path.chomp!
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
if path.include?('{domain_name}')
|
|
54
|
+
urls[target.url(path.gsub('{domain_name}', domain_name))] = index
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
if domain_name != domain_name_with_sub
|
|
57
|
+
urls[target.url(path.gsub('{domain_name}', domain_name_with_sub))] = index + 1
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
index += 1
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
urls[target.url(path)] = index
|
|
59
63
|
end
|
|
60
|
-
else
|
|
61
|
-
urls[target.url(path)] = index
|
|
62
|
-
end
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
index += 1
|
|
66
|
+
end
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
urls
|
data/app/finders/db_exports.rb
CHANGED
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# BackupDB finder
|
|
7
|
-
class BackupDB <
|
|
7
|
+
class BackupDB < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'wp-content/backup-db/'
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# debug.log finder
|
|
7
|
-
class DebugLog <
|
|
7
|
+
class DebugLog < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'wp-content/debug.log'
|