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,104 @@
1
+ module WPScan
2
+ module Controller
3
+ # Specific Core controller to include WordPress checks
4
+ class Core < CMSScanner::Controller::Core
5
+ # @return [ Array<OptParseValidator::Opt> ]
6
+ def cli_options
7
+ [OptURL.new(['--url URL', 'The URL of the blog to scan'], required_unless: :update, default_protocol: 'http')] +
8
+ super.drop(1) + # delete the --url from CMSScanner
9
+ [
10
+ OptChoice.new(['--server SERVER', 'Force the supplied server module to be loaded'],
11
+ choices: %w(apache iis nginx),
12
+ normalize: [:downcase, :to_sym]),
13
+ OptBoolean.new(['--force', 'Do not check if the target is running WordPress']),
14
+ OptBoolean.new(['--[no-]update', 'Wether or not to update the Database'], required_unless: :url)
15
+ ]
16
+ end
17
+
18
+ # @return [ DB::Updater ]
19
+ def local_db
20
+ @local_db ||= DB::Updater.new(DB_DIR)
21
+ end
22
+
23
+ # @return [ Boolean ]
24
+ def update_db_required?
25
+ if local_db.missing_files?
26
+ raise MissingDatabaseFile if parsed_options[:update] == false
27
+
28
+ return true
29
+ end
30
+
31
+ return parsed_options[:update] unless parsed_options[:update].nil?
32
+
33
+ return false unless user_interaction? && local_db.outdated?
34
+
35
+ output('@notice', msg: 'It seems like you have not updated the database for some time.')
36
+ print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
37
+
38
+ Readline.readline =~ /^y/i ? true : false
39
+ end
40
+
41
+ def update_db
42
+ output('db_update_started')
43
+ output('db_update_finished', updated: local_db.update, verbose: parsed_options[:verbose])
44
+
45
+ exit(0) unless parsed_options[:url]
46
+ end
47
+
48
+ def before_scan
49
+ output('banner')
50
+
51
+ update_db if update_db_required?
52
+
53
+ super(false) # disable banner output
54
+
55
+ DB.init_db
56
+
57
+ load_server_module
58
+
59
+ check_wordpress_state
60
+ end
61
+
62
+ # Raises errors if the target is hosted on wordpress.com or is not running WordPress
63
+ # Also check if the homepage_url is still the install url
64
+ def check_wordpress_state
65
+ raise WordPressHostedError if target.wordpress_hosted?
66
+
67
+ if Addressable::URI.parse(target.homepage_url).path =~ %r{/wp-admin/install.php$}i
68
+
69
+ output('not_fully_configured', url: target.homepage_url)
70
+
71
+ exit(WPScan::ExitCode::VULNERABLE)
72
+ end
73
+
74
+ raise NotWordPressError unless target.wordpress? || parsed_options[:force]
75
+ end
76
+
77
+ # Loads the related server module in the target
78
+ # and includes it in the WpItem class which will be needed
79
+ # to check if directory listing is enabled etc
80
+ #
81
+ # @return [ Symbol ] The server module loaded
82
+ def load_server_module
83
+ server = target.server || :Apache # Tries to auto detect the server
84
+
85
+ # Force a specific server module to be loaded if supplied
86
+ case parsed_options[:server]
87
+ when :apache
88
+ server = :Apache
89
+ when :iis
90
+ server = :IIS
91
+ when :nginx
92
+ server = :Nginx
93
+ end
94
+
95
+ mod = CMSScanner::Target::Server.const_get(server)
96
+
97
+ target.extend mod
98
+ WPScan::WpItem.include mod
99
+
100
+ server
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,23 @@
1
+ module WPScan
2
+ module Controller
3
+ # Controller to ensure that the wp-content and wp-plugins
4
+ # directories are found
5
+ class CustomDirectories < CMSScanner::Controller::Base
6
+ def cli_options
7
+ [
8
+ OptString.new(['--wp-content-dir DIR']),
9
+ OptString.new(['--wp-plugins-dir DIR'])
10
+ ]
11
+ end
12
+
13
+ def before_scan
14
+ target.content_dir = parsed_options[:wp_content_dir] if parsed_options[:wp_content_dir]
15
+ target.plugins_dir = parsed_options[:wp_plugins_dir] if parsed_options[:wp_plugins_dir]
16
+
17
+ return if target.content_dir
18
+
19
+ raise 'Unable to identify the wp-content dir, please supply it with --wp-content-dir'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'enumeration/cli_options'
2
+ require_relative 'enumeration/enum_methods'
3
+
4
+ module WPScan
5
+ module Controller
6
+ # Enumeration Controller
7
+ class Enumeration < CMSScanner::Controller::Base
8
+ def before_scan
9
+ # Create the Dynamic Finders
10
+ DB::DynamicPluginFinders.db_data.each do |name, config|
11
+ %w(Comments).each do |klass|
12
+ next unless config[klass] && config[klass]['version']
13
+
14
+ constant_name = name.tr('-', '_').camelize
15
+
16
+ unless Finders::PluginVersion.constants.include?(constant_name.to_sym)
17
+ Finders::PluginVersion.const_set(constant_name, Module.new)
18
+ end
19
+
20
+ mod = WPScan::Finders::PluginVersion.const_get(constant_name)
21
+
22
+ raise "#{mod} has already a #{klass} class" if mod.constants.include?(klass.to_sym)
23
+
24
+ case klass
25
+ when 'Comments' then create_plugins_comments_finders(mod, config[klass])
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def create_plugins_comments_finders(mod, config)
32
+ mod.const_set(
33
+ :Comments, Class.new(Finders::Finder::PluginVersion::Comments) do
34
+ const_set(:PATTERN, Regexp.new(config['pattern'], Regexp::IGNORECASE))
35
+ end
36
+ )
37
+ end
38
+
39
+ def run
40
+ enum = parsed_options[:enumerate] || {}
41
+
42
+ enum_plugins if enum_plugins?(enum)
43
+ enum_themes if enum_themes?(enum)
44
+
45
+ [:timthumbs, :config_backups, :medias].each do |key|
46
+ send("enum_#{key}".to_sym) if enum.key?(key)
47
+ end
48
+
49
+ enum_users if enum_users?(enum)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,126 @@
1
+ module WPScan
2
+ module Controller
3
+ # Enumeration CLI Options
4
+ class Enumeration < CMSScanner::Controller::Base
5
+ def cli_options
6
+ cli_enum_choices + cli_plugins_opts + cli_themes_opts +
7
+ cli_timthumbs_opts + cli_config_backups_opts + cli_medias_opts + cli_users_opts
8
+ end
9
+
10
+ # @return [ Array<OptParseValidator::OptBase> ]
11
+ # rubocop:disable Metrics/MethodLength
12
+ def cli_enum_choices
13
+ [
14
+ OptMultiChoices.new(
15
+ ['--enumerate [OPTS]', '-e', 'Enumeration Process'],
16
+ choices: {
17
+ vp: OptBoolean.new(['--vulnerable-plugins']),
18
+ ap: OptBoolean.new(['--all-plugins']),
19
+ p: OptBoolean.new(['--plugins']),
20
+ vt: OptBoolean.new(['--vulnerable-themes']),
21
+ at: OptBoolean.new(['--all-themes']),
22
+ t: OptBoolean.new(['--themes']),
23
+ tt: OptBoolean.new(['--timthumbs']),
24
+ cb: OptBoolean.new(['--config-backups']),
25
+ u: OptIntegerRange.new(['--users', 'User ids range. e.g: u1-5'], value_if_empty: '1-10'),
26
+ m: OptIntegerRange.new(['--medias', 'Media ids range. e.g m1-15'], value_if_empty: '1-100')
27
+ },
28
+ value_if_empty: 'vp,vt,tt,cb,u,m',
29
+ incompatible: [[:vp, :ap, :p], [:vt, :at, :t]]
30
+ ),
31
+ OptRegexp.new(
32
+ [
33
+ '--exclude-content-based REGEXP_OR_STRING',
34
+ 'Exclude all responses having their body matching (case insensitive) during parts of the enumeration.',
35
+ 'Regexp delimiters are not required.'
36
+ ], options: Regexp::IGNORECASE
37
+ )
38
+ ]
39
+ end
40
+ # rubocop:enable Metrics/MethodLength
41
+
42
+ # @return [ Array<OptParseValidator::OptBase> ]
43
+ def cli_plugins_opts
44
+ [
45
+ OptFilePath.new(['--plugins-list FILE-PATH', 'List of plugins\' location to use'], exists: true),
46
+ OptChoice.new(
47
+ ['--plugins-detection MODE',
48
+ 'Use the supplied mode to enumerate Plugins, instead of the global (--detection-mode) mode.'],
49
+ choices: %w(mixed passive aggressive), normalize: :to_sym
50
+ ),
51
+ OptBoolean.new(['--plugins-version-all', 'Check all the plugins version locations'])
52
+ ]
53
+ end
54
+
55
+ # @return [ Array<OptParseValidator::OptBase> ]
56
+ def cli_themes_opts
57
+ [
58
+ OptFilePath.new(['--themes-list FILE-PATH', 'List of themes\' location to use'], exists: true),
59
+ OptChoice.new(
60
+ ['--themes-detection MODE',
61
+ 'Use the supplied mode to enumerate Themes, instead of the global (--detection-mode) mode.'],
62
+ choices: %w(mixed passive aggressive), normalize: :to_sym
63
+ ),
64
+ OptBoolean.new(['--themes-version-all', 'Check all the themes version locations'])
65
+ ]
66
+ end
67
+
68
+ # @return [ Array<OptParseValidator::OptBase> ]
69
+ def cli_timthumbs_opts
70
+ [
71
+ OptFilePath.new(
72
+ ['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'],
73
+ exists: true, default: File.join(DB_DIR, 'timthumbs-v3.txt')
74
+ ),
75
+ OptChoice.new(
76
+ ['--timthumbs-detection MODE',
77
+ 'Use the supplied mode to enumerate Timthumbs, instead of the global (--detection-mode) mode.'],
78
+ choices: %w(mixed passive aggressive), normalize: :to_sym
79
+ )
80
+ ]
81
+ end
82
+
83
+ # @return [ Array<OptParseValidator::OptBase> ]
84
+ def cli_config_backups_opts
85
+ [
86
+ OptFilePath.new(
87
+ ['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'],
88
+ exists: true, default: File.join(DB_DIR, 'config_backups.txt')
89
+ ),
90
+ OptChoice.new(
91
+ ['--config-backups-detection MODE',
92
+ 'Use the supplied mode to enumerate Configs, instead of the global (--detection-mode) mode.'],
93
+ choices: %w(mixed passive aggressive), normalize: :to_sym
94
+ )
95
+ ]
96
+ end
97
+
98
+ # @return [ Array<OptParseValidator::OptBase> ]
99
+ def cli_medias_opts
100
+ [
101
+ OptChoice.new(
102
+ ['--medias-detection MODE',
103
+ 'Use the supplied mode to enumerate Medias, instead of the global (--detection-mode) mode.'],
104
+ choices: %w(mixed passive aggressive), normalize: :to_sym
105
+ )
106
+ ]
107
+ end
108
+
109
+ # @return [ Array<OptParseValidator::OptBase> ]
110
+ def cli_users_opts
111
+ [
112
+ OptFilePath.new(
113
+ ['--users-list FILE-PATH',
114
+ 'List of users to check during the users enumeration from the Login Error Messages'],
115
+ exists: true
116
+ ),
117
+ OptChoice.new(
118
+ ['--users-detection MODE',
119
+ 'Use the supplied mode to enumerate Users, instead of the global (--detection-mode) mode.'],
120
+ choices: %w(mixed passive aggressive), normalize: :to_sym
121
+ )
122
+ ]
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,157 @@
1
+ module WPScan
2
+ module Controller
3
+ # Enumeration Methods
4
+ class Enumeration < CMSScanner::Controller::Base
5
+ # @param [ String ] type (plugins or themes)
6
+ #
7
+ # @return [ String ] The related enumration message depending on the parsed_options and type supplied
8
+ def enum_message(type)
9
+ return unless type == 'plugins' || type == 'themes'
10
+
11
+ details = if parsed_options[:enumerate][:"vulnerable_#{type}"]
12
+ 'Vulnerable'
13
+ elsif parsed_options[:enumerate][:"all_#{type}"]
14
+ 'All'
15
+ else
16
+ 'Most Popular'
17
+ end
18
+
19
+ "Enumerating #{details} #{type.capitalize}"
20
+ end
21
+
22
+ # @param [ String ] type (plugins, themes etc)
23
+ #
24
+ # @return [ Hash ]
25
+ def default_opts(type)
26
+ {
27
+ mode: parsed_options[:"#{type}_detection"] || parsed_options[:detection_mode],
28
+ exclude_content: parsed_options[:exclude_content_based],
29
+ show_progression: user_interaction?
30
+ }
31
+ end
32
+
33
+ # @param [ Hash ] opts
34
+ #
35
+ # @return [ Boolean ] Wether or not to enumerate the plugins
36
+ def enum_plugins?(opts)
37
+ opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
38
+ end
39
+
40
+ def enum_plugins
41
+ opts = default_opts('plugins').merge(
42
+ list: plugins_list_from_opts(parsed_options),
43
+ version_all: parsed_options[:plugins_version_all],
44
+ sort: true
45
+ )
46
+
47
+ output('@info', msg: enum_message('plugins')) if user_interaction?
48
+ # Enumerate the plugins & find their versions to avoid doing that when #version
49
+ # is called in the view
50
+ plugins = target.plugins(opts).each(&:version)
51
+ plugins.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_plugins]
52
+
53
+ output('plugins', plugins: plugins)
54
+ end
55
+
56
+ # @param [ Hash ] opts
57
+ #
58
+ # @return [ Array<String> ] The plugins list associated to the cli options
59
+ def plugins_list_from_opts(opts)
60
+ # List file provided by the user via the cli
61
+ return File.open(opts[:plugins_list]).map(&:chomp) if opts[:plugins_list]
62
+
63
+ if opts[:enumerate][:all_plugins]
64
+ DB::Plugins.all_slugs
65
+ elsif opts[:enumerate][:plugins]
66
+ DB::Plugins.popular_slugs
67
+ else
68
+ DB::Plugins.vulnerable_slugs
69
+ end
70
+ end
71
+
72
+ # @param [ Hash ] opts
73
+ #
74
+ # @return [ Boolean ] Wether or not to enumerate the themes
75
+ def enum_themes?(opts)
76
+ opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
77
+ end
78
+
79
+ def enum_themes
80
+ opts = default_opts('themes').merge(
81
+ list: themes_list_from_opts(parsed_options),
82
+ version_all: parsed_options[:themes_version_all],
83
+ sort: true
84
+ )
85
+
86
+ output('@info', msg: enum_message('themes')) if user_interaction?
87
+ # Enumerate the themes & find their versions to avoid doing that when #version
88
+ # is called in the view
89
+ themes = target.themes(opts).each(&:version)
90
+ themes.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_themes]
91
+
92
+ output('themes', themes: themes)
93
+ end
94
+
95
+ # @param [ Hash ] opts
96
+ #
97
+ # @return [ Array<String> ] The themes list associated to the cli options
98
+ def themes_list_from_opts(opts)
99
+ # List file provided by the user via the cli
100
+ return File.open(opts[:themes_list]).map(&:chomp) if opts[:themes_list]
101
+
102
+ if opts[:enumerate][:all_themes]
103
+ DB::Themes.all_slugs
104
+ elsif opts[:enumerate][:themes]
105
+ DB::Themes.popular_slugs
106
+ else
107
+ DB::Themes.vulnerable_slugs
108
+ end
109
+ end
110
+
111
+ def enum_timthumbs
112
+ opts = default_opts('timthumbs').merge(list: parsed_options[:timthumbs_list])
113
+
114
+ output('@info', msg: 'Enumerating Timthumbs') if user_interaction?
115
+ output('timthumbs', timthumbs: target.timthumbs(opts))
116
+ end
117
+
118
+ def enum_config_backups
119
+ opts = default_opts('config_baclups').merge(list: parsed_options[:config_backups_list])
120
+
121
+ output('@info', msg: 'Enumerating Config Backups') if user_interaction?
122
+ output('config_backups', config_backups: target.config_backups(opts))
123
+ end
124
+
125
+ def enum_medias
126
+ opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias])
127
+
128
+ output('@info', msg: 'Enumerating Medias') if user_interaction?
129
+ output('medias', medias: target.medias(opts))
130
+ end
131
+
132
+ # @param [ Hash ] opts
133
+ #
134
+ # @return [ Boolean ] Wether or not to enumerate the users
135
+ def enum_users?(opts)
136
+ opts[:users] || (parsed_options[:passwords] && !parsed_options[:username] && !parsed_options[:usernames])
137
+ end
138
+
139
+ def enum_users
140
+ opts = default_opts('users').merge(
141
+ range: enum_users_range,
142
+ list: parsed_options[:users_list]
143
+ )
144
+
145
+ output('@info', msg: 'Enumerating Users') if user_interaction?
146
+ output('users', users: target.users(opts))
147
+ end
148
+
149
+ # @return [ Range ] The user ids range to enumerate
150
+ # If the --enumerate is used, the default value is handled by the Option
151
+ # However, when using --passwords alone, the default has to be set by the code below
152
+ def enum_users_range
153
+ parsed_options[:enumerate] ? parsed_options[:enumerate][:users] : cli_enum_choices[0].choices[:u].validate(nil)
154
+ end
155
+ end
156
+ end
157
+ end