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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile.lock +139 -0
  3. data/LICENSE +74 -0
  4. data/README.md +146 -0
  5. data/app/app.rb +3 -0
  6. data/app/controllers.rb +6 -0
  7. data/app/controllers/brute_force.rb +126 -0
  8. data/app/controllers/core.rb +104 -0
  9. data/app/controllers/custom_directories.rb +23 -0
  10. data/app/controllers/enumeration.rb +53 -0
  11. data/app/controllers/enumeration/cli_options.rb +126 -0
  12. data/app/controllers/enumeration/enum_methods.rb +157 -0
  13. data/app/controllers/main_theme.rb +27 -0
  14. data/app/controllers/wp_version.rb +30 -0
  15. data/app/finders.rb +13 -0
  16. data/app/finders/config_backups.rb +17 -0
  17. data/app/finders/config_backups/known_filenames.rb +46 -0
  18. data/app/finders/interesting_findings.rb +33 -0
  19. data/app/finders/interesting_findings/backup_db.rb +25 -0
  20. data/app/finders/interesting_findings/debug_log.rb +20 -0
  21. data/app/finders/interesting_findings/duplicator_installer_log.rb +23 -0
  22. data/app/finders/interesting_findings/full_path_disclosure.rb +23 -0
  23. data/app/finders/interesting_findings/mu_plugins.rb +48 -0
  24. data/app/finders/interesting_findings/multisite.rb +29 -0
  25. data/app/finders/interesting_findings/readme.rb +26 -0
  26. data/app/finders/interesting_findings/registration.rb +31 -0
  27. data/app/finders/interesting_findings/tmm_db_migrate.rb +24 -0
  28. data/app/finders/interesting_findings/upload_directory_listing.rb +24 -0
  29. data/app/finders/interesting_findings/upload_sql_dump.rb +28 -0
  30. data/app/finders/main_theme.rb +22 -0
  31. data/app/finders/main_theme/css_style.rb +43 -0
  32. data/app/finders/main_theme/urls_in_homepage.rb +25 -0
  33. data/app/finders/main_theme/woo_framework_meta_generator.rb +22 -0
  34. data/app/finders/medias.rb +17 -0
  35. data/app/finders/medias/attachment_brute_forcing.rb +44 -0
  36. data/app/finders/plugin_version.rb +44 -0
  37. data/app/finders/plugin_version/layer_slider/translation_file.rb +40 -0
  38. data/app/finders/plugin_version/readme.rb +79 -0
  39. data/app/finders/plugin_version/revslider/release_log.rb +35 -0
  40. data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +27 -0
  41. data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +31 -0
  42. data/app/finders/plugin_version/w3_total_cache/headers.rb +28 -0
  43. data/app/finders/plugins.rb +24 -0
  44. data/app/finders/plugins/comments.rb +31 -0
  45. data/app/finders/plugins/headers.rb +36 -0
  46. data/app/finders/plugins/known_locations.rb +48 -0
  47. data/app/finders/plugins/urls_in_homepage.rb +29 -0
  48. data/app/finders/theme_version.rb +41 -0
  49. data/app/finders/theme_version/style.rb +43 -0
  50. data/app/finders/theme_version/woo_framework_meta_generator.rb +19 -0
  51. data/app/finders/themes.rb +20 -0
  52. data/app/finders/themes/known_locations.rb +48 -0
  53. data/app/finders/themes/urls_in_homepage.rb +23 -0
  54. data/app/finders/timthumb_version.rb +17 -0
  55. data/app/finders/timthumb_version/bad_request.rb +21 -0
  56. data/app/finders/timthumbs.rb +17 -0
  57. data/app/finders/timthumbs/known_locations.rb +56 -0
  58. data/app/finders/users.rb +24 -0
  59. data/app/finders/users/author_id_brute_forcing.rb +111 -0
  60. data/app/finders/users/author_posts.rb +61 -0
  61. data/app/finders/users/login_error_messages.rb +50 -0
  62. data/app/finders/users/wp_json_api.rb +31 -0
  63. data/app/finders/wp_items.rb +1 -0
  64. data/app/finders/wp_items/urls_in_homepage.rb +68 -0
  65. data/app/finders/wp_version.rb +34 -0
  66. data/app/finders/wp_version/atom_generator.rb +40 -0
  67. data/app/finders/wp_version/meta_generator.rb +27 -0
  68. data/app/finders/wp_version/opml_generator.rb +23 -0
  69. data/app/finders/wp_version/rdf_generator.rb +38 -0
  70. data/app/finders/wp_version/readme.rb +28 -0
  71. data/app/finders/wp_version/rss_generator.rb +43 -0
  72. data/app/finders/wp_version/sitemap_generator.rb +23 -0
  73. data/app/finders/wp_version/stylesheets.rb +55 -0
  74. data/app/finders/wp_version/unique_fingerprinting.rb +64 -0
  75. data/app/models.rb +10 -0
  76. data/app/models/config_backup.rb +5 -0
  77. data/app/models/interesting_finding.rb +6 -0
  78. data/app/models/media.rb +5 -0
  79. data/app/models/plugin.rb +25 -0
  80. data/app/models/theme.rb +99 -0
  81. data/app/models/timthumb.rb +74 -0
  82. data/app/models/user.rb +31 -0
  83. data/app/models/wp_item.rb +142 -0
  84. data/app/models/wp_version.rb +49 -0
  85. data/app/models/xml_rpc.rb +19 -0
  86. data/app/views/cli/brute_force/error.erb +1 -0
  87. data/app/views/cli/brute_force/found.erb +2 -0
  88. data/app/views/cli/brute_force/users.erb +9 -0
  89. data/app/views/cli/core/banner.erb +14 -0
  90. data/app/views/cli/core/db_update_finished.erb +8 -0
  91. data/app/views/cli/core/db_update_started.erb +1 -0
  92. data/app/views/cli/core/not_fully_configured.erb +1 -0
  93. data/app/views/cli/enumeration/config_backups.erb +11 -0
  94. data/app/views/cli/enumeration/medias.erb +11 -0
  95. data/app/views/cli/enumeration/plugins.erb +35 -0
  96. data/app/views/cli/enumeration/themes.erb +11 -0
  97. data/app/views/cli/enumeration/timthumbs.erb +18 -0
  98. data/app/views/cli/enumeration/users.erb +11 -0
  99. data/app/views/cli/finding.erb +32 -0
  100. data/app/views/cli/info.erb +1 -0
  101. data/app/views/cli/main_theme/theme.erb +6 -0
  102. data/app/views/cli/notice.erb +1 -0
  103. data/app/views/cli/theme.erb +64 -0
  104. data/app/views/cli/usage.erb +3 -0
  105. data/app/views/cli/vulnerability.erb +14 -0
  106. data/app/views/cli/wp_version/version.erb +6 -0
  107. data/app/views/json/brute_force/users.erb +10 -0
  108. data/app/views/json/core/banner.erb +12 -0
  109. data/app/views/json/core/db_update_finished.erb +2 -0
  110. data/app/views/json/core/db_update_started.erb +1 -0
  111. data/app/views/json/core/not_fully_configured.erb +1 -0
  112. data/app/views/json/enumeration/config_backups.erb +10 -0
  113. data/app/views/json/enumeration/medias.erb +10 -0
  114. data/app/views/json/enumeration/plugins.erb +25 -0
  115. data/app/views/json/enumeration/themes.erb +10 -0
  116. data/app/views/json/enumeration/timthumbs.erb +19 -0
  117. data/app/views/json/enumeration/users.erb +11 -0
  118. data/app/views/json/finding.erb +26 -0
  119. data/app/views/json/main_theme/theme.erb +7 -0
  120. data/app/views/json/theme.erb +38 -0
  121. data/app/views/json/wp_version/version.erb +8 -0
  122. data/bin/wpscan +15 -0
  123. data/coverage/assets/0.10.0/application.css +799 -0
  124. data/coverage/assets/0.10.0/application.js +1707 -0
  125. data/coverage/assets/0.10.0/colorbox/border.png +0 -0
  126. data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  127. data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  128. data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  129. data/coverage/assets/0.10.0/favicon_green.png +0 -0
  130. data/coverage/assets/0.10.0/favicon_red.png +0 -0
  131. data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  132. data/coverage/assets/0.10.0/loading.gif +0 -0
  133. data/coverage/assets/0.10.0/magnify.png +0 -0
  134. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  135. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  136. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  137. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  138. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  139. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  140. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  141. data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  142. data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  143. data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  144. data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  145. data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  146. data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  147. data/coverage/index.html +27510 -0
  148. data/lib/wpscan.rb +44 -0
  149. data/lib/wpscan/browser.rb +16 -0
  150. data/lib/wpscan/controller.rb +8 -0
  151. data/lib/wpscan/controllers.rb +8 -0
  152. data/lib/wpscan/db.rb +28 -0
  153. data/lib/wpscan/db/dynamic_finders.rb +63 -0
  154. data/lib/wpscan/db/plugin.rb +11 -0
  155. data/lib/wpscan/db/plugins.rb +11 -0
  156. data/lib/wpscan/db/schema.rb +39 -0
  157. data/lib/wpscan/db/theme.rb +11 -0
  158. data/lib/wpscan/db/themes.rb +11 -0
  159. data/lib/wpscan/db/updater.rb +148 -0
  160. data/lib/wpscan/db/wp_item.rb +18 -0
  161. data/lib/wpscan/db/wp_items.rb +21 -0
  162. data/lib/wpscan/db/wp_version.rb +11 -0
  163. data/lib/wpscan/errors/http.rb +34 -0
  164. data/lib/wpscan/errors/update.rb +8 -0
  165. data/lib/wpscan/errors/wordpress.rb +22 -0
  166. data/lib/wpscan/finders.rb +14 -0
  167. data/lib/wpscan/finders/finder/plugin_version/comments.rb +25 -0
  168. data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +23 -0
  169. data/lib/wpscan/helper.rb +6 -0
  170. data/lib/wpscan/references.rb +31 -0
  171. data/lib/wpscan/target.rb +81 -0
  172. data/lib/wpscan/target/platform/wordpress.rb +74 -0
  173. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +93 -0
  174. data/lib/wpscan/version.rb +4 -0
  175. data/lib/wpscan/vulnerability.rb +25 -0
  176. data/lib/wpscan/vulnerable.rb +10 -0
  177. data/wpscan-v3.sublime-project +8 -0
  178. data/wpscan-v3.sublime-workspace +895 -0
  179. data/wpscan.gemspec +55 -0
  180. metadata +419 -0
@@ -0,0 +1,11 @@
1
+ module WPScan
2
+ module DB
3
+ # WP Version
4
+ class Version < WpItem
5
+ # @return [ String ]
6
+ def self.db_file
7
+ @db_file ||= File.join(DB_DIR, 'wordpresses.json')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,8 @@
1
+ module WPScan
2
+ # Error raised when there is a missing DB file and --no-update supplied
3
+ class MissingDatabaseFile < StandardError
4
+ def to_s
5
+ 'Update required, you can not run a scan if a database file is missing.'
6
+ end
7
+ end
8
+ 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,6 @@
1
+ def read_json_file(file)
2
+ # p "Reading #{file}"
3
+ JSON.parse(File.read(file))
4
+ rescue => e
5
+ raise "JSON parsing error in #{file} #{e}"
6
+ 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,4 @@
1
+ # Version
2
+ module WPScan
3
+ VERSION = '3.0'.freeze
4
+ 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