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
@@ -8,7 +8,7 @@ module WPScan
8
8
  # /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
9
9
  FILES = %w[
10
10
  metadata.json wp_fingerprints.json
11
- timthumbs-v3.txt config_backups.txt db_exports.txt
11
+ timthumbs-v3.txt config_backups.txt db_exports.txt backup_folders.txt
12
12
  dynamic_finders.yml LICENSE sponsor.txt
13
13
  ].freeze
14
14
 
@@ -22,7 +22,7 @@ module WPScan
22
22
  def initialize(repo_directory)
23
23
  @repo_directory = Pathname.new(repo_directory).expand_path
24
24
 
25
- FileUtils.mkdir_p(repo_directory.to_s) unless Dir.exist?(repo_directory.to_s)
25
+ FileUtils.mkdir_p(repo_directory.to_s)
26
26
 
27
27
  # When --no-update is passed, return to avoid raising an error if the directory is not writable
28
28
  # Mainly there for Homebrew: https://github.com/wpscanteam/wpscan/pull/1455
@@ -73,13 +73,22 @@ module WPScan
73
73
  # @return [ Hash ] The params for Typhoeus::Request
74
74
  # @note Those params can't be overriden by CLI options
75
75
  def request_params
76
- @request_params ||= Browser.instance.default_request_params.merge(
77
- timeout: 600,
78
- connecttimeout: 300,
79
- accept_encoding: 'gzip, deflate',
80
- cache_ttl: 0,
81
- headers: { 'User-Agent' => Browser.instance.default_user_agent }
82
- )
76
+ @request_params ||= begin
77
+ params = Browser.instance.default_request_params.merge(
78
+ timeout: 600,
79
+ connecttimeout: 300,
80
+ accept_encoding: 'gzip, deflate',
81
+ cache_ttl: 0,
82
+ headers: { 'User-Agent' => Browser.instance.default_user_agent }
83
+ )
84
+
85
+ if ParsedCli.proxy_target_only
86
+ params.delete(:proxy)
87
+ params.delete(:proxyuserpwd)
88
+ end
89
+
90
+ params
91
+ end
83
92
  end
84
93
 
85
94
  # @return [ String ] The raw file URL associated with the given filename
@@ -127,7 +136,8 @@ module WPScan
127
136
  FileUtils.rm(backup_file_path(filename))
128
137
  end
129
138
 
130
- # @return [ String ] The checksum of the downloaded file
139
+ # @return [ Array(String, String) ] The checksum of the downloaded file and the
140
+ # Cloudflare Ray ID of the response (or nil)
131
141
  def download(filename)
132
142
  file_path = local_file_path(filename)
133
143
  file_url = remote_file_url(filename)
@@ -137,7 +147,7 @@ module WPScan
137
147
 
138
148
  File.binwrite(file_path, res.body)
139
149
 
140
- local_file_checksum(filename)
150
+ [local_file_checksum(filename), res.headers && res.headers['CF-Ray']]
141
151
  end
142
152
 
143
153
  # @return [ Array<String> ] The filenames updated
@@ -151,9 +161,9 @@ module WPScan
151
161
  next if File.exist?(local_file_path(filename)) && db_checksum == local_file_checksum(filename)
152
162
 
153
163
  create_backup(filename)
154
- dl_checksum = download(filename)
164
+ dl_checksum, cf_ray = download(filename)
155
165
 
156
- raise Error::ChecksumsMismatch, filename unless dl_checksum == db_checksum
166
+ raise Error::ChecksumsMismatch.new(filename, cf_ray: cf_ray) unless dl_checksum == db_checksum
157
167
 
158
168
  updated << filename
159
169
  rescue StandardError => e
@@ -26,7 +26,7 @@ module WPScan
26
26
  # Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI
27
27
  res = Typhoeus.get(uri.join(path), default_request_params.merge(params))
28
28
 
29
- return {} if res.code == 404 || res.code == 429
29
+ return {} if [404, 429].include?(res.code)
30
30
  return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
31
31
 
