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.
Files changed (252) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +104 -30
  3. data/app/app.rb +26 -0
  4. data/app/controllers/aliases.rb +2 -2
  5. data/app/controllers/authenticated_inventory.rb +43 -0
  6. data/app/controllers/core/cli_options.rb +151 -0
  7. data/app/controllers/core.rb +200 -25
  8. data/app/controllers/custom_directories.rb +1 -1
  9. data/app/controllers/enumeration/cli_options.rb +21 -31
  10. data/app/controllers/enumeration/enum_methods.rb +145 -38
  11. data/app/controllers/enumeration.rb +26 -3
  12. data/app/controllers/interesting_findings.rb +25 -0
  13. data/app/controllers/main_theme.rb +1 -1
  14. data/app/controllers/password_attack.rb +14 -6
  15. data/app/controllers/vuln_api.rb +9 -3
  16. data/app/controllers/wp_version.rb +1 -1
  17. data/app/controllers.rb +1 -0
  18. data/app/finders/backup_folders/known_locations.rb +66 -0
  19. data/app/finders/backup_folders.rb +19 -0
  20. data/app/finders/config_backups/known_filenames.rb +6 -4
  21. data/app/finders/config_backups.rb +1 -1
  22. data/app/finders/db_exports/known_locations.rb +16 -14
  23. data/app/finders/db_exports.rb +1 -1
  24. data/app/finders/interesting_findings/backup_db.rb +1 -1
  25. data/app/finders/interesting_findings/debug_log.rb +1 -1
  26. data/app/finders/interesting_findings/duplicator_installer_log.rb +1 -1
  27. data/app/finders/interesting_findings/emergency_pwd_reset_script.rb +1 -1
  28. data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
  29. data/app/finders/interesting_findings/full_path_disclosure.rb +1 -1
  30. data/app/finders/interesting_findings/headers.rb +17 -0
  31. data/app/finders/interesting_findings/mu_plugins.rb +1 -1
  32. data/app/finders/interesting_findings/multisite.rb +1 -1
  33. data/app/finders/interesting_findings/php_disabled.rb +2 -2
  34. data/app/finders/interesting_findings/readme.rb +1 -1
  35. data/app/finders/interesting_findings/registration.rb +1 -1
  36. data/app/finders/interesting_findings/robots_txt.rb +20 -0
  37. data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
  38. data/app/finders/interesting_findings/tmm_db_migrate.rb +1 -1
  39. data/app/finders/interesting_findings/upload_directory_listing.rb +1 -1
  40. data/app/finders/interesting_findings/upload_sql_dump.rb +2 -2
  41. data/app/finders/interesting_findings/wp_cron.rb +1 -1
  42. data/app/finders/interesting_findings/xml_rpc.rb +61 -0
  43. data/app/finders/interesting_findings.rb +13 -4
  44. data/app/finders/main_theme/css_style_in_homepage.rb +1 -1
  45. data/app/finders/main_theme/urls_in_homepage.rb +3 -7
  46. data/app/finders/main_theme/woo_framework_meta_generator.rb +4 -4
  47. data/app/finders/main_theme.rb +1 -1
  48. data/app/finders/medias/attachment_brute_forcing.rb +2 -2
  49. data/app/finders/medias.rb +1 -1
  50. data/app/finders/passwords/wp_login.rb +2 -2
  51. data/app/finders/passwords/xml_rpc.rb +2 -2
  52. data/app/finders/passwords/xml_rpc_multicall.rb +1 -1
  53. data/app/finders/plugin_version/readme.rb +1 -1
  54. data/app/finders/plugin_version.rb +1 -1
  55. data/app/finders/plugins/known_locations.rb +17 -7
  56. data/app/finders/plugins/urls_in_homepage.rb +3 -7
  57. data/app/finders/plugins/wp_json_api.rb +85 -0
  58. data/app/finders/plugins.rb +2 -1
  59. data/app/finders/theme_version/style.rb +1 -1
  60. data/app/finders/theme_version/woo_framework_meta_generator.rb +1 -1
  61. data/app/finders/theme_version.rb +1 -1
  62. data/app/finders/themes/known_locations.rb +12 -6
  63. data/app/finders/themes/urls_in_homepage.rb +3 -7
  64. data/app/finders/themes/wp_json_api.rb +74 -0
  65. data/app/finders/themes.rb +2 -1
  66. data/app/finders/timthumb_version/bad_request.rb +1 -1
  67. data/app/finders/timthumb_version.rb +1 -1
  68. data/app/finders/timthumbs/known_locations.rb +6 -4
  69. data/app/finders/timthumbs.rb +1 -1
  70. data/app/finders/users/author_id_brute_forcing.rb +11 -7
  71. data/app/finders/users/author_posts.rb +1 -1
  72. data/app/finders/users/author_sitemap.rb +1 -1
  73. data/app/finders/users/login_error_messages.rb +1 -1
  74. data/app/finders/users/oembed_api.rb +3 -1
  75. data/app/finders/users/wp_json_api.rb +11 -7
  76. data/app/finders/users.rb +1 -1
  77. data/app/finders/wp_version/atom_generator.rb +1 -1
  78. data/app/finders/wp_version/rdf_generator.rb +1 -1
  79. data/app/finders/wp_version/readme.rb +1 -1
  80. data/app/finders/wp_version/rss_generator.rb +1 -1
  81. data/app/finders/wp_version/unique_fingerprinting.rb +2 -2
  82. data/app/finders/wp_version.rb +1 -1
  83. data/app/finders.rb +1 -0
  84. data/app/formatters/cli.rb +79 -0
  85. data/app/formatters/cli_no_color.rb +9 -0
  86. data/app/formatters/cli_no_colour.rb +17 -0
  87. data/app/formatters/json.rb +14 -0
  88. data/app/formatters/jsonl.rb +29 -0
  89. data/app/formatters/sarif.rb +311 -0
  90. data/app/models/backup_folder.rb +39 -0
  91. data/app/models/fantastico_fileslist.rb +34 -0
  92. data/app/models/headers.rb +44 -0
  93. data/app/models/interesting_finding.rb +41 -2
  94. data/app/models/plugin.rb +8 -2
  95. data/app/models/robots_txt.rb +31 -0
  96. data/app/models/search_replace_db_2.rb +17 -0
  97. data/app/models/theme.rb +9 -2
  98. data/app/models/timthumb.rb +2 -2
  99. data/app/models/user.rb +35 -0
  100. data/app/models/version.rb +49 -0
  101. data/app/models/wp_item/wordpress_org_data.rb +55 -0
  102. data/app/models/wp_item.rb +109 -9
  103. data/app/models/wp_version.rb +2 -2
  104. data/app/models/xml_rpc.rb +73 -3
  105. data/app/models.rb +2 -1
  106. data/app/user_agents.txt +46 -0
  107. data/app/views/cli/core/banner.erb +3 -3
  108. data/app/views/cli/core/finished.erb +15 -0
  109. data/app/views/cli/core/help.erb +4 -0
  110. data/app/views/cli/core/started.erb +11 -0
  111. data/app/views/cli/enumeration/backup_folders.erb +11 -0
  112. data/app/views/cli/enumeration/plugin.erb +13 -0
  113. data/app/views/cli/enumeration/plugins.erb +1 -12
  114. data/app/views/cli/enumeration/theme.erb +4 -0
  115. data/app/views/cli/enumeration/themes.erb +1 -3
  116. data/app/views/cli/enumeration/user.erb +4 -0
  117. data/app/views/cli/enumeration/users.erb +1 -3
  118. data/app/views/cli/finding.erb +1 -1
  119. data/app/views/cli/interesting_findings/_array.erb +10 -0
  120. data/app/views/cli/interesting_findings/findings.erb +23 -0
  121. data/app/views/cli/scan_aborted.erb +5 -0
  122. data/app/views/cli/update_aborted.erb +5 -0
  123. data/app/views/cli/vuln_api/status.erb +2 -0
  124. data/app/views/cli/vulnerability.erb +6 -0
  125. data/app/views/cli/wp_item.erb +4 -1
  126. data/app/views/json/core/banner.erb +2 -8
  127. data/app/views/json/core/finished.erb +13 -0
  128. data/app/views/json/core/help.erb +4 -0
  129. data/app/views/json/core/started.erb +10 -0
  130. data/app/views/json/enumeration/backup_folders.erb +11 -0
  131. data/app/views/json/enumeration/plugin.erb +15 -0
  132. data/app/views/json/enumeration/theme.erb +5 -0
  133. data/app/views/json/enumeration/user.erb +6 -0
  134. data/app/views/json/finding.erb +8 -2
  135. data/app/views/json/interesting_findings/findings.erb +24 -0
  136. data/app/views/json/notice.erb +1 -0
  137. data/app/views/json/scan_aborted.erb +5 -0
  138. data/app/views/json/update_aborted.erb +5 -0
  139. data/app/views/json/vuln_api/status.erb +2 -0
  140. data/app/views/json/wp_item.erb +4 -1
  141. data/bin/wpscan +1 -0
  142. data/lib/opt_parse_validator/config_files_loader_merger/base.rb +26 -0
  143. data/lib/opt_parse_validator/config_files_loader_merger/json.rb +17 -0
  144. data/lib/opt_parse_validator/config_files_loader_merger/yml.rb +17 -0
  145. data/lib/opt_parse_validator/config_files_loader_merger.rb +62 -0
  146. data/lib/opt_parse_validator/errors.rb +9 -0
  147. data/lib/opt_parse_validator/hacks.rb +19 -0
  148. data/lib/opt_parse_validator/opts/alias.rb +28 -0
  149. data/lib/opt_parse_validator/opts/array.rb +34 -0
  150. data/lib/opt_parse_validator/opts/base.rb +142 -0
  151. data/lib/opt_parse_validator/opts/boolean.rb +19 -0
  152. data/lib/opt_parse_validator/opts/choice.rb +43 -0
  153. data/lib/opt_parse_validator/opts/credentials.rb +15 -0
  154. data/lib/opt_parse_validator/opts/directory_path.rb +17 -0
  155. data/lib/opt_parse_validator/opts/file_path.rb +34 -0
  156. data/lib/opt_parse_validator/opts/headers.rb +33 -0
  157. data/lib/opt_parse_validator/opts/integer.rb +15 -0
  158. data/lib/opt_parse_validator/opts/integer_range.rb +37 -0
  159. data/lib/opt_parse_validator/opts/multi_choices.rb +135 -0
  160. data/lib/opt_parse_validator/opts/path.rb +78 -0
  161. data/lib/opt_parse_validator/opts/positive_integer.rb +16 -0
  162. data/lib/opt_parse_validator/opts/proxy.rb +7 -0
  163. data/lib/opt_parse_validator/opts/regexp.rb +14 -0
  164. data/lib/opt_parse_validator/opts/smart_list.rb +30 -0
  165. data/lib/opt_parse_validator/opts/string.rb +8 -0
  166. data/lib/opt_parse_validator/opts/uri.rb +41 -0
  167. data/lib/opt_parse_validator/opts/url.rb +11 -0
  168. data/lib/opt_parse_validator/opts.rb +9 -0
  169. data/lib/opt_parse_validator/version.rb +6 -0
  170. data/lib/opt_parse_validator.rb +161 -0
  171. data/lib/wpscan/browser/actions.rb +48 -0
  172. data/lib/wpscan/browser/options.rb +92 -0
  173. data/lib/wpscan/browser.rb +87 -2
  174. data/lib/wpscan/browser_authenticator.rb +64 -0
  175. data/lib/wpscan/cache/file_store.rb +77 -0
  176. data/lib/wpscan/cache/typhoeus.rb +25 -0
  177. data/lib/wpscan/controller.rb +100 -4
  178. data/lib/wpscan/controllers.rb +78 -3
  179. data/lib/wpscan/db/dynamic_finders/base.rb +3 -7
  180. data/lib/wpscan/db/dynamic_finders/plugin.rb +2 -2
  181. data/lib/wpscan/db/dynamic_finders/wordpress.rb +1 -1
  182. data/lib/wpscan/db/fingerprints.rb +2 -2
  183. data/lib/wpscan/db/updater.rb +23 -13
  184. data/lib/wpscan/db/vuln_api.rb +19 -7
  185. data/lib/wpscan/db/wp_item.rb +2 -2
  186. data/lib/wpscan/errors/enumeration.rb +4 -4
  187. data/lib/wpscan/errors/http.rb +82 -3
  188. data/lib/wpscan/errors/saml.rb +28 -0
  189. data/lib/wpscan/errors/scan.rb +14 -0
  190. data/lib/wpscan/errors/update.rb +11 -3
  191. data/lib/wpscan/errors/vuln_api.rb +24 -0
  192. data/lib/wpscan/errors/wordpress.rb +2 -2
  193. data/lib/wpscan/errors/wp_auth.rb +37 -0
  194. data/lib/wpscan/errors.rb +4 -3
  195. data/lib/wpscan/exit_code.rb +25 -0
  196. data/lib/wpscan/finders/base_finders.rb +45 -0
  197. data/lib/wpscan/finders/dynamic_finder/finder.rb +1 -1
  198. data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +1 -1
  199. data/lib/wpscan/finders/dynamic_finder/version/comment.rb +1 -1
  200. data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +1 -1
  201. data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +1 -1
  202. data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +3 -5
  203. data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +1 -1
  204. data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +3 -3
  205. data/lib/wpscan/finders/dynamic_finder/wp_version.rb +1 -1
  206. data/lib/wpscan/finders/finder/breadth_first_dictionary_attack.rb +257 -0
  207. data/lib/wpscan/finders/finder/enumerator.rb +77 -0
  208. data/lib/wpscan/finders/finder/fingerprinter.rb +48 -0
  209. data/lib/wpscan/finders/finder/smart_url_checker/findings.rb +33 -0
  210. data/lib/wpscan/finders/finder/smart_url_checker.rb +60 -0
  211. data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +1 -1
  212. data/lib/wpscan/finders/finder.rb +78 -0
  213. data/lib/wpscan/finders/finding.rb +54 -0
  214. data/lib/wpscan/finders/findings.rb +33 -0
  215. data/lib/wpscan/finders/independent_finder.rb +33 -0
  216. data/lib/wpscan/finders/independent_finders.rb +26 -0
  217. data/lib/wpscan/finders/same_type_finder.rb +19 -0
  218. data/lib/wpscan/finders/same_type_finders.rb +28 -0
  219. data/lib/wpscan/finders/unique_finder.rb +19 -0
  220. data/lib/wpscan/finders/unique_finders.rb +47 -0
  221. data/lib/wpscan/finders.rb +11 -12
  222. data/lib/wpscan/formatter/buffer.rb +17 -0
  223. data/lib/wpscan/formatter.rb +152 -0
  224. data/lib/wpscan/helper.rb +7 -1
  225. data/lib/wpscan/http_status_tracking.rb +128 -0
  226. data/lib/wpscan/numeric.rb +13 -0
  227. data/lib/wpscan/parsed_cli.rb +31 -2
  228. data/lib/wpscan/progressbar_null_output.rb +23 -0
  229. data/lib/wpscan/public_suffix/domain.rb +44 -0
  230. data/lib/wpscan/references.rb +118 -4
  231. data/lib/wpscan/scan.rb +127 -0
  232. data/lib/wpscan/target/hashes.rb +45 -0
  233. data/lib/wpscan/target/platform/php.rb +124 -0
  234. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +3 -3
  235. data/lib/wpscan/target/platform/wordpress.rb +7 -8
  236. data/lib/wpscan/target/platform.rb +3 -0
  237. data/lib/wpscan/target/scope.rb +103 -0
  238. data/lib/wpscan/target/server/apache.rb +27 -0
  239. data/lib/wpscan/target/server/generic.rb +72 -0
  240. data/lib/wpscan/target/server/iis.rb +29 -0
  241. data/lib/wpscan/target/server/nginx.rb +27 -0
  242. data/lib/wpscan/target/server.rb +6 -0
  243. data/lib/wpscan/target.rb +129 -9
  244. data/lib/wpscan/typhoeus/hydra.rb +12 -0
  245. data/lib/wpscan/typhoeus/response.rb +24 -1
  246. data/lib/wpscan/version.rb +1 -1
  247. data/lib/wpscan/vulnerability.rb +49 -3
  248. data/lib/wpscan/vulnerability_filter.rb +68 -0
  249. data/lib/wpscan/vulnerable.rb +13 -1
  250. data/lib/wpscan/web_site.rb +152 -0
  251. data/lib/wpscan.rb +126 -29
  252. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OptParseValidator
4
+ # Implementation of the Proxy Option
5
+ class OptProxy < OptURI
6
+ end
7
+ 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OptParseValidator
4
+ # Alias of OptBase
5
+ # Used for convenience
6
+ class OptString < OptBase
7
+ end
8
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OptParseValidator
4
+ # Implementation of the URL Option
5
+ class OptURL < OptURI
6
+ # @return [ Array ] The allowed protocols
7
+ def allowed_protocols
8
+ %w[http https]
9
+ end
10
+ end
11
+ 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Gem Version
4
+ module OptParseValidator
5
+ VERSION = '1.10.1'
6
+ 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