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 |