32
32
  raise Error::HTTP, res
@@ -41,6 +41,9 @@ module WPScan
41
41
  end
42
42
 
43
43
  { 'http_error' => e }
44
+ rescue JSON::ParserError => e
45
+ # API returned non-JSON response (HTML, plain text, etc.)
46
+ { 'parse_error' => e }
44
47
  end
45
48
 
46
49
  # @return [ Hash ]
@@ -70,12 +73,21 @@ module WPScan
70
73
  # @return [ Hash ]
71
74
  # @note Those params can not be overriden by CLI options
72
75
  def self.default_request_params
73
- @default_request_params ||= Browser.instance.default_request_params.merge(
74
- headers: {
75
- 'User-Agent' => Browser.instance.default_user_agent,
76
- 'Authorization' => "Token token=#{token}"
77
- }
78
- )
76
+ @default_request_params ||= begin
77
+ params = Browser.instance.default_request_params.merge(
78
+ headers: {
79
+ 'User-Agent' => Browser.instance.default_user_agent,
80
+ 'Authorization' => "Token token=#{token}"
81
+ }
82
+ )
83
+
84
+ if ParsedCli.proxy_target_only
85
+ params.delete(:proxy)
86
+ params.delete(:proxyuserpwd)
87
+ end
88
+
89
+ params
90
+ end
79
91
  end
80
92
  end
81
93
  end
@@ -16,9 +16,9 @@ module WPScan
16
16
  @metadata ||= read_json_file(metadata_file)
17
17
  end
18
18
 
19
- # @return [ String ]
19
+ # @return [ Pathname ]
20
20
  def self.metadata_file
21
- @metadata_file ||= DB_DIR.join('metadata.json').to_s
21
+ @metadata_file ||= DB_DIR.join('metadata.json')
22
22
  end
23
23
  end
24
24
  end
@@ -5,16 +5,16 @@ module WPScan
5
5
  class PluginsThresholdReached < Standard
6
6
  def to_s
7
7
  "The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
8
- 'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
9
- 'option to ignore the bad responses.'
8
+ 'which might indicate False Positive. You can use --plugins-threshold to increase or disable ' \
9
+ 'this limit (set to 0 to disable), or use --exclude-content-based to ignore bad responses.'
10
10
  end
11
11
  end
12
12
 
13
13
  class ThemesThresholdReached < Standard
14
14
  def to_s
15
15
  "The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
16
- 'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
17
- 'option to ignore the bad responses.'
16
+ 'which might indicate False Positive. You can use --themes-threshold to increase or disable ' \
17
+ 'this limit (set to 0 to disable), or use --exclude-content-based to ignore bad responses.'
18
18
  end
19
19
  end
20
20
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module WPScan
4
4
  module Error
5
- # HTTP Error
5
+ # Generic HTTP Error
6
6
  class HTTP < Standard
7
7
  attr_reader :response
8
8
 
9
- # @param [ Typhoeus::Response ] res
9
+ # @param [ Typhoeus::Response ] response
10
10
  def initialize(response)
11
11
  @response = response
12
12
  end
@@ -31,7 +31,86 @@ module WPScan
31
31
  # Used in the Updater
32
32
  class Download < HTTP
33
33
  def to_s
