wpscan 3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|