wpscan 3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile.lock +139 -0
- data/LICENSE +74 -0
- data/README.md +146 -0
- data/app/app.rb +3 -0
- data/app/controllers.rb +6 -0
- data/app/controllers/brute_force.rb +126 -0
- data/app/controllers/core.rb +104 -0
- data/app/controllers/custom_directories.rb +23 -0
- data/app/controllers/enumeration.rb +53 -0
- data/app/controllers/enumeration/cli_options.rb +126 -0
- data/app/controllers/enumeration/enum_methods.rb +157 -0
- data/app/controllers/main_theme.rb +27 -0
- data/app/controllers/wp_version.rb +30 -0
- data/app/finders.rb +13 -0
- data/app/finders/config_backups.rb +17 -0
- data/app/finders/config_backups/known_filenames.rb +46 -0
- data/app/finders/interesting_findings.rb +33 -0
- data/app/finders/interesting_findings/backup_db.rb +25 -0
- data/app/finders/interesting_findings/debug_log.rb +20 -0
- data/app/finders/interesting_findings/duplicator_installer_log.rb +23 -0
- data/app/finders/interesting_findings/full_path_disclosure.rb +23 -0
- data/app/finders/interesting_findings/mu_plugins.rb +48 -0
- data/app/finders/interesting_findings/multisite.rb +29 -0
- data/app/finders/interesting_findings/readme.rb +26 -0
- data/app/finders/interesting_findings/registration.rb +31 -0
- data/app/finders/interesting_findings/tmm_db_migrate.rb +24 -0
- data/app/finders/interesting_findings/upload_directory_listing.rb +24 -0
- data/app/finders/interesting_findings/upload_sql_dump.rb +28 -0
- data/app/finders/main_theme.rb +22 -0
- data/app/finders/main_theme/css_style.rb +43 -0
- data/app/finders/main_theme/urls_in_homepage.rb +25 -0
- data/app/finders/main_theme/woo_framework_meta_generator.rb +22 -0
- data/app/finders/medias.rb +17 -0
- data/app/finders/medias/attachment_brute_forcing.rb +44 -0
- data/app/finders/plugin_version.rb +44 -0
- data/app/finders/plugin_version/layer_slider/translation_file.rb +40 -0
- data/app/finders/plugin_version/readme.rb +79 -0
- data/app/finders/plugin_version/revslider/release_log.rb +35 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +27 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +31 -0
- data/app/finders/plugin_version/w3_total_cache/headers.rb +28 -0
- data/app/finders/plugins.rb +24 -0
- data/app/finders/plugins/comments.rb +31 -0
- data/app/finders/plugins/headers.rb +36 -0
- data/app/finders/plugins/known_locations.rb +48 -0
- data/app/finders/plugins/urls_in_homepage.rb +29 -0
- data/app/finders/theme_version.rb +41 -0
- data/app/finders/theme_version/style.rb +43 -0
- data/app/finders/theme_version/woo_framework_meta_generator.rb +19 -0
- data/app/finders/themes.rb +20 -0
- data/app/finders/themes/known_locations.rb +48 -0
- data/app/finders/themes/urls_in_homepage.rb +23 -0
- data/app/finders/timthumb_version.rb +17 -0
- data/app/finders/timthumb_version/bad_request.rb +21 -0
- data/app/finders/timthumbs.rb +17 -0
- data/app/finders/timthumbs/known_locations.rb +56 -0
- data/app/finders/users.rb +24 -0
- data/app/finders/users/author_id_brute_forcing.rb +111 -0
- data/app/finders/users/author_posts.rb +61 -0
- data/app/finders/users/login_error_messages.rb +50 -0
- data/app/finders/users/wp_json_api.rb +31 -0
- data/app/finders/wp_items.rb +1 -0
- data/app/finders/wp_items/urls_in_homepage.rb +68 -0
- data/app/finders/wp_version.rb +34 -0
- data/app/finders/wp_version/atom_generator.rb +40 -0
- data/app/finders/wp_version/meta_generator.rb +27 -0
- data/app/finders/wp_version/opml_generator.rb +23 -0
- data/app/finders/wp_version/rdf_generator.rb +38 -0
- data/app/finders/wp_version/readme.rb +28 -0
- data/app/finders/wp_version/rss_generator.rb +43 -0
- data/app/finders/wp_version/sitemap_generator.rb +23 -0
- data/app/finders/wp_version/stylesheets.rb +55 -0
- data/app/finders/wp_version/unique_fingerprinting.rb +64 -0
- data/app/models.rb +10 -0
- data/app/models/config_backup.rb +5 -0
- data/app/models/interesting_finding.rb +6 -0
- data/app/models/media.rb +5 -0
- data/app/models/plugin.rb +25 -0
- data/app/models/theme.rb +99 -0
- data/app/models/timthumb.rb +74 -0
- data/app/models/user.rb +31 -0
- data/app/models/wp_item.rb +142 -0
- data/app/models/wp_version.rb +49 -0
- data/app/models/xml_rpc.rb +19 -0
- data/app/views/cli/brute_force/error.erb +1 -0
- data/app/views/cli/brute_force/found.erb +2 -0
- data/app/views/cli/brute_force/users.erb +9 -0
- data/app/views/cli/core/banner.erb +14 -0
- data/app/views/cli/core/db_update_finished.erb +8 -0
- data/app/views/cli/core/db_update_started.erb +1 -0
- data/app/views/cli/core/not_fully_configured.erb +1 -0
- data/app/views/cli/enumeration/config_backups.erb +11 -0
- data/app/views/cli/enumeration/medias.erb +11 -0
- data/app/views/cli/enumeration/plugins.erb +35 -0
- data/app/views/cli/enumeration/themes.erb +11 -0
- data/app/views/cli/enumeration/timthumbs.erb +18 -0
- data/app/views/cli/enumeration/users.erb +11 -0
- data/app/views/cli/finding.erb +32 -0
- data/app/views/cli/info.erb +1 -0
- data/app/views/cli/main_theme/theme.erb +6 -0
- data/app/views/cli/notice.erb +1 -0
- data/app/views/cli/theme.erb +64 -0
- data/app/views/cli/usage.erb +3 -0
- data/app/views/cli/vulnerability.erb +14 -0
- data/app/views/cli/wp_version/version.erb +6 -0
- data/app/views/json/brute_force/users.erb +10 -0
- data/app/views/json/core/banner.erb +12 -0
- data/app/views/json/core/db_update_finished.erb +2 -0
- data/app/views/json/core/db_update_started.erb +1 -0
- data/app/views/json/core/not_fully_configured.erb +1 -0
- data/app/views/json/enumeration/config_backups.erb +10 -0
- data/app/views/json/enumeration/medias.erb +10 -0
- data/app/views/json/enumeration/plugins.erb +25 -0
- data/app/views/json/enumeration/themes.erb +10 -0
- data/app/views/json/enumeration/timthumbs.erb +19 -0
- data/app/views/json/enumeration/users.erb +11 -0
- data/app/views/json/finding.erb +26 -0
- data/app/views/json/main_theme/theme.erb +7 -0
- data/app/views/json/theme.erb +38 -0
- data/app/views/json/wp_version/version.erb +8 -0
- data/bin/wpscan +15 -0
- data/coverage/assets/0.10.0/application.css +799 -0
- data/coverage/assets/0.10.0/application.js +1707 -0
- data/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/coverage/assets/0.10.0/loading.gif +0 -0
- data/coverage/assets/0.10.0/magnify.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +27510 -0
- data/lib/wpscan.rb +44 -0
- data/lib/wpscan/browser.rb +16 -0
- data/lib/wpscan/controller.rb +8 -0
- data/lib/wpscan/controllers.rb +8 -0
- data/lib/wpscan/db.rb +28 -0
- data/lib/wpscan/db/dynamic_finders.rb +63 -0
- data/lib/wpscan/db/plugin.rb +11 -0
- data/lib/wpscan/db/plugins.rb +11 -0
- data/lib/wpscan/db/schema.rb +39 -0
- data/lib/wpscan/db/theme.rb +11 -0
- data/lib/wpscan/db/themes.rb +11 -0
- data/lib/wpscan/db/updater.rb +148 -0
- data/lib/wpscan/db/wp_item.rb +18 -0
- data/lib/wpscan/db/wp_items.rb +21 -0
- data/lib/wpscan/db/wp_version.rb +11 -0
- data/lib/wpscan/errors/http.rb +34 -0
- data/lib/wpscan/errors/update.rb +8 -0
- data/lib/wpscan/errors/wordpress.rb +22 -0
- data/lib/wpscan/finders.rb +14 -0
- data/lib/wpscan/finders/finder/plugin_version/comments.rb +25 -0
- data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +23 -0
- data/lib/wpscan/helper.rb +6 -0
- data/lib/wpscan/references.rb +31 -0
- data/lib/wpscan/target.rb +81 -0
- data/lib/wpscan/target/platform/wordpress.rb +74 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +93 -0
- data/lib/wpscan/version.rb +4 -0
- data/lib/wpscan/vulnerability.rb +25 -0
- data/lib/wpscan/vulnerable.rb +10 -0
- data/wpscan-v3.sublime-project +8 -0
- data/wpscan-v3.sublime-workspace +895 -0
- data/wpscan.gemspec +55 -0
- 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
|