34
- "Unable to get #{failure_details}"
34
+ msg = "Unable to get #{failure_details}"
35
+
36
+ if response.effective_url.to_s.include?('data.wpscan.org')
37
+ cf_ray = response.headers && response.headers['CF-Ray']
38
+ msg += "\nCloudflare Ray ID: #{cf_ray}" if cf_ray
39
+ msg += "\nIf this issue persists, you can:\n " \
40
+ "- Check our status page at https://status.wpscan.com/\n " \
41
+ "- Contact us via https://wpscan.com/contact/ (please include the Ray ID above if any)\n " \
42
+ '- Or open a GitHub issue at https://github.com/wpscanteam/wpscan/issues'
43
+ end
44
+
45
+ msg
46
+ end
47
+ end
48
+
49
+ # Target Down Error
50
+ class TargetDown < Standard
51
+ attr_reader :response
52
+
53
+ def initialize(response)
54
+ @response = response
55
+ end
56
+
57
+ def to_s
58
+ "The url supplied '#{response.request.url}' seems to be down (#{response.return_message})"
59
+ end
60
+ end
61
+
62
+ # HTTP Authentication Required Error
63
+ class HTTPAuthRequired < Standard
64
+ # :nocov:
65
+ def to_s
66
+ 'HTTP authentication required (or was invalid), please provide it with --http-auth'
67
+ end
68
+ # :nocov:
69
+ end
70
+
71
+ # Proxy Authentication Required Error
72
+ class ProxyAuthRequired < Standard
73
+ # :nocov:
74
+ def to_s
75
+ 'Proxy authentication required (or was invalid), please provide it with --proxy-auth'
76
+ end
77
+ # :nocov:
78
+ end
79
+
80
+ # Access Forbidden Error
81
+ class AccessForbidden < Standard
82
+ attr_reader :random_user_agent_used
83
+
84
+ # @param [ Boolean ] random_user_agent_used
85
+ def initialize(random_user_agent_used)
86
+ @random_user_agent_used = random_user_agent_used
87
+ end
88
+
89
+ def to_s
90
+ msg = if random_user_agent_used
91
+ 'Well... --random-user-agent didn\'t work, use --force to skip this check if needed.'
92
+ else
93
+ 'Please re-try with --random-user-agent'
94
+ end
95
+
96
+ "The target is responding with a 403, this might be due to a WAF. #{msg}"
97
+ end
98
+ end
99
+
100
+ # HTTP Redirect Error
101
+ class HTTPRedirect < Standard
102
+ attr_reader :redirect_uri
103
+
104
+ # @param [ String ] url
105
+ def initialize(url)
106
+ @redirect_uri = Addressable::URI.parse(url).normalize
107
+ end
108
+
109
+ def to_s
110
+ "The URL supplied redirects to #{redirect_uri}. Use the --follow-redirect " \
111
+ 'option to automatically scan the redirected URL, the --ignore-main-redirect ' \
112
+ 'option to ignore the redirection and scan the target, or change the --url option ' \
113
+ 'value to the redirected URL.'
35
114
  end
36
115
  end
37
116
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Error
5
+ # SAML Authentication Required Error
6
+ class SAMLAuthenticationRequired < Standard
7
+ def initialize(message = 'SAML authentication is required to access this resource, consider using --expect-saml.')
8
+ super
9
+ end
10
+ end
11
+
12
+ # SAML Authentication Failed Error
13
+ class SAMLAuthenticationFailed < Standard
14
+ def initialize(message = 'SAML authentication is required to access this resource. ' \
15
+ 'Please ensure correct authentication credentials.')
16
+ super
17
+ end
18
+ end
19
+
20
+ # Ferrum Browser Error. Callers should pass a context-specific message
21
+ # (missing Chrome binary, non-interactive terminal, browser closed mid-auth, ...).
22
+ class BrowserFailed < Standard
23
+ def initialize(message = 'The browser was closed or failed before SAML authentication could be completed.')
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Error
5
+ # Used instead of the Timeout::Error
6
+ class MaxScanDurationReached < Standard
7
+ # :nocov:
8
+ def to_s
9
+ 'Max Scan Duration Reached'
10
+ end
11
+ # :nocov:
12
+ end
13
+ end
14
+ end
@@ -10,14 +10,22 @@ module WPScan
10
10
  end
11
11
 
12
12
  class ChecksumsMismatch < Standard
13
- attr_reader :db_file
13
+ attr_reader :db_file, :cf_ray
14
14
 
15
- def initialize(db_file)
15
+ def initialize(db_file, cf_ray: nil)
16
16
  @db_file = db_file
17
+ @cf_ray = cf_ray
18
+ super()
17
19
  end
18
20
 
19
21
  def to_s
