wpscan 3.0

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