wpscan 3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|