20
- "#{db_file}: checksums do not match. Please try again in a few minutes."
22
+ msg = "#{db_file}: checksums do not match. Please try again in a few minutes."
23
+ msg += "\nCloudflare Ray ID: #{cf_ray}" if cf_ray
24
+ msg += "\nIf this issue persists, you can:\n " \
25
+ "- Check our status page at https://status.wpscan.com/\n " \
26
+ "- Contact us via https://wpscan.com/contact/ (please include the Ray ID above if any)\n " \
27
+ '- Or open a GitHub issue at https://github.com/wpscanteam/wpscan/issues'
28
+ msg
21
29
  end
22
30
  end
23
31
  end
@@ -16,5 +16,29 @@ module WPScan
16
16
  'Your API limit has been reached'
17
17
  end
18
18
  end
19
+
20
+ # Error raised when trying to enumerate vulnerable plugins/themes without an API token
21
+ class ApiTokenRequiredForVulnerableEnumeration < Standard
22
+ def to_s
23
+ 'An API token is required for vulnerable plugin/theme enumeration. ' \
24
+ 'You can get a free API token with 25 daily requests by registering at https://wpscan.com/register. ' \
25
+ 'Provide it via --api-token TOKEN or the WPSCAN_API_TOKEN environment variable.'
26
+ end
27
+ end
28
+
29
+ # Error raised when there's a connection issue with the WPScan API
30
+ class ApiConnectionError < Standard
31
+ attr_reader :original_error
32
+
33
+ def initialize(original_error)
34
+ @original_error = original_error
35
+ super()
36
+ end
37
+
38
+ def to_s
39
+ "Unable to connect to the WPScan API: #{original_error}. " \
40
+ 'Please check https://status.wpscan.com/ for service status.'
41
+ end
42
+ end
19
43
  end
20
44
  end
@@ -25,8 +25,8 @@ module WPScan
25
25
 
26
26
  class WpContentDirNotDetected < Standard
27
27
  def to_s
28
- 'Unable to identify the wp-content dir, please supply it with --wp-content-dir,' \
29
- ' use the --scope option or make sure the --url value given is the correct one'
28
+ 'Unable to identify the wp-content dir, please supply it with --wp-content-dir, ' \
29
+ 'use the --scope option or make sure the --url value given is the correct one'
30
30
  end
31
31
  end
32
32
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Error
5
+ # Raised when authenticated requests to the WP REST API are rejected.
6
+ class WpAuthFailed < Standard
7
+ def initialize(code, url)
8
+ super()
9
+ @code = code
10
+ @url = url
11
+ end
12
+
13
+ def to_s
14
+ "Authenticated REST API request to #{@url} returned HTTP #{@code}. " \
15
+ 'WordPress core does NOT accept regular account passwords on /wp-json over Basic Auth. ' \
16
+ 'The password passed to --wp-auth must be a WordPress Application Password ' \
17
+ '(WP >= 5.6): in wp-admin go to Users -> Profile -> Application Passwords, ' \
18
+ 'create one, and use the generated 24-character string (keep the spaces) as the password.'
19
+ end
20
+ end
21
+
22
+ # Raised when the WP REST plugins/themes endpoint is unreachable for reasons
23
+ # other than authentication (404, 5xx, security plugins blocking /wp-json, ...).
24
+ class WpAuthEndpointUnavailable < Standard
25
+ def initialize(code, url)
26
+ super()
27
+ @code = code
28
+ @url = url
29
+ end
30
+
31
+ def to_s
32
+ "Authenticated REST API endpoint #{@url} returned HTTP #{@code}. " \
33
+ 'The endpoint may be disabled, blocked, or require WordPress 5.5+.'
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/wpscan/errors.rb CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  module WPScan
4
4
  module Error
5
- include CMSScanner::Error
6
-
7
5
  class Standard < StandardError
8
6
  end
9
7
  end
10
8
  end
11
9
 
12
- require_relative 'errors/enumeration'
13
10
  require_relative 'errors/http'
11
+ require_relative 'errors/scan'
12
+ require_relative 'errors/enumeration'
14
13
  require_relative 'errors/update'
