Class: WPScan::Controller::Enumeration
- Defined in:
- app/controllers/enumeration.rb,
app/controllers/enumeration/cli_options.rb,
app/controllers/enumeration/enum_methods.rb
Overview
Enumeration Methods
Constant Summary collapse
- WP_AUTH_SUPPRESSED_CHOICES =
Plugin/theme enumeration choices skipped when –wp-auth is supplied (authoritative inventory already fetched by AuthenticatedInventory).
%i[ vulnerable_plugins all_plugins popular_plugins vulnerable_themes all_themes popular_themes ].freeze
Constants included from OptParseValidator
Instance Method Summary collapse
- #before_scan ⇒ Object
- #cli_backup_folders_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_config_backups_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_db_exports_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_enum_choices ⇒ Array<OptParseValidator::OptBase>
- #cli_medias_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_options ⇒ Object
- #cli_plugins_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_themes_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_timthumbs_opts ⇒ Array<OptParseValidator::OptBase>
- #cli_users_opts ⇒ Array<OptParseValidator::OptBase>
- #default_opts(type) ⇒ Hash
- #enum_backup_folders ⇒ Object
- #enum_config_backups ⇒ Object
- #enum_db_exports ⇒ Object
- #enum_detection_message(detection_mode) ⇒ String
- #enum_medias ⇒ Object
-
#enum_message(type, detection_mode) ⇒ String
The related enumration message depending on the ParsedCli and type supplied.
- #enum_plugins ⇒ Object
-
#enum_plugins?(opts) ⇒ Boolean
Wether or not to enumerate the plugins.
- #enum_themes ⇒ Object
-
#enum_themes?(opts) ⇒ Boolean
Wether or not to enumerate the themes.
- #enum_timthumbs ⇒ Object
- #enum_users ⇒ Object
-
#enum_users?(opts) ⇒ Boolean
Wether or not to enumerate the users.
-
#enum_users_range ⇒ Range
If the –enumerate is used, the default value is handled by the Option However, when using –passwords alone, the default has to be set by the code below.
-
#enum_wp_items(singular, target_method:, opts:, only_vulnerable:) ⇒ Object
Shared plugins/themes enumeration body.
- #finalize_wp_items_output(items, singular:, opts:, stream:, only_vulnerable:) ⇒ Object
-
#plugins_list_from_opts(opts) ⇒ Array<String>
The plugins list associated to the cli options.
-
#resolve_list_enumerate_collisions(enum) ⇒ Object
Resolves collisions between –plugins-list/–themes-list and the corresponding –enumerate choices.
- #run ⇒ Object
-
#stream_findings? ⇒ Boolean
Whether enumeration findings should be streamed as they are discovered rather than batched at end of step.
- #stream_wp_item(item, singular:, only_vulnerable:) ⇒ Object
-
#suppress_plugin_theme_choices_when_authenticated(enum) ⇒ Object
Suppresses plugin/theme –enumerate choices and –plugins-list / –themes-list when –wp-auth was supplied, since AuthenticatedInventory already populated the target with authoritative data.
-
#themes_list_from_opts(opts) ⇒ Array<String>
The themes list associated to the cli options.
Methods inherited from Base
#==, #after_scan, #datastore, #formatter, #option_parser, option_parser=, #output, #render, reset, #target, #tmp_directory, #user_interaction?
Instance Method Details
#before_scan ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'app/controllers/enumeration.rb', line 10 def before_scan enum = ParsedCli.enumerate || {} # Plugin/theme enumeration is bypassed when --wp-auth is set, so the API token # requirement for `vp`/`vt` is irrelevant in that case. return if ParsedCli.wp_auth # Check if vulnerable plugin/theme enumeration is requested without an API token return unless (enum[:vulnerable_plugins] || enum[:vulnerable_themes]) && DB::VulnApi.token.nil? raise Error::ApiTokenRequiredForVulnerableEnumeration end |
#cli_backup_folders_opts ⇒ Array<OptParseValidator::OptBase>
137 138 139 140 141 142 143 144 |
# File 'app/controllers/enumeration/cli_options.rb', line 137 def cli_backup_folders_opts [ OptFilePath.new( ['--backup-folders-list FILE-PATH', 'List of backup folders to use'], exists: true, default: DB_DIR.join('backup_folders.txt'), advanced: true ) ] end |
#cli_config_backups_opts ⇒ Array<OptParseValidator::OptBase>
117 118 119 120 121 122 123 124 |
# File 'app/controllers/enumeration/cli_options.rb', line 117 def cli_config_backups_opts [ OptFilePath.new( ['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'], exists: true, default: DB_DIR.join('config_backups.txt'), advanced: true ) ] end |
#cli_db_exports_opts ⇒ Array<OptParseValidator::OptBase>
127 128 129 130 131 132 133 134 |
# File 'app/controllers/enumeration/cli_options.rb', line 127 def cli_db_exports_opts [ OptFilePath.new( ['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'], exists: true, default: DB_DIR.join('db_exports.txt'), advanced: true ) ] end |
#cli_enum_choices ⇒ Array<OptParseValidator::OptBase>
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'app/controllers/enumeration/cli_options.rb', line 14 def cli_enum_choices [ OptMultiChoices.new( ['-e', '--enumerate [OPTS]', 'Enumeration Process', 'Note: --plugins-list overrides vp/ap/p; --themes-list overrides vt/at/t.'], choices: { vp: OptBoolean.new(['--vulnerable-plugins']), ap: OptBoolean.new(['--all-plugins']), p: OptBoolean.new(['--popular-plugins']), vt: OptBoolean.new(['--vulnerable-themes']), at: OptBoolean.new(['--all-themes']), t: OptBoolean.new(['--popular-themes']), tt: OptBoolean.new(['--timthumbs']), cb: OptBoolean.new(['--config-backups']), dbe: OptBoolean.new(['--db-exports']), bf: OptBoolean.new(['--backup-folders']), u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'), m: OptIntegerRange.new(['--medias', 'Media IDs range. e.g m1-15', 'Note: Permalink setting must be set to "Plain" for those to be detected'], value_if_empty: '1-100') }, value_if_empty: 'vp,vt,tt,cb,dbe,bf,u,m', incompatible: [%i[vp ap p], %i[vt at t]] ), OptRegexp.new( [ '--exclude-content-based REGEXP_OR_STRING', 'Exclude all responses matching the Regexp (case insensitive) during parts of the enumeration.', 'Both the headers and body are checked. Regexp delimiters are not required.' ], options: Regexp::IGNORECASE ) ] end |
#cli_medias_opts ⇒ Array<OptParseValidator::OptBase>
147 148 149 |
# File 'app/controllers/enumeration/cli_options.rb', line 147 def cli_medias_opts [] end |
#cli_options ⇒ Object
7 8 9 10 11 |
# File 'app/controllers/enumeration/cli_options.rb', line 7 def cli_enum_choices + cli_plugins_opts + cli_themes_opts + cli_timthumbs_opts + cli_config_backups_opts + cli_db_exports_opts + cli_backup_folders_opts + cli_medias_opts + cli_users_opts end |
#cli_plugins_opts ⇒ Array<OptParseValidator::OptBase>
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'app/controllers/enumeration/cli_options.rb', line 50 def cli_plugins_opts [ OptSmartList.new(['--plugins-list LIST', 'List of plugins to enumerate'], advanced: true), OptChoice.new( ['--plugins-detection MODE', 'Use the supplied mode to enumerate Plugins.'], choices: %w[mixed passive aggressive], normalize: :to_sym ), OptBoolean.new( ['--plugins-version-all', 'Check all the plugins version locations according to the choosen mode (--detection-mode, ' \ '--plugins-detection and --plugins-version-detection)'], advanced: true ), OptChoice.new( ['--plugins-version-detection MODE', 'Use the supplied mode to check plugins\' versions.'], choices: %w[mixed passive aggressive], normalize: :to_sym ), OptInteger.new( ['--plugins-threshold THRESHOLD', 'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \ 'Set to 0 to ignore the threshold.'], default: 100, advanced: true ) ] end |
#cli_themes_opts ⇒ Array<OptParseValidator::OptBase>
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'app/controllers/enumeration/cli_options.rb', line 78 def cli_themes_opts [ OptSmartList.new(['--themes-list LIST', 'List of themes to enumerate'], advanced: true), OptChoice.new( ['--themes-detection MODE', 'Use the supplied mode to enumerate Themes, instead of the global (--detection-mode) mode.'], choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true ), OptBoolean.new( ['--themes-version-all', 'Check all the themes version locations according to the choosen mode (--detection-mode, ' \ '--themes-detection and --themes-version-detection)'], advanced: true ), OptChoice.new( ['--themes-version-detection MODE', 'Use the supplied mode to check themes versions instead of the --detection-mode ' \ 'or --themes-detection modes.'], choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true ), OptInteger.new( ['--themes-threshold THRESHOLD', 'Raise an error when the number of detected themes via known locations reaches the threshold. ' \ 'Set to 0 to ignore the threshold.'], default: 20, advanced: true ) ] end |
#cli_timthumbs_opts ⇒ Array<OptParseValidator::OptBase>
107 108 109 110 111 112 113 114 |
# File 'app/controllers/enumeration/cli_options.rb', line 107 def cli_timthumbs_opts [ OptFilePath.new( ['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'], exists: true, default: DB_DIR.join('timthumbs-v3.txt'), advanced: true ) ] end |
#cli_users_opts ⇒ Array<OptParseValidator::OptBase>
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'app/controllers/enumeration/cli_options.rb', line 152 def cli_users_opts [ OptSmartList.new( ['--users-list LIST', 'List of users to check during the users enumeration from the Login Error Messages'], advanced: true ), OptChoice.new( ['--users-detection MODE', 'Use the supplied mode to enumerate Users, instead of the global (--detection-mode) mode.'], choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true ), OptRegexp.new( [ '--exclude-usernames REGEXP_OR_STRING', 'Exclude usernames matching the Regexp/string (case insensitive). Regexp delimiters are not required.' ], options: Regexp::IGNORECASE ) ] end |
#default_opts(type) ⇒ Hash
50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'app/controllers/enumeration/enum_methods.rb', line 50 def default_opts(type) mode = ParsedCli.[:"#{type}_detection"] || ParsedCli.detection_mode { mode: mode, exclude_content: ParsedCli.exclude_content_based, show_progression: user_interaction?, version_detection: { mode: ParsedCli.[:"#{type}_version_detection"] || mode, confidence_threshold: ParsedCli.[:"#{type}_version_all"] ? 0 : 100 } } end |
#enum_backup_folders ⇒ Object
256 257 258 259 260 261 |
# File 'app/controllers/enumeration/enum_methods.rb', line 256 def enum_backup_folders opts = { list: ParsedCli.backup_folders_list, show_progression: user_interaction? } output('@info', msg: 'Enumerating Backup Folders') if user_interaction? output('backup_folders', backup_folders: target.backup_folders(opts)) end |
#enum_config_backups ⇒ Object
242 243 244 245 246 247 |
# File 'app/controllers/enumeration/enum_methods.rb', line 242 def enum_config_backups opts = { list: ParsedCli.config_backups_list, show_progression: user_interaction? } output('@info', msg: 'Enumerating Config Backups') if user_interaction? output('config_backups', config_backups: target.config_backups(opts)) end |
#enum_db_exports ⇒ Object
249 250 251 252 253 254 |
# File 'app/controllers/enumeration/enum_methods.rb', line 249 def enum_db_exports opts = { list: ParsedCli.db_exports_list, show_progression: user_interaction? } output('@info', msg: 'Enumerating DB Exports') if user_interaction? output('db_exports', db_exports: target.db_exports(opts)) end |
#enum_detection_message(detection_mode) ⇒ String
37 38 39 40 41 42 43 44 45 |
# File 'app/controllers/enumeration/enum_methods.rb', line 37 def (detection_mode) detection_method = if detection_mode == :mixed 'Passive and Aggressive' else detection_mode.to_s.capitalize end "(via #{detection_method} Methods)" end |
#enum_medias ⇒ Object
263 264 265 266 267 268 269 270 271 272 |
# File 'app/controllers/enumeration/enum_methods.rb', line 263 def enum_medias opts = { range: ParsedCli.enumerate[:medias], show_progression: user_interaction? } if user_interaction? output('@info', msg: 'Enumerating Medias (Permalink setting must be set to "Plain" for those to be detected)') end output('medias', medias: target.medias(opts)) end |
#enum_message(type, detection_mode) ⇒ String
Returns The related enumration message depending on the ParsedCli and type supplied.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'app/controllers/enumeration/enum_methods.rb', line 19 def (type, detection_mode) return unless %w[plugins themes].include?(type) enumerate = ParsedCli.enumerate || {} details = if enumerate[:"vulnerable_#{type}"] 'Vulnerable' elsif enumerate[:"all_#{type}"] 'All' else 'Most Popular' end "Enumerating #{details} #{type.capitalize} #{(detection_mode)}" end |
#enum_plugins ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'app/controllers/enumeration/enum_methods.rb', line 119 def enum_plugins opts = default_opts('plugins').merge( list: plugins_list_from_opts(ParsedCli.), threshold: ParsedCli.plugins_threshold, sort: true ) output('@info', msg: ('plugins', opts[:mode])) if user_interaction? enum_wp_items( 'plugin', target_method: :plugins, opts: opts, only_vulnerable: ParsedCli.enumerate&.dig(:vulnerable_plugins) ) end |
#enum_plugins?(opts) ⇒ Boolean
Returns Wether or not to enumerate the plugins.
113 114 115 116 117 |
# File 'app/controllers/enumeration/enum_methods.rb', line 113 def enum_plugins?(opts) return false if ParsedCli.wp_auth ParsedCli.plugins_list || opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins] end |
#enum_themes ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'app/controllers/enumeration/enum_methods.rb', line 159 def enum_themes opts = default_opts('themes').merge( list: themes_list_from_opts(ParsedCli.), threshold: ParsedCli.themes_threshold, sort: true ) output('@info', msg: ('themes', opts[:mode])) if user_interaction? enum_wp_items( 'theme', target_method: :themes, opts: opts, only_vulnerable: ParsedCli.enumerate&.dig(:vulnerable_themes) ) end |
#enum_themes?(opts) ⇒ Boolean
Returns Wether or not to enumerate the themes.
153 154 155 156 157 |
# File 'app/controllers/enumeration/enum_methods.rb', line 153 def enum_themes?(opts) return false if ParsedCli.wp_auth ParsedCli.themes_list || opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes] end |
#enum_timthumbs ⇒ Object
235 236 237 238 239 240 |
# File 'app/controllers/enumeration/enum_methods.rb', line 235 def enum_timthumbs opts = { list: ParsedCli.timthumbs_list, show_progression: user_interaction? } output('@info', msg: 'Enumerating Timthumbs') if user_interaction? output('timthumbs', timthumbs: target.timthumbs(opts)) end |
#enum_users ⇒ Object
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'app/controllers/enumeration/enum_methods.rb', line 281 def enum_users opts = default_opts('users').merge( range: enum_users_range, list: ParsedCli.users_list ) output('@info', msg: "Enumerating Users #{(opts[:mode])}") if user_interaction? stream = stream_findings? exclude = ParsedCli.exclude_usernames users = target.users(opts) do |user| next unless stream next if exclude&.match?(user.username) output('user', user: user) end || [] if stream output('@notice', msg: users.empty? ? 'No Users Found.' : "#{users.size} user(s) Identified.") else output('users', users: users) end end |
#enum_users?(opts) ⇒ Boolean
Returns Wether or not to enumerate the users.
277 278 279 |
# File 'app/controllers/enumeration/enum_methods.rb', line 277 def enum_users?(opts) opts[:users] || (ParsedCli.passwords && !ParsedCli.username && !ParsedCli.usernames) end |
#enum_users_range ⇒ Range
If the –enumerate is used, the default value is handled by the Option However, when using –passwords alone, the default has to be set by the code below
309 310 311 |
# File 'app/controllers/enumeration/enum_methods.rb', line 309 def enum_users_range ParsedCli.enumerate&.dig(:users) || cli_enum_choices[0].choices[:u].validate(nil) end |
#enum_wp_items(singular, target_method:, opts:, only_vulnerable:) ⇒ Object
Shared plugins/themes enumeration body. Streams per-item output when the active formatter supports it (and –no-stream wasn’t passed), otherwise batches the result and renders the plural view.
182 183 184 185 186 187 188 189 190 191 |
# File 'app/controllers/enumeration/enum_methods.rb', line 182 def enum_wp_items(singular, target_method:, opts:, only_vulnerable:) stream = stream_findings? items = target.send(target_method, opts) do |item| stream_wp_item(item, singular: singular, only_vulnerable: only_vulnerable) if stream end finalize_wp_items_output(items, singular: singular, opts: opts, stream: stream, only_vulnerable: only_vulnerable) end |
#finalize_wp_items_output(items, singular:, opts:, stream:, only_vulnerable:) ⇒ Object
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'app/controllers/enumeration/enum_methods.rb', line 193 def finalize_wp_items_output(items, singular:, opts:, stream:, only_vulnerable:) plural = "#{singular}s" if !stream && user_interaction? && !items.empty? mode_msg = (opts[:version_detection][:mode]) output('@info', msg: "Checking #{singular.capitalize} Versions #{mode_msg}") end items.each(&:version) unless stream items.select!(&:vulnerable?) if only_vulnerable if stream summary = items.empty? ? "No #{plural} Found." : "#{items.size} #{singular}(s) Identified." output('@notice', msg: summary) else output(plural, plural.to_sym => items) end end |
#plugins_list_from_opts(opts) ⇒ Array<String>
Returns The plugins list associated to the cli options.
137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/controllers/enumeration/enum_methods.rb', line 137 def plugins_list_from_opts(opts) # List file provided by the user via the cli return opts[:plugins_list] if opts[:plugins_list] if opts[:enumerate][:all_plugins] DB::Plugins.all_slugs elsif opts[:enumerate][:popular_plugins] DB::Plugins.popular_slugs else DB::Plugins.vulnerable_slugs end end |
#resolve_list_enumerate_collisions(enum) ⇒ Object
Resolves collisions between –plugins-list/–themes-list and the corresponding –enumerate choices. The list options take precedence; colliding enumerate keys are removed from the supplied hash and a notice is emitted for each ignored choice.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'app/controllers/enumeration/enum_methods.rb', line 70 def resolve_list_enumerate_collisions(enum) { plugins_list: %i[vulnerable_plugins all_plugins popular_plugins], themes_list: %i[vulnerable_themes all_themes popular_themes] }.each do |list_opt, enum_keys| next unless ParsedCli.send(list_opt) ignored = enum_keys.select { |k| enum.key?(k) } next if ignored.empty? ignored.each { |k| enum.delete(k) } output( '@notice', msg: "--#{list_opt.to_s.tr('_', '-')} provided; " \ "ignoring colliding --enumerate choice(s): #{ignored.join(', ')}" ) end end |
#run ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'app/controllers/enumeration.rb', line 30 def run enum = ParsedCli.enumerate || {} resolve_list_enumerate_collisions(enum) suppress_plugin_theme_choices_when_authenticated(enum) enum_plugins if enum_plugins?(enum) enum_themes if enum_themes?(enum) %i[timthumbs config_backups db_exports backup_folders medias].each do |key| send(:"enum_#{key}") if enum.key?(key) end enum_users if enum_users?(enum) end |
#stream_findings? ⇒ Boolean
Returns Whether enumeration findings should be streamed as they are discovered rather than batched at end of step. Streaming requires both a streaming-capable formatter (cli, cli_no_color, jsonl) and the user not having opted out via –no-stream.
11 12 13 |
# File 'app/controllers/enumeration/enum_methods.rb', line 11 def stream_findings? formatter.streams? && ParsedCli.stream != false end |
#stream_wp_item(item, singular:, only_vulnerable:) ⇒ Object
212 213 214 215 216 217 |
# File 'app/controllers/enumeration/enum_methods.rb', line 212 def stream_wp_item(item, singular:, only_vulnerable:) item.version return if only_vulnerable && !item.vulnerable? output(singular, singular.to_sym => item) end |
#suppress_plugin_theme_choices_when_authenticated(enum) ⇒ Object
Suppresses plugin/theme –enumerate choices and –plugins-list / –themes-list when –wp-auth was supplied, since AuthenticatedInventory already populated the target with authoritative data.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'app/controllers/enumeration/enum_methods.rb', line 95 def suppress_plugin_theme_choices_when_authenticated(enum) return unless ParsedCli.wp_auth suppressed = enum.keys & WP_AUTH_SUPPRESSED_CHOICES suppressed.each { |k| enum.delete(k) } lists_suppressed = %i[plugins_list themes_list].select { |opt| ParsedCli.send(opt) } return if suppressed.empty? && lists_suppressed.empty? ignored = (suppressed + lists_suppressed.map { |o| "--#{o.to_s.tr('_', '-')}" }).join(', ') output('@notice', msg: "--wp-auth provided; ignoring plugin/theme enumeration option(s): #{ignored} " \ '(authoritative inventory already retrieved via the WP REST API).') end |
#themes_list_from_opts(opts) ⇒ Array<String>
Returns The themes list associated to the cli options.
222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'app/controllers/enumeration/enum_methods.rb', line 222 def themes_list_from_opts(opts) # List file provided by the user via the cli return opts[:themes_list] if opts[:themes_list] if opts[:enumerate][:all_themes] DB::Themes.all_slugs elsif opts[:enumerate][:popular_themes] DB::Themes.popular_slugs else DB::Themes.vulnerable_slugs end end |