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
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# DuplicatorInstallerLog finder
|
|
7
|
-
class DuplicatorInstallerLog <
|
|
7
|
+
class DuplicatorInstallerLog < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'installer-log.txt'
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Emergency Password Reset Script finder
|
|
7
|
-
class EmergencyPwdResetScript <
|
|
7
|
+
class EmergencyPwdResetScript < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'emergency.php'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module InterestingFindings
|
|
6
|
+
# FantasticoFileslist finder
|
|
7
|
+
class FantasticoFileslist < Finder
|
|
8
|
+
# @return [ InterestingFinding ]
|
|
9
|
+
def aggressive(_opts = {})
|
|
10
|
+
path = 'fantastico_fileslist.txt'
|
|
11
|
+
res = target.head_and_get(path)
|
|
12
|
+
|
|
13
|
+
return if res.body.strip.empty?
|
|
14
|
+
return unless res.headers && res.headers['Content-Type']&.start_with?('text/plain')
|
|
15
|
+
|
|
16
|
+
WPScan::Model::FantasticoFileslist.new(target.url(path), confidence: 70, found_by: found_by)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Full Path Disclosure finder
|
|
7
|
-
class FullPathDisclosure <
|
|
7
|
+
class FullPathDisclosure < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'wp-includes/rss-functions.php'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module InterestingFindings
|
|
6
|
+
# Interesting Headers finder
|
|
7
|
+
class Headers < Finder
|
|
8
|
+
# @return [ InterestingFinding ]
|
|
9
|
+
def passive(_opts = {})
|
|
10
|
+
r = WPScan::Model::Headers.new(target.homepage_url, confidence: 100, found_by: found_by)
|
|
11
|
+
|
|
12
|
+
r.interesting_entries.empty? ? nil : r
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Must Use Plugins Directory checker
|
|
7
|
-
class MuPlugins <
|
|
7
|
+
class MuPlugins < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def passive(_opts = {})
|
|
10
10
|
pattern = %r{#{target.content_dir}/mu-plugins/}i
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Multisite checker
|
|
7
|
-
class Multisite <
|
|
7
|
+
class Multisite < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
url = target.url('wp-signup.php')
|
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# See https://github.com/wpscanteam/wpscan/issues/1593
|
|
7
|
-
class PHPDisabled <
|
|
8
|
-
PATTERN = /\$wp_version
|
|
7
|
+
class PHPDisabled < WPScan::Finders::Finder
|
|
8
|
+
PATTERN = /\$wp_version =/
|
|
9
9
|
|
|
10
10
|
# @return [ InterestingFinding ]
|
|
11
11
|
def aggressive(_opts = {})
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Registration Enabled checker
|
|
7
|
-
class Registration <
|
|
7
|
+
class Registration < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def passive(_opts = {})
|
|
10
10
|
# Maybe check in the homepage if there is the registration url ?
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module InterestingFindings
|
|
6
|
+
# Robots.txt finder
|
|
7
|
+
class RobotsTxt < Finder
|
|
8
|
+
# @return [ InterestingFinding ]
|
|
9
|
+
def aggressive(_opts = {})
|
|
10
|
+
path = 'robots.txt'
|
|
11
|
+
res = target.head_and_get(path)
|
|
12
|
+
|
|
13
|
+
return unless res&.code == 200 && res.body =~ /(?:user-agent|(?:dis)?allow):/i
|
|
14
|
+
|
|
15
|
+
WPScan::Model::RobotsTxt.new(target.url(path), confidence: 100, found_by: found_by)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module InterestingFindings
|
|
6
|
+
# SearchReplaceDB2 finder
|
|
7
|
+
class SearchReplaceDB2 < Finder
|
|
8
|
+
# @return [ InterestingFinding ]
|
|
9
|
+
def aggressive(_opts = {})
|
|
10
|
+
path = 'searchreplacedb2.php'
|
|
11
|
+
|
|
12
|
+
return unless /by interconnect/i.match?(target.head_and_get(path).body)
|
|
13
|
+
|
|
14
|
+
WPScan::Model::SearchReplaceDB2.new(target.url(path), confidence: 100, found_by: found_by)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# Tmm DB Migrate finder
|
|
7
|
-
class TmmDbMigrate <
|
|
7
|
+
class TmmDbMigrate < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# UploadDirectoryListing finder
|
|
7
|
-
class UploadDirectoryListing <
|
|
7
|
+
class UploadDirectoryListing < WPScan::Finders::Finder
|
|
8
8
|
# @return [ InterestingFinding ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
path = 'wp-content/uploads/'
|
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module InterestingFindings
|
|
6
6
|
# UploadSQLDump finder
|
|
7
|
-
class UploadSQLDump <
|
|
8
|
-
SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO
|
|
7
|
+
class UploadSQLDump < WPScan::Finders::Finder
|
|
8
|
+
SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO/
|
|
9
9
|
|
|
10
10
|
# @return [ InterestingFinding ]
|
|
11
11
|
def aggressive(_opts = {})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module InterestingFindings
|
|
6
|
+
# XML RPC finder
|
|
7
|
+
class XMLRPC < Finder
|
|
8
|
+
# @return [ Array<String> ] The potential urls to the XMl RPC file
|
|
9
|
+
def potential_urls
|
|
10
|
+
@potential_urls ||= []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [ Array<XMLRPC> ]
|
|
14
|
+
def passive(opts = {})
|
|
15
|
+
[passive_headers(opts), passive_body(opts)].compact
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [ XMLRPC ]
|
|
19
|
+
def passive_headers(_opts = {})
|
|
20
|
+
url = target.homepage_res.headers['X-Pingback']
|
|
21
|
+
|
|
22
|
+
return unless target.in_scope?(url)
|
|
23
|
+
|
|
24
|
+
potential_urls << url
|
|
25
|
+
|
|
26
|
+
WPScan::Model::XMLRPC.new(url, confidence: 30, found_by: 'Headers (Passive Detection)')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [ XMLRPC ]
|
|
30
|
+
def passive_body(_opts = {})
|
|
31
|
+
target.homepage_res.html.css('link[rel="pingback"]').each do |tag|
|
|
32
|
+
url = tag.attribute('href').to_s
|
|
33
|
+
|
|
34
|
+
next unless target.in_scope?(url)
|
|
35
|
+
|
|
36
|
+
potential_urls << url
|
|
37
|
+
|
|
38
|
+
return WPScan::Model::XMLRPC.new(url, confidence: 30, found_by: 'Link Tag (Passive Detection)')
|
|
39
|
+
end
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [ XMLRPC ]
|
|
44
|
+
def aggressive(_opts = {})
|
|
45
|
+
potential_urls << target.url('xmlrpc.php')
|
|
46
|
+
|
|
47
|
+
potential_urls.uniq.each do |potential_url|
|
|
48
|
+
next unless target.in_scope?(potential_url)
|
|
49
|
+
|
|
50
|
+
res = WPScan::Browser.post(potential_url, body: Digest::MD5.hexdigest(rand(999_999).to_s[0..5]))
|
|
51
|
+
|
|
52
|
+
next unless /<methodResponse>/i.match?(res&.body)
|
|
53
|
+
|
|
54
|
+
return WPScan::Model::XMLRPC.new(potential_url, confidence: 100, found_by: DIRECT_ACCESS)
|
|
55
|
+
end
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'interesting_findings/headers'
|
|
4
|
+
require_relative 'interesting_findings/robots_txt'
|
|
5
|
+
require_relative 'interesting_findings/fantastico_fileslist'
|
|
6
|
+
require_relative 'interesting_findings/search_replace_db_2'
|
|
7
|
+
require_relative 'interesting_findings/xml_rpc'
|
|
3
8
|
require_relative 'interesting_findings/readme'
|
|
4
9
|
require_relative 'interesting_findings/wp_cron'
|
|
5
10
|
require_relative 'interesting_findings/multisite'
|
|
@@ -18,18 +23,22 @@ require_relative 'interesting_findings/emergency_pwd_reset_script'
|
|
|
18
23
|
module WPScan
|
|
19
24
|
module Finders
|
|
20
25
|
module InterestingFindings
|
|
21
|
-
# Interesting Files Finder
|
|
22
|
-
class Base
|
|
26
|
+
# Interesting Files Finder (base + WordPress-specific finders).
|
|
27
|
+
class Base
|
|
28
|
+
include IndependentFinder
|
|
29
|
+
|
|
23
30
|
# @param [ WPScan::Target ] target
|
|
24
31
|
def initialize(target)
|
|
25
|
-
|
|
32
|
+
%w[Headers RobotsTxt FantasticoFileslist SearchReplaceDB2 XMLRPC].each do |f|
|
|
33
|
+
finders << WPScan::Finders::InterestingFindings.const_get(f).new(target)
|
|
34
|
+
end
|
|
26
35
|
|
|
27
36
|
%w[
|
|
28
37
|
Readme DebugLog FullPathDisclosure BackupDB DuplicatorInstallerLog
|
|
29
38
|
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
|
30
39
|
UploadSQLDump EmergencyPwdResetScript WPCron PHPDisabled
|
|
31
40
|
].each do |f|
|
|
32
|
-
finders << InterestingFindings.const_get(f).new(target)
|
|
41
|
+
finders << WPScan::Finders::InterestingFindings.const_get(f).new(target)
|
|
33
42
|
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module MainTheme
|
|
6
6
|
# From the CSS style in the homepage
|
|
7
|
-
class CssStyleInHomepage <
|
|
7
|
+
class CssStyleInHomepage < WPScan::Finders::Finder
|
|
8
8
|
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
|
|
9
9
|
|
|
10
10
|
def create_theme(slug, style_url, opts)
|
|
@@ -4,22 +4,18 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module MainTheme
|
|
6
6
|
# URLs In Homepage Finder
|
|
7
|
-
class UrlsInHomepage <
|
|
7
|
+
class UrlsInHomepage < WPScan::Finders::Finder
|
|
8
8
|
include WpItems::UrlsInPage
|
|
9
9
|
|
|
10
10
|
# @param [ Hash ] opts
|
|
11
11
|
#
|
|
12
12
|
# @return [ Array<Theme> ]
|
|
13
13
|
def passive(opts = {})
|
|
14
|
-
found = []
|
|
15
|
-
|
|
16
14
|
slugs = items_from_links('themes', uniq: false) + items_from_codes('themes', uniq: false)
|
|
17
15
|
|
|
18
|
-
slugs.
|
|
19
|
-
|
|
16
|
+
slugs.tally.map do |slug, occurences|
|
|
17
|
+
Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
|
20
18
|
end
|
|
21
|
-
|
|
22
|
-
found
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
# @return [ Typhoeus::Response ]
|
|
@@ -4,10 +4,10 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module MainTheme
|
|
6
6
|
# From the WooFramework meta generators
|
|
7
|
-
class WooFrameworkMetaGenerator <
|
|
8
|
-
THEME_PATTERN = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?"\s+/?>}
|
|
9
|
-
FRAMEWORK_PATTERN = %r{<meta name="generator" content="WooFramework\s?([^"]+)?"\s+/?>}
|
|
10
|
-
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i
|
|
7
|
+
class WooFrameworkMetaGenerator < WPScan::Finders::Finder
|
|
8
|
+
THEME_PATTERN = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?"\s+/?>}
|
|
9
|
+
FRAMEWORK_PATTERN = %r{<meta name="generator" content="WooFramework\s?([^"]+)?"\s+/?>}
|
|
10
|
+
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i
|
|
11
11
|
|
|
12
12
|
def passive(opts = {})
|
|
13
13
|
return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
|
data/app/finders/main_theme.rb
CHANGED
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module Medias
|
|
6
6
|
# Medias Finder, see https://github.com/wpscanteam/wpscan/issues/172
|
|
7
|
-
class AttachmentBruteForcing <
|
|
8
|
-
include
|
|
7
|
+
class AttachmentBruteForcing < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::Enumerator
|
|
9
9
|
|
|
10
10
|
# @param [ Hash ] opts
|
|
11
11
|
# @option opts [ Range ] :range Mandatory
|
data/app/finders/medias.rb
CHANGED
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module Passwords
|
|
6
6
|
# Password attack against the wp-login.php
|
|
7
|
-
class WpLogin <
|
|
8
|
-
include
|
|
7
|
+
class WpLogin < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::BreadthFirstDictionaryAttack
|
|
9
9
|
|
|
10
10
|
def login_request(username, password)
|
|
11
11
|
target.login_request(username, password)
|
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module Passwords
|
|
6
6
|
# Password attack against the XMLRPC interface
|
|
7
|
-
class XMLRPC <
|
|
8
|
-
include
|
|
7
|
+
class XMLRPC < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::BreadthFirstDictionaryAttack
|
|
9
9
|
|
|
10
10
|
def login_request(username, password)
|
|
11
11
|
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
|
@@ -5,7 +5,7 @@ module WPScan
|
|
|
5
5
|
module Passwords
|
|
6
6
|
# Password attack against the XMLRPC interface with the multicall method
|
|
7
7
|
# WP < 4.4 is vulnerable to such attack
|
|
8
|
-
class XMLRPCMulticall <
|
|
8
|
+
class XMLRPCMulticall < WPScan::Finders::Finder
|
|
9
9
|
# @param [ Array<User> ] users
|
|
10
10
|
# @param [ Array<String> ] passwords
|
|
11
11
|
#
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module PluginVersion
|
|
6
6
|
# Plugin Version Finder from the readme.txt file
|
|
7
|
-
class Readme <
|
|
7
|
+
class Readme < WPScan::Finders::Finder
|
|
8
8
|
# @return [ Version ]
|
|
9
9
|
def aggressive(_opts = {})
|
|
10
10
|
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
|
@@ -4,8 +4,8 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module Plugins
|
|
6
6
|
# Known Locations Plugins Finder
|
|
7
|
-
class KnownLocations <
|
|
8
|
-
include
|
|
7
|
+
class KnownLocations < WPScan::Finders::Finder
|
|
8
|
+
include WPScan::Finders::Finder::Enumerator
|
|
9
9
|
|
|
10
10
|
# @return [ Array<Integer> ]
|
|
11
11
|
def valid_response_codes
|
|
@@ -14,22 +14,32 @@ module WPScan
|
|
|
14
14
|
|
|
15
15
|
# @param [ Hash ] opts
|
|
16
16
|
# @option opts [ String ] :list
|
|
17
|
+
# @option opts [ Findings ] :found Shared findings collection passed by
|
|
18
|
+
# BaseFinders#run_finder. We append directly into it as each plugin
|
|
19
|
+
# is detected so that Findings#on_append fires during the hydra run,
|
|
20
|
+
# enabling streaming output. Falls back to a local array when called
|
|
21
|
+
# outside the framework (e.g. directly from specs).
|
|
17
22
|
#
|
|
18
|
-
# @return [ Array<Plugin> ]
|
|
23
|
+
# @return [ Array<Plugin> ] Items appended this call (empty when
|
|
24
|
+
# already streamed into opts[:found] to avoid double-appending).
|
|
19
25
|
def aggressive(opts = {})
|
|
20
|
-
|
|
26
|
+
shared = opts[:found]
|
|
27
|
+
local = shared ? nil : []
|
|
28
|
+
count = 0
|
|
21
29
|
|
|
22
30
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
|
23
31
|
finding_opts = opts.merge(found_by: found_by,
|
|
24
32
|
confidence: 80,
|
|
25
33
|
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
plugin = Model::Plugin.new(slug, target, finding_opts)
|
|
36
|
+
(shared || local) << plugin
|
|
37
|
+
count += 1
|
|
28
38
|
|
|
29
|
-
raise Error::PluginsThresholdReached if opts[:threshold].positive? &&
|
|
39
|
+
raise Error::PluginsThresholdReached if opts[:threshold].positive? && count >= opts[:threshold]
|
|
30
40
|
end
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
local || []
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
# @param [ Hash ] opts
|
|
@@ -5,20 +5,16 @@ module WPScan
|
|
|
5
5
|
module Plugins
|
|
6
6
|
# URLs In Homepage Finder
|
|
7
7
|
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
|
8
|
-
class UrlsInHomepage <
|
|
8
|
+
class UrlsInHomepage < WPScan::Finders::Finder
|
|
9
9
|
include WpItems::UrlsInPage
|
|
10
10
|
|
|
11
11
|
# @param [ Hash ] opts
|
|
12
12
|
#
|
|
13
13
|
# @return [ Array<Plugin> ]
|
|
14
14
|
def passive(opts = {})
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
|
18
|
-
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
|
15
|
+
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.map do |slug|
|
|
16
|
+
Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
|
19
17
|
end
|
|
20
|
-
|
|
21
|
-
found
|
|
22
18
|
end
|
|
23
19
|
|
|
24
20
|
# @return [ Typhoeus::Response ]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
module Finders
|
|
5
|
+
module Plugins
|
|
6
|
+
# Authenticated plugin inventory via the WordPress REST API
|
|
7
|
+
# endpoint /wp-json/wp/v2/plugins (WP >= 5.5).
|
|
8
|
+
#
|
|
9
|
+
# Requires admin credentials with the install_plugins / activate_plugins
|
|
10
|
+
# capability. An Application Password (WP >= 5.6) is the recommended
|
|
11
|
+
# secret as it bypasses 2FA and is colon-free by construction.
|
|
12
|
+
class WpJsonApi < WPScan::Finders::Finder
|
|
13
|
+
FOUND_BY = 'WP REST API (Authenticated)'
|
|
14
|
+
|
|
15
|
+
# @param [ Hash ] opts
|
|
16
|
+
# @option opts [ String ] :userpwd "user:password" credentials for Basic Auth
|
|
17
|
+
#
|
|
18
|
+
# @return [ Array<Model::Plugin> ]
|
|
19
|
+
def aggressive(opts = {})
|
|
20
|
+
response = Browser.get(api_url, userpwd: opts[:userpwd], headers: { 'Accept' => 'application/json' })
|
|
21
|
+
|
|
22
|
+
raise Error::WpAuthFailed.new(response.code, api_url) if [401, 403].include?(response.code)
|
|
23
|
+
raise Error::WpAuthEndpointUnavailable.new(response.code, api_url) unless response.code == 200
|
|
24
|
+
|
|
25
|
+
plugins_from_response(response)
|
|
26
|
+
rescue JSON::ParserError, TypeError
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param [ Typhoeus::Response ] response
|
|
31
|
+
#
|
|
32
|
+
# @return [ Array<Model::Plugin> ]
|
|
33
|
+
def plugins_from_response(response)
|
|
34
|
+
json = JSON.parse(response.body)
|
|
35
|
+
return [] unless json.is_a?(Enumerable)
|
|
36
|
+
|
|
37
|
+
json.filter_map { |entry| build_plugin(entry, response.effective_url) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [ String ] The REST API URL for the plugins endpoint
|
|
41
|
+
def api_url
|
|
42
|
+
@api_url ||= target.url('wp-json/wp/v2/plugins')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# @param [ Hash ] entry
|
|
48
|
+
# @param [ String ] effective_url
|
|
49
|
+
#
|
|
50
|
+
# @return [ Model::Plugin, nil ]
|
|
51
|
+
def build_plugin(entry, effective_url)
|
|
52
|
+
slug = slug_from_entry(entry)
|
|
53
|
+
return nil if slug.nil? || slug.empty?
|
|
54
|
+
|
|
55
|
+
plugin = Model::Plugin.new(
|
|
56
|
+
slug,
|
|
57
|
+
target,
|
|
58
|
+
confidence: 100,
|
|
59
|
+
found_by: FOUND_BY,
|
|
60
|
+
interesting_entries: [effective_url]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
version = entry['version']
|
|
64
|
+
plugin.instance_variable_set(
|
|
65
|
+
:@version,
|
|
66
|
+
version && !version.to_s.empty? ? Model::Version.new(version, confidence: 100, found_by: FOUND_BY) : false
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
plugin
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param [ Hash ] entry
|
|
73
|
+
#
|
|
74
|
+
# @return [ String, nil ]
|
|
75
|
+
# WP REST returns "plugin" as e.g. "akismet/akismet" or just "hello" for the legacy single-file plugin.
|
|
76
|
+
def slug_from_entry(entry)
|
|
77
|
+
raw = entry['plugin'] || entry['textdomain']
|
|
78
|
+
return nil unless raw.is_a?(String)
|
|
79
|
+
|
|
80
|
+
raw.split('/').first
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/app/finders/plugins.rb
CHANGED
|
@@ -11,13 +11,14 @@ require_relative 'plugins/body_pattern'
|
|
|
11
11
|
require_relative 'plugins/javascript_var'
|
|
12
12
|
require_relative 'plugins/query_parameter'
|
|
13
13
|
require_relative 'plugins/config_parser' # Not loaded below as not implemented
|
|
14
|
+
require_relative 'plugins/wp_json_api' # Authenticated, used by AuthenticatedInventory controller
|
|
14
15
|
|
|
15
16
|
module WPScan
|
|
16
17
|
module Finders
|
|
17
18
|
module Plugins
|
|
18
19
|
# Plugins Finder
|
|
19
20
|
class Base
|
|
20
|
-
include
|
|
21
|
+
include WPScan::Finders::SameTypeFinder
|
|
21
22
|
|
|
22
23
|
# @param [ WPScan::Target ] target
|
|
23
24
|
def initialize(target)
|
|
@@ -4,7 +4,7 @@ module WPScan
|
|
|
4
4
|
module Finders
|
|
5
5
|
module ThemeVersion
|
|
6
6
|
# Theme Version Finder from the WooFramework generators
|
|
7
|
-
class WooFrameworkMetaGenerator <
|
|
7
|
+
class WooFrameworkMetaGenerator < WPScan::Finders::Finder
|
|
8
8
|
# @param [ Hash ] opts
|
|
9
9
|
#
|
|
10
10
|
# @return [ Version ]
|