15
14
  require_relative 'errors/vuln_api'
16
15
  require_relative 'errors/wordpress'
16
+ require_relative 'errors/wp_auth'
17
17
  require_relative 'errors/xmlrpc'
18
+ require_relative 'errors/saml'
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ # Exit Code Values
5
+ module ExitCode
6
+ # No error, scan finished w/o any vulnerabilies found
7
+ OK = 0
8
+
9
+ # All exceptions raised by OptParseValidator and OptionParser
10
+ CLI_OPTION_ERROR = 1
11
+
12
+ # Interrupt received
13
+ INTERRUPTED = 2
14
+
15
+ # Unhandled/unexpected Exception occured
16
+ EXCEPTION = 3
17
+
18
+ # Error, scan did not finish
19
+ ERROR = 4
20
+
21
+ # The target has at least one vulnerability.
22
+ # Currently, the interesting findings do not count as vulnerable things
23
+ VULNERABLE = 5
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Finders
5
+ # Base class container for the Finders (i.e IndependentFinders etc)
6
+ class BaseFinders < Array
7
+ # @return [ Findings ]
8
+ def findings
9
+ @findings ||= WPScan::Finders::Findings.new
10
+ end
11
+
12
+ # Should be implemented in child classes
13
+ def run; end
14
+
15
+ protected
16
+
17
+ # @param [ Symbol ] mode :mixed, :passive or :aggressive
18
+ # @return [ Array<Symbol> ] The symbols to call for the mode
19
+ def symbols_from_mode(mode)
20
+ symbols = %i[passive aggressive]
21
+
22
+ return symbols if mode.nil? || mode == :mixed
23
+
24
+ symbols.include?(mode) ? Array(mode) : []
25
+ end
26
+
27
+ # @param [ WPScan::Finders::Finder ] finder
28
+ # @param [ Symbol ] symbol See return values of #symbols_from_mode
29
+ # @param [ Hash ] opts
30
+ def run_finder(finder, symbol, opts)
31
+ Array(finder.send(symbol, opts.merge(found: findings))).compact.each do |found|
32
+ findings << found
33
+ end
34
+ end
35
+
36
+ # Allow child classes to filter the findings, such as return the best one
37
+ # or remove the low confidence ones.
38
+ #
39
+ # @return [ Findings ]
40
+ def filter_findings
41
+ findings
42
+ end
43
+ end
44
+ end
45
+ end
@@ -4,7 +4,7 @@ module WPScan
4
4
  module Finders
5
5
  module DynamicFinder
6
6
  # To be used as a base when creating a dynamic finder
7
- class Finder < CMSScanner::Finders::Finder
7
+ class Finder < WPScan::Finders::Finder
8
8
  # @param [ Array ] args
9
9
  def self.child_class_constant(*args)
10
10
  args.each do |arg|
@@ -9,7 +9,7 @@ module WPScan
9
9
  class BodyPattern < Finders::DynamicFinder::Version::Finder
10
10
  # @return [ Hash ]
11
11
  def self.child_class_constants
12
- @child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
12
+ @child_class_constants ||= super.merge(PATTERN: nil, CONFIDENCE: 60)
13
13
  end
14
14
 
15
15
  # @param [ Typhoeus::Response ] response
@@ -9,7 +9,7 @@ module WPScan
9
9
  class Comment < Finders::DynamicFinder::Version::Xpath
10
10
  # @return [ Hash ]
11
11
  def self.child_class_constants
12
- @child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
12
+ @child_class_constants ||= super.merge(PATTERN: nil, XPATH: '//comment()')
13
13
  end
14
14
  end
15
15
  end
@@ -8,7 +8,7 @@ module WPScan
8
8
  class HeaderPattern < Finders::DynamicFinder::Version::Finder
9
9
  # @return [ Hash ]
10
10
  def self.child_class_constants
11
- @child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
11
+ @child_class_constants ||= super.merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
12
12
  end
13
13
 
14
14
  # @param [ Typhoeus::Response ] response
