wpscan 3.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 +7 -0
- data/Gemfile.lock +139 -0
- data/LICENSE +74 -0
- data/README.md +146 -0
- data/app/app.rb +3 -0
- data/app/controllers.rb +6 -0
- data/app/controllers/brute_force.rb +126 -0
- data/app/controllers/core.rb +104 -0
- data/app/controllers/custom_directories.rb +23 -0
- data/app/controllers/enumeration.rb +53 -0
- data/app/controllers/enumeration/cli_options.rb +126 -0
- data/app/controllers/enumeration/enum_methods.rb +157 -0
- data/app/controllers/main_theme.rb +27 -0
- data/app/controllers/wp_version.rb +30 -0
- data/app/finders.rb +13 -0
- data/app/finders/config_backups.rb +17 -0
- data/app/finders/config_backups/known_filenames.rb +46 -0
- data/app/finders/interesting_findings.rb +33 -0
- data/app/finders/interesting_findings/backup_db.rb +25 -0
- data/app/finders/interesting_findings/debug_log.rb +20 -0
- data/app/finders/interesting_findings/duplicator_installer_log.rb +23 -0
- data/app/finders/interesting_findings/full_path_disclosure.rb +23 -0
- data/app/finders/interesting_findings/mu_plugins.rb +48 -0
- data/app/finders/interesting_findings/multisite.rb +29 -0
- data/app/finders/interesting_findings/readme.rb +26 -0
- data/app/finders/interesting_findings/registration.rb +31 -0
- data/app/finders/interesting_findings/tmm_db_migrate.rb +24 -0
- data/app/finders/interesting_findings/upload_directory_listing.rb +24 -0
- data/app/finders/interesting_findings/upload_sql_dump.rb +28 -0
- data/app/finders/main_theme.rb +22 -0
- data/app/finders/main_theme/css_style.rb +43 -0
- data/app/finders/main_theme/urls_in_homepage.rb +25 -0
- data/app/finders/main_theme/woo_framework_meta_generator.rb +22 -0
- data/app/finders/medias.rb +17 -0
- data/app/finders/medias/attachment_brute_forcing.rb +44 -0
- data/app/finders/plugin_version.rb +44 -0
- data/app/finders/plugin_version/layer_slider/translation_file.rb +40 -0
- data/app/finders/plugin_version/readme.rb +79 -0
- data/app/finders/plugin_version/revslider/release_log.rb +35 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +27 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +31 -0
- data/app/finders/plugin_version/w3_total_cache/headers.rb +28 -0
- data/app/finders/plugins.rb +24 -0
- data/app/finders/plugins/comments.rb +31 -0
- data/app/finders/plugins/headers.rb +36 -0
- data/app/finders/plugins/known_locations.rb +48 -0
- data/app/finders/plugins/urls_in_homepage.rb +29 -0
- data/app/finders/theme_version.rb +41 -0
- data/app/finders/theme_version/style.rb +43 -0
- data/app/finders/theme_version/woo_framework_meta_generator.rb +19 -0
- data/app/finders/themes.rb +20 -0
- data/app/finders/themes/known_locations.rb +48 -0
- data/app/finders/themes/urls_in_homepage.rb +23 -0
- data/app/finders/timthumb_version.rb +17 -0
- data/app/finders/timthumb_version/bad_request.rb +21 -0
- data/app/finders/timthumbs.rb +17 -0
- data/app/finders/timthumbs/known_locations.rb +56 -0
- data/app/finders/users.rb +24 -0
- data/app/finders/users/author_id_brute_forcing.rb +111 -0
- data/app/finders/users/author_posts.rb +61 -0
- data/app/finders/users/login_error_messages.rb +50 -0
- data/app/finders/users/wp_json_api.rb +31 -0
- data/app/finders/wp_items.rb +1 -0
- data/app/finders/wp_items/urls_in_homepage.rb +68 -0
- data/app/finders/wp_version.rb +34 -0
- data/app/finders/wp_version/atom_generator.rb +40 -0
- data/app/finders/wp_version/meta_generator.rb +27 -0
- data/app/finders/wp_version/opml_generator.rb +23 -0
- data/app/finders/wp_version/rdf_generator.rb +38 -0
- data/app/finders/wp_version/readme.rb +28 -0
- data/app/finders/wp_version/rss_generator.rb +43 -0
- data/app/finders/wp_version/sitemap_generator.rb +23 -0
- data/app/finders/wp_version/stylesheets.rb +55 -0
- data/app/finders/wp_version/unique_fingerprinting.rb +64 -0
- data/app/models.rb +10 -0
- data/app/models/config_backup.rb +5 -0
- data/app/models/interesting_finding.rb +6 -0
- data/app/models/media.rb +5 -0
- data/app/models/plugin.rb +25 -0
- data/app/models/theme.rb +99 -0
- data/app/models/timthumb.rb +74 -0
- data/app/models/user.rb +31 -0
- data/app/models/wp_item.rb +142 -0
- data/app/models/wp_version.rb +49 -0
- data/app/models/xml_rpc.rb +19 -0
- data/app/views/cli/brute_force/error.erb +1 -0
- data/app/views/cli/brute_force/found.erb +2 -0
- data/app/views/cli/brute_force/users.erb +9 -0
- data/app/views/cli/core/banner.erb +14 -0
- data/app/views/cli/core/db_update_finished.erb +8 -0
- data/app/views/cli/core/db_update_started.erb +1 -0
- data/app/views/cli/core/not_fully_configured.erb +1 -0
- data/app/views/cli/enumeration/config_backups.erb +11 -0
- data/app/views/cli/enumeration/medias.erb +11 -0
- data/app/views/cli/enumeration/plugins.erb +35 -0
- data/app/views/cli/enumeration/themes.erb +11 -0
- data/app/views/cli/enumeration/timthumbs.erb +18 -0
- data/app/views/cli/enumeration/users.erb +11 -0
- data/app/views/cli/finding.erb +32 -0
- data/app/views/cli/info.erb +1 -0
- data/app/views/cli/main_theme/theme.erb +6 -0
- data/app/views/cli/notice.erb +1 -0
- data/app/views/cli/theme.erb +64 -0
- data/app/views/cli/usage.erb +3 -0
- data/app/views/cli/vulnerability.erb +14 -0
- data/app/views/cli/wp_version/version.erb +6 -0
- data/app/views/json/brute_force/users.erb +10 -0
- data/app/views/json/core/banner.erb +12 -0
- data/app/views/json/core/db_update_finished.erb +2 -0
- data/app/views/json/core/db_update_started.erb +1 -0
- data/app/views/json/core/not_fully_configured.erb +1 -0
- data/app/views/json/enumeration/config_backups.erb +10 -0
- data/app/views/json/enumeration/medias.erb +10 -0
- data/app/views/json/enumeration/plugins.erb +25 -0
- data/app/views/json/enumeration/themes.erb +10 -0
- data/app/views/json/enumeration/timthumbs.erb +19 -0
- data/app/views/json/enumeration/users.erb +11 -0
- data/app/views/json/finding.erb +26 -0
- data/app/views/json/main_theme/theme.erb +7 -0
- data/app/views/json/theme.erb +38 -0
- data/app/views/json/wp_version/version.erb +8 -0
- data/bin/wpscan +15 -0
- data/coverage/assets/0.10.0/application.css +799 -0
- data/coverage/assets/0.10.0/application.js +1707 -0
- data/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/coverage/assets/0.10.0/loading.gif +0 -0
- data/coverage/assets/0.10.0/magnify.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +27510 -0
- data/lib/wpscan.rb +44 -0
- data/lib/wpscan/browser.rb +16 -0
- data/lib/wpscan/controller.rb +8 -0
- data/lib/wpscan/controllers.rb +8 -0
- data/lib/wpscan/db.rb +28 -0
- data/lib/wpscan/db/dynamic_finders.rb +63 -0
- data/lib/wpscan/db/plugin.rb +11 -0
- data/lib/wpscan/db/plugins.rb +11 -0
- data/lib/wpscan/db/schema.rb +39 -0
- data/lib/wpscan/db/theme.rb +11 -0
- data/lib/wpscan/db/themes.rb +11 -0
- data/lib/wpscan/db/updater.rb +148 -0
- data/lib/wpscan/db/wp_item.rb +18 -0
- data/lib/wpscan/db/wp_items.rb +21 -0
- data/lib/wpscan/db/wp_version.rb +11 -0
- data/lib/wpscan/errors/http.rb +34 -0
- data/lib/wpscan/errors/update.rb +8 -0
- data/lib/wpscan/errors/wordpress.rb +22 -0
- data/lib/wpscan/finders.rb +14 -0
- data/lib/wpscan/finders/finder/plugin_version/comments.rb +25 -0
- data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +23 -0
- data/lib/wpscan/helper.rb +6 -0
- data/lib/wpscan/references.rb +31 -0
- data/lib/wpscan/target.rb +81 -0
- data/lib/wpscan/target/platform/wordpress.rb +74 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +93 -0
- data/lib/wpscan/version.rb +4 -0
- data/lib/wpscan/vulnerability.rb +25 -0
- data/lib/wpscan/vulnerable.rb +10 -0
- data/wpscan-v3.sublime-project +8 -0
- data/wpscan-v3.sublime-workspace +895 -0
- data/wpscan.gemspec +55 -0
- metadata +419 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# HTTP Error
|
|
3
|
+
class HTTPError < StandardError
|
|
4
|
+
attr_reader :response
|
|
5
|
+
|
|
6
|
+
# @param [ Typhoeus::Response ] res
|
|
7
|
+
def initialize(response)
|
|
8
|
+
@response = response
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def failure_details
|
|
12
|
+
msg = response.effective_url
|
|
13
|
+
|
|
14
|
+
msg += if response.code.zero? || response.timed_out?
|
|
15
|
+
" (#{response.return_message})"
|
|
16
|
+
else
|
|
17
|
+
" (status: #{response.code})"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
msg
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
"HTTP Error: #{failure_details}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Used in the Updater
|
|
29
|
+
class DownloadError < HTTPError
|
|
30
|
+
def to_s
|
|
31
|
+
"Unable to get #{failure_details}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# WordPress hosted (*.wordpress.com)
|
|
3
|
+
class WordPressHostedError < StandardError
|
|
4
|
+
def to_s
|
|
5
|
+
'Scanning *.wordpress.com hosted blogs is not supported.'
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Not WordPress Error
|
|
10
|
+
class NotWordPressError < StandardError
|
|
11
|
+
def to_s
|
|
12
|
+
'The remote website is up, but does not seem to be running WordPress.'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Invalid Wp Version (used in the WpVersion#new)
|
|
17
|
+
class InvalidWordPressVersion < StandardError
|
|
18
|
+
def to_s
|
|
19
|
+
'The WordPress version is invalid'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'wpscan/finders/finder/wp_version/smart_url_checker'
|
|
2
|
+
require 'wpscan/finders/finder/plugin_version/comments'
|
|
3
|
+
|
|
4
|
+
module WPScan
|
|
5
|
+
# Custom Finders
|
|
6
|
+
module Finders
|
|
7
|
+
include CMSScanner::Finders
|
|
8
|
+
|
|
9
|
+
# Custom InterestingFindings
|
|
10
|
+
module InterestingFindings
|
|
11
|
+
include CMSScanner::Finders::InterestingFindings
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
class Finder
|
|
4
|
+
module PluginVersion
|
|
5
|
+
# Plugin Version from the Comments in the homepage, used in dynamic PluginVersion finders
|
|
6
|
+
class Comments < CMSScanner::Finders::Finder
|
|
7
|
+
def passive(_opts = {})
|
|
8
|
+
target.target.comments_from_page(self.class::PATTERN) do |match|
|
|
9
|
+
# Avoid nil version, i.e a pattern allowing both versionable and non
|
|
10
|
+
# versionable string to be detected
|
|
11
|
+
next unless match[1]
|
|
12
|
+
|
|
13
|
+
return WPScan::Version.new(
|
|
14
|
+
match[1],
|
|
15
|
+
found_by: found_by,
|
|
16
|
+
confidence: 80,
|
|
17
|
+
interesting_entries: ["#{target.target.url}, Match: '#{match}'"]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
class Finder
|
|
4
|
+
module WpVersion
|
|
5
|
+
# SmartURLChecker specific for the WP Version
|
|
6
|
+
module SmartURLChecker
|
|
7
|
+
include CMSScanner::Finders::Finder::SmartURLChecker
|
|
8
|
+
|
|
9
|
+
def create_version(number, opts = {})
|
|
10
|
+
WPScan::WpVersion.new(
|
|
11
|
+
number,
|
|
12
|
+
found_by: opts[:found_by] || found_by,
|
|
13
|
+
confidence: opts[:confidence] || 80,
|
|
14
|
+
interesting_entries: opts[:entries]
|
|
15
|
+
)
|
|
16
|
+
rescue WPScan::InvalidWordPressVersion
|
|
17
|
+
nil # Invalid Version returned as nil and will be ignored by Finders
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# References module (which should be included along with the CMSScanner::References)
|
|
3
|
+
# to allow the use of the wpvulndb reference
|
|
4
|
+
module References
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# See ActiveSupport::Concern
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# @return [ Array<Symbol> ]
|
|
10
|
+
def references_keys
|
|
11
|
+
@references_keys ||= super << :wpvulndb
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def references_urls
|
|
16
|
+
wpvulndb_urls + super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def wpvulndb_ids
|
|
20
|
+
references[:wpvulndb] || []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def wpvulndb_urls
|
|
24
|
+
wpvulndb_ids.reduce([]) { |acc, elem| acc << wpvulndb_url(elem) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def wpvulndb_url(id)
|
|
28
|
+
"https://wpvulndb.com/vulnerabilities/#{id}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'wpscan/target/platform/wordpress'
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
# Includes the WordPress Platform
|
|
5
|
+
class Target < CMSScanner::Target
|
|
6
|
+
include Platform::WordPress
|
|
7
|
+
|
|
8
|
+
# @return [ Boolean ]
|
|
9
|
+
def vulnerable?
|
|
10
|
+
[@wp_version, @main_theme, @plugins, @themes, @timthumbs].each do |e|
|
|
11
|
+
[*e].each { |ae| return true if ae && ae.vulnerable? }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
return true unless [*@config_backups].empty?
|
|
15
|
+
|
|
16
|
+
[*@users].each { |u| return true if u.password }
|
|
17
|
+
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [ Hash ] opts
|
|
22
|
+
#
|
|
23
|
+
# @return [ WpVersion, false ] The WpVersion found or false if not detected
|
|
24
|
+
def wp_version(opts = {})
|
|
25
|
+
@wp_version = Finders::WpVersion::Base.find(self, opts) if @wp_version.nil?
|
|
26
|
+
|
|
27
|
+
@wp_version
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param [ Hash ] opts
|
|
31
|
+
#
|
|
32
|
+
# @return [ Theme ]
|
|
33
|
+
def main_theme(opts = {})
|
|
34
|
+
@main_theme = Finders::MainTheme::Base.find(self, opts) if @main_theme.nil?
|
|
35
|
+
|
|
36
|
+
@main_theme
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param [ Hash ] opts
|
|
40
|
+
#
|
|
41
|
+
# @return [ Array<Plugin> ]
|
|
42
|
+
def plugins(opts = {})
|
|
43
|
+
@plugins ||= Finders::Plugins::Base.find(self, opts)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param [ Hash ] opts
|
|
47
|
+
#
|
|
48
|
+
# @return [ Array<Theme> ]
|
|
49
|
+
def themes(opts = {})
|
|
50
|
+
@themes ||= Finders::Themes::Base.find(self, opts)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param [ Hash ] opts
|
|
54
|
+
#
|
|
55
|
+
# @return [ Array<Timthumb> ]
|
|
56
|
+
def timthumbs(opts = {})
|
|
57
|
+
@timthumbs ||= Finders::Timthumbs::Base.find(self, opts)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param [ Hash ] opts
|
|
61
|
+
#
|
|
62
|
+
# @return [ Array<ConfigBackup> ]
|
|
63
|
+
def config_backups(opts = {})
|
|
64
|
+
@config_backups ||= Finders::ConfigBackups::Base.find(self, opts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param [ Hash ] opts
|
|
68
|
+
#
|
|
69
|
+
# @return [ Array<Media> ]
|
|
70
|
+
def medias(opts = {})
|
|
71
|
+
@medias ||= Finders::Medias::Base.find(self, opts)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @param [ Hash ] opts
|
|
75
|
+
#
|
|
76
|
+
# @return [ Array<User> ]
|
|
77
|
+
def users(opts = {})
|
|
78
|
+
@users ||= Finders::Users::Base.find(self, opts)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
%w(custom_directories).each do |required|
|
|
2
|
+
require "wpscan/target/platform/wordpress/#{required}"
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module WPScan
|
|
6
|
+
class Target < CMSScanner::Target
|
|
7
|
+
module Platform
|
|
8
|
+
# Some WordPress specific implementation
|
|
9
|
+
module WordPress
|
|
10
|
+
include CMSScanner::Target::Platform::PHP
|
|
11
|
+
|
|
12
|
+
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i
|
|
13
|
+
|
|
14
|
+
# These methods are used in the associated interesting_findings finders
|
|
15
|
+
# to keep the boolean state of the finding rather than re-check the whole thing again
|
|
16
|
+
attr_accessor :multisite, :registration_enabled, :mu_plugins
|
|
17
|
+
alias multisite? multisite
|
|
18
|
+
alias registration_enabled? registration_enabled
|
|
19
|
+
alias mu_plugins? mu_plugins
|
|
20
|
+
|
|
21
|
+
# @return [ Boolean ]
|
|
22
|
+
def wordpress?
|
|
23
|
+
# res = Browser.get(url)
|
|
24
|
+
|
|
25
|
+
in_scope_urls(homepage_res) do |url|
|
|
26
|
+
return true if Addressable::URI.parse(url).path.match(WORDPRESS_PATTERN)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
homepage_res.html.css('meta[name="generator"]').each do |node|
|
|
30
|
+
return true if node['content'] =~ /wordpress/i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
|
34
|
+
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [ String ]
|
|
39
|
+
def registration_url
|
|
40
|
+
multisite? ? url('wp-signup.php') : url('wp-login.php?action=register')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def wordpress_hosted?
|
|
44
|
+
uri.host =~ /wordpress.com$/i ? true : false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param [ String ] username
|
|
48
|
+
# @param [ String ] password
|
|
49
|
+
#
|
|
50
|
+
# @return [ Typhoeus::Response ]
|
|
51
|
+
def do_login(username, password)
|
|
52
|
+
login_request(username, password).run
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [ String ] username
|
|
56
|
+
# @param [ String ] password
|
|
57
|
+
#
|
|
58
|
+
# @return [ Typhoeus::Request ]
|
|
59
|
+
def login_request(username, password)
|
|
60
|
+
Browser.instance.forge_request(
|
|
61
|
+
login_url,
|
|
62
|
+
method: :post,
|
|
63
|
+
body: { log: username, pwd: password }
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [ String ] The URL to the login page
|
|
68
|
+
def login_url
|
|
69
|
+
url('wp-login.php')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
class Target < CMSScanner::Target
|
|
3
|
+
module Platform
|
|
4
|
+
# wp-content & plugins directory implementation
|
|
5
|
+
module WordPress
|
|
6
|
+
def content_dir=(dir)
|
|
7
|
+
@content_dir = dir.chomp('/')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def plugins_dir=(dir)
|
|
11
|
+
@plugins_dir = dir.chomp('/')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [ String ] The wp-content directory
|
|
15
|
+
def content_dir
|
|
16
|
+
unless @content_dir
|
|
17
|
+
escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
|
|
18
|
+
pattern = %r{#{escaped_url}(.+?)\/(?:themes|plugins|uploads)\/}i
|
|
19
|
+
|
|
20
|
+
in_scope_urls(homepage_res) do |url|
|
|
21
|
+
return @content_dir = Regexp.last_match[1] if url.match(pattern)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@content_dir
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [ Addressable::URI ]
|
|
29
|
+
def content_uri
|
|
30
|
+
uri.join("#{content_dir}/")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [ String ]
|
|
34
|
+
def content_url
|
|
35
|
+
content_uri.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [ String ]
|
|
39
|
+
def plugins_dir
|
|
40
|
+
@plugins_dir ||= "#{content_dir}/plugins"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [ Addressable::URI ]
|
|
44
|
+
def plugins_uri
|
|
45
|
+
uri.join("#{plugins_dir}/")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [ String ]
|
|
49
|
+
def plugins_url
|
|
50
|
+
plugins_uri.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# TODO: Factorise the code and the content_dir one ?
|
|
54
|
+
# @return [ String, False ] The sub_dir is found, false otherwise
|
|
55
|
+
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
|
56
|
+
# the check would be done each time
|
|
57
|
+
def sub_dir
|
|
58
|
+
unless @sub_dir
|
|
59
|
+
escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
|
|
60
|
+
pattern = %r{#{escaped_url}(.+?)\/(?:xmlrpc\.php|wp\-includes\/)}i
|
|
61
|
+
|
|
62
|
+
in_scope_urls(homepage_res) do |url|
|
|
63
|
+
return @sub_dir = Regexp.last_match[1] if url.match(pattern)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@sub_dir = false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@sub_dir
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Override of the WebSite#url to consider the custom WP directories
|
|
73
|
+
#
|
|
74
|
+
# @param [ String ] path Optional path to merge with the uri
|
|
75
|
+
#
|
|
76
|
+
# @return [ String ]
|
|
77
|
+
def url(path = nil)
|
|
78
|
+
return @uri.to_s unless path
|
|
79
|
+
|
|
80
|
+
if path =~ %r{wp\-content/plugins}i
|
|
81
|
+
path.gsub!('wp-content/plugins', plugins_dir)
|
|
82
|
+
elsif path =~ /wp\-content/i
|
|
83
|
+
path.gsub!('wp-content', content_dir)
|
|
84
|
+
elsif path[0] != '/' && sub_dir
|
|
85
|
+
path = "#{sub_dir}/#{path}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
super(path)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# Specific implementation
|
|
3
|
+
class Vulnerability < CMSScanner::Vulnerability
|
|
4
|
+
include References
|
|
5
|
+
|
|
6
|
+
# @param [ Hash ] json_data
|
|
7
|
+
# @return [ Vulnerability ]
|
|
8
|
+
def self.load_from_json(json_data)
|
|
9
|
+
references = { wpvulndb: json_data['id'].to_s }
|
|
10
|
+
|
|
11
|
+
if json_data['references']
|
|
12
|
+
references_keys.each do |key|
|
|
13
|
+
references[key] = json_data['references'][key.to_s] if json_data['references'].key?(key.to_s)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
new(
|
|
18
|
+
json_data['title'],
|
|
19
|
+
references,
|
|
20
|
+
json_data['vuln_type'],
|
|
21
|
+
json_data['fixed_in']
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|