wpscan 3.8.28 → 4.0.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 +4 -4
- data/README.md +104 -30
- data/app/app.rb +26 -0
- data/app/controllers/aliases.rb +2 -2
- data/app/controllers/authenticated_inventory.rb +43 -0
- data/app/controllers/core/cli_options.rb +151 -0
- data/app/controllers/core.rb +200 -25
- data/app/controllers/custom_directories.rb +1 -1
- data/app/controllers/enumeration/cli_options.rb +21 -31
- data/app/controllers/enumeration/enum_methods.rb +145 -38
- data/app/controllers/enumeration.rb +26 -3
- data/app/controllers/interesting_findings.rb +25 -0
- data/app/controllers/main_theme.rb +1 -1
- data/app/controllers/password_attack.rb +14 -6
- data/app/controllers/vuln_api.rb +9 -3
- data/app/controllers/wp_version.rb +1 -1
- data/app/controllers.rb +1 -0
- data/app/finders/backup_folders/known_locations.rb +66 -0
- data/app/finders/backup_folders.rb +19 -0
- data/app/finders/config_backups/known_filenames.rb +6 -4
- data/app/finders/config_backups.rb +1 -1
- data/app/finders/db_exports/known_locations.rb +16 -14
- data/app/finders/db_exports.rb +1 -1
- data/app/finders/interesting_findings/backup_db.rb +1 -1
- data/app/finders/interesting_findings/debug_log.rb +1 -1
- data/app/finders/interesting_findings/duplicator_installer_log.rb +1 -1
- data/app/finders/interesting_findings/emergency_pwd_reset_script.rb +1 -1
- data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
- data/app/finders/interesting_findings/full_path_disclosure.rb +1 -1
- data/app/finders/interesting_findings/headers.rb +17 -0
- data/app/finders/interesting_findings/mu_plugins.rb +1 -1
- data/app/finders/interesting_findings/multisite.rb +1 -1
- data/app/finders/interesting_findings/php_disabled.rb +2 -2
- data/app/finders/interesting_findings/readme.rb +1 -1
- data/app/finders/interesting_findings/registration.rb +1 -1
- data/app/finders/interesting_findings/robots_txt.rb +20 -0
- data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
- data/app/finders/interesting_findings/tmm_db_migrate.rb +1 -1
- data/app/finders/interesting_findings/upload_directory_listing.rb +1 -1
- data/app/finders/interesting_findings/upload_sql_dump.rb +2 -2
- data/app/finders/interesting_findings/wp_cron.rb +1 -1
- data/app/finders/interesting_findings/xml_rpc.rb +61 -0
- data/app/finders/interesting_findings.rb +13 -4
- data/app/finders/main_theme/css_style_in_homepage.rb +1 -1
- data/app/finders/main_theme/urls_in_homepage.rb +3 -7
- data/app/finders/main_theme/woo_framework_meta_generator.rb +4 -4
- data/app/finders/main_theme.rb +1 -1
- data/app/finders/medias/attachment_brute_forcing.rb +2 -2
- data/app/finders/medias.rb +1 -1
- data/app/finders/passwords/wp_login.rb +2 -2
- data/app/finders/passwords/xml_rpc.rb +2 -2
- data/app/finders/passwords/xml_rpc_multicall.rb +1 -1
- data/app/finders/plugin_version/readme.rb +1 -1
- data/app/finders/plugin_version.rb +1 -1
- data/app/finders/plugins/known_locations.rb +17 -7
- data/app/finders/plugins/urls_in_homepage.rb +3 -7
- data/app/finders/plugins/wp_json_api.rb +85 -0
- data/app/finders/plugins.rb +2 -1
- data/app/finders/theme_version/style.rb +1 -1
- data/app/finders/theme_version/woo_framework_meta_generator.rb +1 -1
- data/app/finders/theme_version.rb +1 -1
- data/app/finders/themes/known_locations.rb +12 -6
- data/app/finders/themes/urls_in_homepage.rb +3 -7
- data/app/finders/themes/wp_json_api.rb +74 -0
- data/app/finders/themes.rb +2 -1
- data/app/finders/timthumb_version/bad_request.rb +1 -1
- data/app/finders/timthumb_version.rb +1 -1
- data/app/finders/timthumbs/known_locations.rb +6 -4
- data/app/finders/timthumbs.rb +1 -1
- data/app/finders/users/author_id_brute_forcing.rb +11 -7
- data/app/finders/users/author_posts.rb +1 -1
- data/app/finders/users/author_sitemap.rb +1 -1
- data/app/finders/users/login_error_messages.rb +1 -1
- data/app/finders/users/oembed_api.rb +3 -1
- data/app/finders/users/wp_json_api.rb +11 -7
- data/app/finders/users.rb +1 -1
- data/app/finders/wp_version/atom_generator.rb +1 -1
- data/app/finders/wp_version/rdf_generator.rb +1 -1
- data/app/finders/wp_version/readme.rb +1 -1
- data/app/finders/wp_version/rss_generator.rb +1 -1
- data/app/finders/wp_version/unique_fingerprinting.rb +2 -2
- data/app/finders/wp_version.rb +1 -1
- data/app/finders.rb +1 -0
- data/app/formatters/cli.rb +79 -0
- data/app/formatters/cli_no_color.rb +9 -0
- data/app/formatters/cli_no_colour.rb +17 -0
- data/app/formatters/json.rb +14 -0
- data/app/formatters/jsonl.rb +29 -0
- data/app/formatters/sarif.rb +311 -0
- data/app/models/backup_folder.rb +39 -0
- data/app/models/fantastico_fileslist.rb +34 -0
- data/app/models/headers.rb +44 -0
- data/app/models/interesting_finding.rb +41 -2
- data/app/models/plugin.rb +8 -2
- data/app/models/robots_txt.rb +31 -0
- data/app/models/search_replace_db_2.rb +17 -0
- data/app/models/theme.rb +9 -2
- data/app/models/timthumb.rb +2 -2
- data/app/models/user.rb +35 -0
- data/app/models/version.rb +49 -0
- data/app/models/wp_item/wordpress_org_data.rb +55 -0
- data/app/models/wp_item.rb +109 -9
- data/app/models/wp_version.rb +2 -2
- data/app/models/xml_rpc.rb +73 -3
- data/app/models.rb +2 -1
- data/app/user_agents.txt +46 -0
- data/app/views/cli/core/banner.erb +3 -3
- data/app/views/cli/core/finished.erb +15 -0
- data/app/views/cli/core/help.erb +4 -0
- data/app/views/cli/core/started.erb +11 -0
- data/app/views/cli/enumeration/backup_folders.erb +11 -0
- data/app/views/cli/enumeration/plugin.erb +13 -0
- data/app/views/cli/enumeration/plugins.erb +1 -12
- data/app/views/cli/enumeration/theme.erb +4 -0
- data/app/views/cli/enumeration/themes.erb +1 -3
- data/app/views/cli/enumeration/user.erb +4 -0
- data/app/views/cli/enumeration/users.erb +1 -3
- data/app/views/cli/finding.erb +1 -1
- data/app/views/cli/interesting_findings/_array.erb +10 -0
- data/app/views/cli/interesting_findings/findings.erb +23 -0
- data/app/views/cli/scan_aborted.erb +5 -0
- data/app/views/cli/update_aborted.erb +5 -0
- data/app/views/cli/vuln_api/status.erb +2 -0
- data/app/views/cli/vulnerability.erb +6 -0
- data/app/views/cli/wp_item.erb +4 -1
- data/app/views/json/core/banner.erb +2 -8
- data/app/views/json/core/finished.erb +13 -0
- data/app/views/json/core/help.erb +4 -0
- data/app/views/json/core/started.erb +10 -0
- data/app/views/json/enumeration/backup_folders.erb +11 -0
- data/app/views/json/enumeration/plugin.erb +15 -0
- data/app/views/json/enumeration/theme.erb +5 -0
- data/app/views/json/enumeration/user.erb +6 -0
- data/app/views/json/finding.erb +8 -2
- data/app/views/json/interesting_findings/findings.erb +24 -0
- data/app/views/json/notice.erb +1 -0
- data/app/views/json/scan_aborted.erb +5 -0
- data/app/views/json/update_aborted.erb +5 -0
- data/app/views/json/vuln_api/status.erb +2 -0
- data/app/views/json/wp_item.erb +4 -1
- data/bin/wpscan +1 -0
- data/lib/opt_parse_validator/config_files_loader_merger/base.rb +26 -0
- data/lib/opt_parse_validator/config_files_loader_merger/json.rb +17 -0
- data/lib/opt_parse_validator/config_files_loader_merger/yml.rb +17 -0
- data/lib/opt_parse_validator/config_files_loader_merger.rb +62 -0
- data/lib/opt_parse_validator/errors.rb +9 -0
- data/lib/opt_parse_validator/hacks.rb +19 -0
- data/lib/opt_parse_validator/opts/alias.rb +28 -0
- data/lib/opt_parse_validator/opts/array.rb +34 -0
- data/lib/opt_parse_validator/opts/base.rb +142 -0
- data/lib/opt_parse_validator/opts/boolean.rb +19 -0
- data/lib/opt_parse_validator/opts/choice.rb +43 -0
- data/lib/opt_parse_validator/opts/credentials.rb +15 -0
- data/lib/opt_parse_validator/opts/directory_path.rb +17 -0
- data/lib/opt_parse_validator/opts/file_path.rb +34 -0
- data/lib/opt_parse_validator/opts/headers.rb +33 -0
- data/lib/opt_parse_validator/opts/integer.rb +15 -0
- data/lib/opt_parse_validator/opts/integer_range.rb +37 -0
- data/lib/opt_parse_validator/opts/multi_choices.rb +135 -0
- data/lib/opt_parse_validator/opts/path.rb +78 -0
- data/lib/opt_parse_validator/opts/positive_integer.rb +16 -0
- data/lib/opt_parse_validator/opts/proxy.rb +7 -0
- data/lib/opt_parse_validator/opts/regexp.rb +14 -0
- data/lib/opt_parse_validator/opts/smart_list.rb +30 -0
- data/lib/opt_parse_validator/opts/string.rb +8 -0
- data/lib/opt_parse_validator/opts/uri.rb +41 -0
- data/lib/opt_parse_validator/opts/url.rb +11 -0
- data/lib/opt_parse_validator/opts.rb +9 -0
- data/lib/opt_parse_validator/version.rb +6 -0
- data/lib/opt_parse_validator.rb +161 -0
- data/lib/wpscan/browser/actions.rb +48 -0
- data/lib/wpscan/browser/options.rb +92 -0
- data/lib/wpscan/browser.rb +87 -2
- data/lib/wpscan/browser_authenticator.rb +64 -0
- data/lib/wpscan/cache/file_store.rb +77 -0
- data/lib/wpscan/cache/typhoeus.rb +25 -0
- data/lib/wpscan/controller.rb +100 -4
- data/lib/wpscan/controllers.rb +78 -3
- data/lib/wpscan/db/dynamic_finders/base.rb +3 -7
- data/lib/wpscan/db/dynamic_finders/plugin.rb +2 -2
- data/lib/wpscan/db/dynamic_finders/wordpress.rb +1 -1
- data/lib/wpscan/db/fingerprints.rb +2 -2
- data/lib/wpscan/db/updater.rb +23 -13
- data/lib/wpscan/db/vuln_api.rb +19 -7
- data/lib/wpscan/db/wp_item.rb +2 -2
- data/lib/wpscan/errors/enumeration.rb +4 -4
- data/lib/wpscan/errors/http.rb +82 -3
- data/lib/wpscan/errors/saml.rb +28 -0
- data/lib/wpscan/errors/scan.rb +14 -0
- data/lib/wpscan/errors/update.rb +11 -3
- data/lib/wpscan/errors/vuln_api.rb +24 -0
- data/lib/wpscan/errors/wordpress.rb +2 -2
- data/lib/wpscan/errors/wp_auth.rb +37 -0
- data/lib/wpscan/errors.rb +4 -3
- data/lib/wpscan/exit_code.rb +25 -0
- data/lib/wpscan/finders/base_finders.rb +45 -0
- data/lib/wpscan/finders/dynamic_finder/finder.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/comment.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +3 -5
- data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +1 -1
- data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +3 -3
- data/lib/wpscan/finders/dynamic_finder/wp_version.rb +1 -1
- data/lib/wpscan/finders/finder/breadth_first_dictionary_attack.rb +257 -0
- data/lib/wpscan/finders/finder/enumerator.rb +77 -0
- data/lib/wpscan/finders/finder/fingerprinter.rb +48 -0
- data/lib/wpscan/finders/finder/smart_url_checker/findings.rb +33 -0
- data/lib/wpscan/finders/finder/smart_url_checker.rb +60 -0
- data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +1 -1
- data/lib/wpscan/finders/finder.rb +78 -0
- data/lib/wpscan/finders/finding.rb +54 -0
- data/lib/wpscan/finders/findings.rb +33 -0
- data/lib/wpscan/finders/independent_finder.rb +33 -0
- data/lib/wpscan/finders/independent_finders.rb +26 -0
- data/lib/wpscan/finders/same_type_finder.rb +19 -0
- data/lib/wpscan/finders/same_type_finders.rb +28 -0
- data/lib/wpscan/finders/unique_finder.rb +19 -0
- data/lib/wpscan/finders/unique_finders.rb +47 -0
- data/lib/wpscan/finders.rb +11 -12
- data/lib/wpscan/formatter/buffer.rb +17 -0
- data/lib/wpscan/formatter.rb +152 -0
- data/lib/wpscan/helper.rb +7 -1
- data/lib/wpscan/http_status_tracking.rb +128 -0
- data/lib/wpscan/numeric.rb +13 -0
- data/lib/wpscan/parsed_cli.rb +31 -2
- data/lib/wpscan/progressbar_null_output.rb +23 -0
- data/lib/wpscan/public_suffix/domain.rb +44 -0
- data/lib/wpscan/references.rb +118 -4
- data/lib/wpscan/scan.rb +127 -0
- data/lib/wpscan/target/hashes.rb +45 -0
- data/lib/wpscan/target/platform/php.rb +124 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +3 -3
- data/lib/wpscan/target/platform/wordpress.rb +7 -8
- data/lib/wpscan/target/platform.rb +3 -0
- data/lib/wpscan/target/scope.rb +103 -0
- data/lib/wpscan/target/server/apache.rb +27 -0
- data/lib/wpscan/target/server/generic.rb +72 -0
- data/lib/wpscan/target/server/iis.rb +29 -0
- data/lib/wpscan/target/server/nginx.rb +27 -0
- data/lib/wpscan/target/server.rb +6 -0
- data/lib/wpscan/target.rb +129 -9
- data/lib/wpscan/typhoeus/hydra.rb +12 -0
- data/lib/wpscan/typhoeus/response.rb +24 -1
- data/lib/wpscan/version.rb +1 -1
- data/lib/wpscan/vulnerability.rb +49 -3
- data/lib/wpscan/vulnerability_filter.rb +68 -0
- data/lib/wpscan/vulnerable.rb +13 -1
- data/lib/wpscan/web_site.rb +152 -0
- data/lib/wpscan.rb +126 -29
- metadata +362 -20
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the Integer Option
|
|
5
|
+
class OptInteger < OptBase
|
|
6
|
+
# @param [ String ] value
|
|
7
|
+
#
|
|
8
|
+
# @return [ Integer ]
|
|
9
|
+
def validate(value)
|
|
10
|
+
raise Error, "#{value} is not an integer" if value.to_i.to_s != value
|
|
11
|
+
|
|
12
|
+
value.to_i
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the Integer Range Option
|
|
5
|
+
class OptIntegerRange < OptBase
|
|
6
|
+
# @return [ Void ]
|
|
7
|
+
def append_help_messages
|
|
8
|
+
option << "Range separator to use: '#{separator}'"
|
|
9
|
+
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param [ String ] value
|
|
14
|
+
#
|
|
15
|
+
# @return [ Range ]
|
|
16
|
+
def validate(value)
|
|
17
|
+
a = super.split(separator)
|
|
18
|
+
|
|
19
|
+
raise Error, "Incorrect number of ranges found: #{a.size}, should be 2" unless a.size == 2
|
|
20
|
+
|
|
21
|
+
first_integer = a.first.to_i
|
|
22
|
+
last_integer = a.last.to_i
|
|
23
|
+
|
|
24
|
+
unless first_integer.to_s == a.first && last_integer.to_s == a.last
|
|
25
|
+
raise Error,
|
|
26
|
+
'Argument is not a valid integer range'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
(first_integer..last_integer)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [ String ]
|
|
33
|
+
def separator
|
|
34
|
+
attrs[:separator] || '-'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the MultiChoices Option
|
|
5
|
+
class OptMultiChoices < OptArray
|
|
6
|
+
# @param [ Array ] option See OptBase#new
|
|
7
|
+
# @param [ Hash ] attrs
|
|
8
|
+
# @option attrs [ Hash ] :choices
|
|
9
|
+
# @option attrs [ Array<Array> ] :incompatible
|
|
10
|
+
# @options attrs [ String ] :separator See OptArray#new
|
|
11
|
+
def initialize(option, attrs = {})
|
|
12
|
+
raise Error, 'The :choices attribute is mandatory' unless attrs.key?(:choices)
|
|
13
|
+
raise Error, 'The :choices attribute must be a hash' unless attrs[:choices].is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def append_help_messages
|
|
19
|
+
option << 'Available Choices:'
|
|
20
|
+
|
|
21
|
+
append_choices_help_messages
|
|
22
|
+
|
|
23
|
+
super
|
|
24
|
+
|
|
25
|
+
append_incompatible_help_messages
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def append_choices_help_messages
|
|
29
|
+
max_spaces = choices.keys.max_by(&:size).size
|
|
30
|
+
|
|
31
|
+
choices.each do |key, opt|
|
|
32
|
+
first_line_prefix = " #{key} #{' ' * (max_spaces - key.length)}"
|
|
33
|
+
other_lines_prefix = ' ' * first_line_prefix.size
|
|
34
|
+
|
|
35
|
+
opt_help_messages(opt).each_with_index do |message, index|
|
|
36
|
+
option << "#{index.zero? ? first_line_prefix : other_lines_prefix} #{message}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def help_message_for_default
|
|
42
|
+
msg = +''
|
|
43
|
+
|
|
44
|
+
default.each do |key, value|
|
|
45
|
+
msg << if value == true
|
|
46
|
+
key.to_s.titleize
|
|
47
|
+
else
|
|
48
|
+
"#{key.to_s.titleize}: #{value}"
|
|
49
|
+
end
|
|
50
|
+
msg << ', '
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
msg.chomp(', ')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param [ OptBase ] opt
|
|
57
|
+
#
|
|
58
|
+
# @return [ Array<String> ]
|
|
59
|
+
def opt_help_messages(opt)
|
|
60
|
+
opt.help_messages.empty? ? [opt.to_s.humanize] : opt.help_messages
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def append_incompatible_help_messages
|
|
64
|
+
return if incompatible.empty?
|
|
65
|
+
|
|
66
|
+
option << 'Incompatible choices (only one of each group/s can be used):'
|
|
67
|
+
|
|
68
|
+
incompatible.each do |a|
|
|
69
|
+
option << " - #{a.join(', ')}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param [ String ] value
|
|
74
|
+
#
|
|
75
|
+
# @return [ Hash ]
|
|
76
|
+
def validate(value)
|
|
77
|
+
results = {}
|
|
78
|
+
|
|
79
|
+
super.each do |item|
|
|
80
|
+
opt = choices[item.to_sym]
|
|
81
|
+
|
|
82
|
+
if opt
|
|
83
|
+
opt_value = opt.value_if_empty.nil? || opt.value_if_empty
|
|
84
|
+
else
|
|
85
|
+
opt, opt_value = value_from_pattern(item)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
results[opt.to_sym] = opt.normalize(opt.validate(opt_value))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
verify_compatibility(results)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [ Array ]
|
|
95
|
+
def value_from_pattern(item)
|
|
96
|
+
choices.each do |key, opt|
|
|
97
|
+
next unless item =~ /\A#{key}(.*)\z/
|
|
98
|
+
|
|
99
|
+
return [opt, Regexp.last_match[1]]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raise Error, "Unknown choice: #{item}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [ Array<Array<Symbol>> ]
|
|
106
|
+
def incompatible
|
|
107
|
+
Array(attrs[:incompatible])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @param [ Hash ] values
|
|
111
|
+
#
|
|
112
|
+
# @return [ Hash ]
|
|
113
|
+
def verify_compatibility(values)
|
|
114
|
+
incompatible.each do |a|
|
|
115
|
+
last_match = ''
|
|
116
|
+
|
|
117
|
+
a.each do |key|
|
|
118
|
+
sym = choices[key].to_sym
|
|
119
|
+
|
|
120
|
+
next unless values.key?(sym)
|
|
121
|
+
|
|
122
|
+
raise Error, "Incompatible choices detected: #{last_match}, #{key}" unless last_match.empty?
|
|
123
|
+
|
|
124
|
+
last_match = key
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
values
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# No normalization
|
|
131
|
+
def normalize(value)
|
|
132
|
+
value
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the Path Option
|
|
5
|
+
class OptPath < OptBase
|
|
6
|
+
# Initialize attrs:
|
|
7
|
+
#
|
|
8
|
+
# :create if set to true, will create the path
|
|
9
|
+
#
|
|
10
|
+
# :exists if set to false, will ignore the file? and directory? checks
|
|
11
|
+
#
|
|
12
|
+
# :file Check if the path is a file
|
|
13
|
+
# :directory Check if the path is a directory
|
|
14
|
+
#
|
|
15
|
+
# :executable
|
|
16
|
+
# :readable
|
|
17
|
+
# :writable
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
# @param [ String ] value
|
|
21
|
+
#
|
|
22
|
+
# @return [ Pathname ]
|
|
23
|
+
def validate(value)
|
|
24
|
+
path = Pathname.new(value)
|
|
25
|
+
|
|
26
|
+
allowed_attrs.each do |key|
|
|
27
|
+
method = "check_#{key}"
|
|
28
|
+
send(method, path) if respond_to?(method) && attrs[key]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def allowed_attrs
|
|
35
|
+
%i[create file directory executable readable writable]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# check_create is implemented in the file_path and directory_path opts
|
|
39
|
+
|
|
40
|
+
# @param [ Pathname ] path
|
|
41
|
+
def check_file(path)
|
|
42
|
+
raise Error, "The path '#{path}' does not exist or is not a file" unless path.file? || attrs[:exists] == false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param [ Pathname ] path
|
|
46
|
+
def check_directory(path)
|
|
47
|
+
return if path.directory? || attrs[:exists] == false
|
|
48
|
+
|
|
49
|
+
raise Error,
|
|
50
|
+
"The path '#{path}' does not exist or is not a directory"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param [ Pathname ] path
|
|
54
|
+
def check_executable(path)
|
|
55
|
+
raise Error, "The path '#{path}' is not executable#{process_identity}" unless path.executable?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param [ Pathname ] path
|
|
59
|
+
def check_readable(path)
|
|
60
|
+
raise Error, "The path '#{path}' is not readable#{process_identity}" unless path.readable?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# If the path does not exist, it will check for the parent
|
|
64
|
+
# directory write permission
|
|
65
|
+
#
|
|
66
|
+
# @param [ Pathname ] path
|
|
67
|
+
def check_writable(path)
|
|
68
|
+
return unless (path.exist? && !path.writable?) || !path.parent.writable?
|
|
69
|
+
|
|
70
|
+
raise Error, "The path '#{path}' is not writable#{process_identity}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [ String ] Current process uid/gid, formatted for inclusion in error messages
|
|
74
|
+
def process_identity
|
|
75
|
+
" (uid=#{Process.uid}, gid=#{Process.gid})"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the Positive Integer Option
|
|
5
|
+
class OptPositiveInteger < OptInteger
|
|
6
|
+
# @param [ String ] value
|
|
7
|
+
#
|
|
8
|
+
# @return [ Integer ]
|
|
9
|
+
def validate(value)
|
|
10
|
+
i = super
|
|
11
|
+
raise Error, "#{i} is not > 0" unless i.positive?
|
|
12
|
+
|
|
13
|
+
i
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the Regexp Option
|
|
5
|
+
# See http://ruby-doc.org/core-2.2.1/Regexp.html#method-c-new for expected values in :options
|
|
6
|
+
class OptRegexp < OptBase
|
|
7
|
+
# @param [ String ] value
|
|
8
|
+
#
|
|
9
|
+
# @return [ Regexp ]
|
|
10
|
+
def validate(value)
|
|
11
|
+
Regexp.new(super, attrs[:options])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the SmartList Option
|
|
5
|
+
# Such option allow users to supply a list like
|
|
6
|
+
# - name1
|
|
7
|
+
# - name1,name2,name3
|
|
8
|
+
# - /tmp/names.txt
|
|
9
|
+
class OptSmartList < OptArray
|
|
10
|
+
# @return [ Void ]
|
|
11
|
+
def append_help_messages
|
|
12
|
+
super
|
|
13
|
+
# removes the help message from OptArray about the separator as useless here
|
|
14
|
+
# can't use option as it's an attr_reader only
|
|
15
|
+
@option -= ["Separator to use between the values: '#{separator}'"]
|
|
16
|
+
|
|
17
|
+
option << "Examples: 'a1', '#{%w[a1 a2 a3].join(separator)}', '/tmp/a.txt'"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [ String ] value
|
|
21
|
+
#
|
|
22
|
+
# @return [ Array<String> ]
|
|
23
|
+
def validate(value)
|
|
24
|
+
# Might be a better way to do this especially with a big file
|
|
25
|
+
File.open(value) { |f| f.map(&:chomp) }
|
|
26
|
+
rescue Errno::ENOENT, Errno::ENAMETOOLONG
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OptParseValidator
|
|
4
|
+
# Implementation of the URI Option
|
|
5
|
+
class OptURI < OptBase
|
|
6
|
+
# return [ Void ]
|
|
7
|
+
def append_help_messages
|
|
8
|
+
option << "Allowed Protocols: #{allowed_protocols.join(', ')}" unless allowed_protocols.empty?
|
|
9
|
+
option << "Default Protocol if none provided: #{default_protocol}" if default_protocol
|
|
10
|
+
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [ Array<String> ]
|
|
15
|
+
def allowed_protocols
|
|
16
|
+
@allowed_protocols ||= Array(attrs[:protocols]).map(&:downcase)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The default protocol (or scheme) to use if none was given
|
|
20
|
+
def default_protocol
|
|
21
|
+
@default_protocol ||= attrs[:default_protocol]&.downcase
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [ String ] value
|
|
25
|
+
#
|
|
26
|
+
# @return [ String ]
|
|
27
|
+
def validate(value)
|
|
28
|
+
uri = Addressable::URI.parse(value)
|
|
29
|
+
|
|
30
|
+
uri = Addressable::URI.parse("#{default_protocol}://#{value}") if !uri.scheme && default_protocol
|
|
31
|
+
|
|
32
|
+
unless allowed_protocols.empty? || allowed_protocols.include?(uri.scheme&.downcase)
|
|
33
|
+
# For future refs: will have to check if the uri.scheme exists,
|
|
34
|
+
# otherwise it means that the value was empty
|
|
35
|
+
raise Addressable::URI::InvalidURIError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
uri.to_s
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
%w[
|
|
4
|
+
base string integer positive_integer choice boolean uri url proxy credentials
|
|
5
|
+
path file_path directory_path array integer_range multi_choices regexp headers
|
|
6
|
+
smart_list alias
|
|
7
|
+
].each do |opt|
|
|
8
|
+
require "opt_parse_validator/opts/#{opt}"
|
|
9
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Gems
|
|
4
|
+
require 'addressable/uri'
|
|
5
|
+
require 'active_support/inflector'
|
|
6
|
+
require 'active_support/core_ext/hash'
|
|
7
|
+
# Standard Libs
|
|
8
|
+
require 'optparse'
|
|
9
|
+
require 'pathname'
|
|
10
|
+
# Custom Libs
|
|
11
|
+
require 'opt_parse_validator/errors'
|
|
12
|
+
require 'opt_parse_validator/hacks'
|
|
13
|
+
require 'opt_parse_validator/opts'
|
|
14
|
+
require 'opt_parse_validator/version'
|
|
15
|
+
require 'opt_parse_validator/config_files_loader_merger' # Could even create a gem out of it, as completely independent
|
|
16
|
+
|
|
17
|
+
# Gem namespace
|
|
18
|
+
module OptParseValidator
|
|
19
|
+
# Validator
|
|
20
|
+
class OptParser < OptionParser
|
|
21
|
+
attr_reader :symbols_used, :opts
|
|
22
|
+
|
|
23
|
+
def initialize(banner = nil, width = 32, indent = ' ' * 4)
|
|
24
|
+
@results = {}
|
|
25
|
+
@symbols_used = []
|
|
26
|
+
@opts = []
|
|
27
|
+
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [ OptParseValidator::ConfigFilesLoaderMerger ]
|
|
32
|
+
def config_files
|
|
33
|
+
@config_files ||= ConfigFilesLoaderMerger.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param [ Array<OptBase> ] options
|
|
37
|
+
#
|
|
38
|
+
# @return [ Self ] For chaining #new.add.results
|
|
39
|
+
def add(*options)
|
|
40
|
+
options.each do |option|
|
|
41
|
+
check_option(option)
|
|
42
|
+
|
|
43
|
+
@opts << option
|
|
44
|
+
@symbols_used << option.to_sym
|
|
45
|
+
|
|
46
|
+
# Set the default option value if it exists
|
|
47
|
+
# The default value is not validated as it is provided by devs
|
|
48
|
+
# and should be set to the correct format/value directly
|
|
49
|
+
@results[option.to_sym] = option.default unless option.default.nil?
|
|
50
|
+
|
|
51
|
+
register_callback(option)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [ Hash ]
|
|
58
|
+
def results(argv = default_argv)
|
|
59
|
+
load_config_files
|
|
60
|
+
parse!(argv)
|
|
61
|
+
post_processing
|
|
62
|
+
|
|
63
|
+
@results
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
# Raise it as an OptParseValidator::Error if not already one
|
|
66
|
+
raise e.is_a?(Error) ? e.class : Error, e.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [ String ] The simplified help (without any of the advanced option/s listed)
|
|
70
|
+
def simple_help
|
|
71
|
+
help = to_s
|
|
72
|
+
|
|
73
|
+
# Removes all advanced help messages
|
|
74
|
+
@opts.select(&:advanced?).each do |opt|
|
|
75
|
+
messages_pattern = //
|
|
76
|
+
|
|
77
|
+
opt.help_messages.each do |msg|
|
|
78
|
+
messages_pattern = /#{messages_pattern}\s*#{Regexp.escape(msg)}/
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
pattern = /\s*#{Regexp.escape(opt.option[0..1].select { |o| o[0] == '-' }.join(', '))}#{messages_pattern}/
|
|
82
|
+
|
|
83
|
+
help.gsub!(pattern, '')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
help
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [ String ] The full help, with the advanced option/s listed
|
|
90
|
+
def full_help
|
|
91
|
+
to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
protected
|
|
95
|
+
|
|
96
|
+
# Ensures the opt given is valid
|
|
97
|
+
#
|
|
98
|
+
# @param [ OptBase ] opt
|
|
99
|
+
#
|
|
100
|
+
# @return [ void ]
|
|
101
|
+
def check_option(opt)
|
|
102
|
+
raise Error, "The option is not an OptBase, #{opt.class} supplied" unless opt.is_a?(OptBase)
|
|
103
|
+
raise Error, "The option #{opt.to_sym} is already used !" if @symbols_used.include?(opt.to_sym)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @param [ OptBase ] opt
|
|
107
|
+
#
|
|
108
|
+
# @return [ void ]
|
|
109
|
+
def register_callback(opt)
|
|
110
|
+
on(*opt.option) do |arg|
|
|
111
|
+
if opt.alias?
|
|
112
|
+
parse!(opt.alias_for.split)
|
|
113
|
+
else
|
|
114
|
+
@results[opt.to_sym] = opt.normalize(opt.validate(arg))
|
|
115
|
+
end
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
# Adds the long option name to the message
|
|
118
|
+
# And raises it as an OptParseValidator::Error if not already one
|
|
119
|
+
# e.g --proxy Invalid Scheme format.
|
|
120
|
+
raise e.is_a?(Error) ? e.class : Error, "#{opt.to_long} #{e}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [ Void ]
|
|
125
|
+
def load_config_files
|
|
126
|
+
files_data = config_files.parse
|
|
127
|
+
|
|
128
|
+
@opts.each do |opt|
|
|
129
|
+
next unless files_data.key?(opt.to_sym)
|
|
130
|
+
|
|
131
|
+
begin
|
|
132
|
+
@results[opt.to_sym] = opt.normalize(opt.validate(files_data[opt.to_sym].to_s))
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
# Adds the long option name to the message
|
|
135
|
+
# And raises it as an OptParseValidator::Error if not already one
|
|
136
|
+
# e.g --proxy Invalid Scheme format.
|
|
137
|
+
raise e.is_a?(Error) ? e.class : Error, "#{opt.to_long} #{e}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Ensure that all required options are supplied
|
|
143
|
+
# Should be overriden to modify the behavior
|
|
144
|
+
#
|
|
145
|
+
# @return [ Void ]
|
|
146
|
+
def post_processing
|
|
147
|
+
@opts.each do |opt|
|
|
148
|
+
raise NoRequiredOption, "The option #{opt.to_long} is required" if opt.required? && !@results.key?(opt.to_sym)
|
|
149
|
+
|
|
150
|
+
next if opt.required_unless.empty? || @results.key?(opt.to_sym)
|
|
151
|
+
|
|
152
|
+
fail_msg = 'One of the following options is required: ' \
|
|
153
|
+
"#{opt.to_long}, --#{opt.required_unless.join(', --').tr('_', '-')}"
|
|
154
|
+
|
|
155
|
+
raise NoRequiredOption, fail_msg unless opt.required_unless.any? do |sym|
|
|
156
|
+
@results.key?(sym)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WPScan
|
|
4
|
+
class Browser
|
|
5
|
+
# Browser Actions (get, post etc)
|
|
6
|
+
module Actions
|
|
7
|
+
# @param [ String ] url
|
|
8
|
+
# @param [ Hash ] params
|
|
9
|
+
#
|
|
10
|
+
# @return [ Typhoeus::Request ]
|
|
11
|
+
def forge_request(url, params = {})
|
|
12
|
+
WPScan::Browser.instance.forge_request(url, params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param [ String ] url
|
|
16
|
+
# @param [ Hash ] params
|
|
17
|
+
#
|
|
18
|
+
# @return [ Typhoeus::Response ]
|
|
19
|
+
def get(url, params = {})
|
|
20
|
+
forge_request(url, params.merge(method: :get)).run
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param [ String ] url
|
|
24
|
+
# @param [ Hash ] params
|
|
25
|
+
#
|
|
26
|
+
# @return [ Typhoeus::Response ]
|
|
27
|
+
def post(url, params = {})
|
|
28
|
+
forge_request(url, params.merge(method: :post)).run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param [ String ] url
|
|
32
|
+
# @param [ Hash ] params
|
|
33
|
+
#
|
|
34
|
+
# @return [ Typhoeus::Response ]
|
|
35
|
+
def head(url, params = {})
|
|
36
|
+
forge_request(url, params.merge(method: :head)).run
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param [ String ] url
|
|
40
|
+
# @param [ Hash ] params
|
|
41
|
+
#
|
|
42
|
+
# @return [ Typhoeus::Response ]
|
|
43
|
+
def get_and_follow_location(url, params = {})
|
|
44
|
+
get(url, { followlocation: true, maxredirs: 3 }.merge(params))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|