@@ -8,7 +8,7 @@ module WPScan
8
8
  class JavascriptVar < Finders::DynamicFinder::Version::Finder
9
9
  # @return [ Hash ]
10
10
  def self.child_class_constants
11
- @child_class_constants ||= super().merge(
11
+ @child_class_constants ||= super.merge(
12
12
  XPATH: '//script[not(@src)]', VERSION_KEY: nil,
13
13
  PATTERN: nil, CONFIDENCE: 60
14
14
  )
@@ -8,7 +8,7 @@ module WPScan
8
8
  class QueryParameter < Finders::DynamicFinder::Version::Finder
9
9
  # @return [ Hash ]
10
10
  def self.child_class_constants
11
- @child_class_constants ||= super().merge(
11
+ @child_class_constants ||= super.merge(
12
12
  XPATH: nil, FILES: nil, PATTERN: /(?:v|ver|version)=(?<v>\d+\.[.\d]+)/i, CONFIDENCE_PER_OCCURENCE: 10
13
13
  )
14
14
  end
@@ -17,10 +17,8 @@ module WPScan
17
17
  # @param [ Hash ] opts
18
18
  # @return [ Array<Version>, nil ]
19
19
  def find(response, _opts = {})
20
- found = []
21
-
22
- scan_response(response).each do |version_number, occurences|
23
- found << create_version(
20
+ found = scan_response(response).map do |version_number, occurences|
21
+ create_version(
24
22
  version_number,
25
23
  confidence: self.class::CONFIDENCE_PER_OCCURENCE * occurences.size,
26
24
  interesting_entries: occurences
@@ -8,7 +8,7 @@ module WPScan
8
8
  class Xpath < Finders::DynamicFinder::Version::Finder
9
9
  # @return [ Hash ]
10
10
  def self.child_class_constants
11
- @child_class_constants ||= super().merge(
11
+ @child_class_constants ||= super.merge(
12
12
  XPATH: nil, PATTERN: /\A(?<v>\d+\.[.\d]+)/, CONFIDENCE: 60
13
13
  )
14
14
  end
@@ -11,14 +11,14 @@ module WPScan
11
11
  # Also used to factorise some code used between such finders.
12
12
  # The #process_response should be implemented in each child class, or the
13
13
  # #passive and #aggressive overriden
14
- class Finder < CMSScanner::Finders::Finder
14
+ class Finder < WPScan::Finders::Finder
15
15
  # @return [ Hash ] The related dynamic finder passive configurations
16
16
  # for the current class (all its usefullness comes from child classes)
17
17
  def passive_configs
18
18
  # So far only the Plugins have dynamic finders so using DB:: DynamicFinders::Plugin
19
19
  # is ok. However, when Themes have some, will need to create other child classes for them
20
20
 
21
- method = "passive_#{self.class.to_s.demodulize.underscore}_finder_configs".to_sym
21
+ method = :"passive_#{self.class.to_s.demodulize.underscore}_finder_configs"
22
22
 
23
23
  DB::DynamicFinders::Plugin.public_send(method)
24
24
  end
@@ -51,7 +51,7 @@ module WPScan
51
51
  # So far only the Plugins have dynamic finders so using DB:: DynamicFinders::Plugin
52
52
  # is ok. However, when Themes have some, will need to create other child classes for them
53
53
 
54
- method = "aggressive_#{self.class.to_s.demodulize.underscore}_finder_configs".to_sym
54
+ method = :"aggressive_#{self.class.to_s.demodulize.underscore}_finder_configs"
55
55
 
56
56
  DB::DynamicFinders::Plugin.public_send(method)
57
57
  end
@@ -33,7 +33,7 @@ module WPScan
33
33
 
34
34
  # @return [ Hash ]
35
35
  def self.child_class_constants
36
- @child_class_constants ||= super().merge(PATTERN: /ver=(?<v>\d+\.[.\d]+)/i)
36
+ @child_class_constants ||= super.merge(PATTERN: /ver=(?<v>\d+\.[.\d]+)/i)
37
37
  end
38
38
  end
39
39