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
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'core/cli_options'
4
+ require 'socket'
5
+
3
6
  module WPScan
4
7
  module Controller
5
- # Specific Core controller to include WordPress checks
6
- class Core < CMSScanner::Controller::Core
8
+ # Core Controller (WordPress-aware).
9
+ class Core < Base
7
10
  # @return [ Array<OptParseValidator::Opt> ]
8
11
  def cli_options
9
12
  [OptURL.new(['--url URL', 'The URL of the blog to scan'],
10
13
  required_unless: %i[update help hh version], default_protocol: 'http')] +
11
- super.drop(2) + # delete the --url and --force from CMSScanner
14
+ base_cli_options.drop(2) + # drop the base --url and --force
12
15
  [
13
16
  OptChoice.new(['--server SERVER', 'Force the supplied server module to be loaded'],
14
17
  choices: %w[apache iis nginx],
@@ -19,6 +22,156 @@ module WPScan
19
22
  ]
20
23
  end
21
24
 
25
+ def setup_cache
26
+ return unless WPScan::ParsedCli.cache_dir
27
+
28
+ storage_path = File.join(WPScan::ParsedCli.cache_dir, Digest::MD5.hexdigest(target.url))
29
+
30
+ Typhoeus::Config.cache = Cache::Typhoeus.new(storage_path)
31
+ Typhoeus::Config.cache.clean if WPScan::ParsedCli.clear_cache
32
+ end
33
+
34
+ def before_scan
35
+ @last_update = local_db.last_update
36
+ @saml_authenticated = false
37
+
38
+ maybe_output_banner_help_and_version
39
+
40
+ update_db if update_db_required?
41
+ setup_cache
42
+ check_target_availability
43
+ load_server_module
44
+ check_wordpress_state
45
+ rescue Error::NotWordPress => e
46
+ target.maybe_add_cookies
47
+ raise e unless target.wordpress?(ParsedCli.detection_mode)
48
+ end
49
+
50
+ def maybe_output_banner_help_and_version
51
+ output('banner') if WPScan::ParsedCli.banner
52
+ output('help', help: option_parser.simple_help, simple: true) if WPScan::ParsedCli.help
53
+ output('help', help: option_parser.full_help, simple: false) if WPScan::ParsedCli.hh
54
+ output('version') if WPScan::ParsedCli.version
55
+
56
+ exit(WPScan::ExitCode::OK) if WPScan::ParsedCli.help || WPScan::ParsedCli.hh || WPScan::ParsedCli.version
57
+ end
58
+
59
+ # Checks that the target is accessible, raises related errors otherwise.
60
+ #
61
+ # @return [ Void ]
62
+ def check_target_availability
63
+ res = WPScan::Browser.get(target.url)
64
+
65
+ case res.code
66
+ when 0
67
+ raise Error::TargetDown, res
68
+ when 401
69
+ raise Error::HTTPAuthRequired
70
+ when 403
71
+ raise Error::AccessForbidden, WPScan::ParsedCli.random_user_agent unless WPScan::ParsedCli.force
72
+ when 407
73
+ raise Error::ProxyAuthRequired
74
+ end
75
+
76
+ handle_redirection(res)
77
+ end
78
+
79
+ # Checks whether the response or its redirect chain contains a SAMLRequest,
80
+ # indicating that the target requires SAML authentication.
81
+ #
82
+ # @param [ Addressable::URI ] effective_uri Final URL after following redirects
83
+ # @param [ Typhoeus::Response ] homepage_res Response whose redirect chain to inspect
84
+ #
85
+ # @return [ Boolean ]
86
+ def saml_request?(effective_uri, homepage_res = nil)
87
+ return false unless effective_uri
88
+
89
+ return true if effective_uri.to_s.match?(/[?&]SAMLRequest/i)
90
+
91
+ # SAML flows often bounce through intermediate pages before the IdP;
92
+ # walk the redirect chain to catch a SAMLRequest in any Location header.
93
+ !!homepage_res&.redirections&.any? do |redirect_response|
94
+ redirect_response.headers['Location']&.match?(/SAMLRequest/i)
95
+ end
96
+ end
97
+
98
+ # Drives an interactive SAML login via a headless browser, injects the
99
+ # resulting session cookies into the shared Browser, and clears the target's
100
+ # cached homepage so the rest of the scan runs against the authenticated session.
101
+ #
102
+ # @param [ Addressable::URI ] effective_uri URL that triggered the SAML redirect
103
+ #
104
+ # @return [ Void ]
105
+ def handle_saml_authentication(effective_uri)
106
+ raise Error::SAMLAuthenticationFailed if WPScan::ParsedCli.cookie_string && !WPScan::ParsedCli.expect_saml
107
+ raise Error::SAMLAuthenticationRequired unless WPScan::ParsedCli.expect_saml
108
+
109
+ new_cookies = BrowserAuthenticator.authenticate(effective_uri.to_s)
110
+
111
+ browser = WPScan::Browser.instance
112
+ browser.cookie_string = [browser.cookie_string, new_cookies].compact.reject(&:empty?).join('; ')
113
+
114
+ # Discard the pre-auth homepage so subsequent finders refetch with the new cookies.
115
+ target.reset_homepage_cache!
116
+
117
+ @saml_authenticated = true
118
+ end
119
+
120
+ # Checks for redirects; an out-of-scope redirect raises Error::HTTPRedirect.
121
+ #
122
+ # @param [ Typhoeus::Response ] res
123
+ def handle_redirection(res)
124
+ effective_url = target.homepage_res.effective_url # get and follow location of target.url
125
+ effective_uri = Addressable::URI.parse(effective_url)
126
+ is_saml = saml_request?(effective_uri, target.homepage_res)
127
+
128
+ if WPScan::ParsedCli.expect_saml && !is_saml && !@saml_authenticated
129
+ puts 'SAML authentication was expected but not required.'
130
+ puts # New line to serve as buffer before the scan results start
131
+ end
132
+
133
+ if is_saml
134
+ raise Error::SAMLAuthenticationFailed if @saml_authenticated
135
+
136
+ handle_saml_authentication(effective_uri)
137
+ return check_target_availability
138
+ end
139
+
140
+ handle_scheme_redirect(effective_url, effective_uri)
141
+ handle_follow_redirect(effective_url, effective_uri)
142
+
143
+ return if target.in_scope?(effective_url)
144
+
145
+ raise Error::HTTPRedirect, effective_url unless WPScan::ParsedCli.ignore_main_redirect
146
+
147
+ # Sets homepage_res back to unfollowed response when ignore_main_redirect is used
148
+ target.homepage_res = res
149
+ end
150
+
151
+ # Handles scheme-only redirects (http => https or vice versa)
152
+ #
153
+ # @param [ String ] effective_url
154
+ # @param [ Addressable::URI ] effective_uri
155
+ def handle_scheme_redirect(effective_url, effective_uri)
156
+ # http://a.com => https://a.com (or the opposite)
157
+ if !WPScan::ParsedCli.ignore_main_redirect && target.uri.domain == effective_uri.domain &&
158
+ target.uri.path == effective_uri.path && target.uri.scheme != effective_uri.scheme
159
+
160
+ target.url = effective_url
161
+ end
162
+ end
163
+
164
+ # Handles --follow-redirect option
165
+ #
166
+ # @param [ String ] effective_url
167
+ # @param [ Addressable::URI ] effective_uri
168
+ def handle_follow_redirect(effective_url, effective_uri)
169
+ return unless WPScan::ParsedCli.follow_redirect && target.url != effective_url
170
+
171
+ target.url = effective_url
172
+ target.scope << effective_uri.host
173
+ end
174
+
22
175
  # @return [ DB::Updater ]
23
176
  def local_db
24
177
  @local_db ||= DB::Updater.new(DB_DIR)
@@ -38,34 +191,29 @@ module WPScan
38
191
 
39
192
  output('@notice', msg: 'It seems like you have not updated the database for some time.')
40
193
  print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
194
+ $stdout.flush
195
+
196
+ response = $stdin.gets.to_s.strip
41
197
 
42
- /^y/i.match?(Readline.readline)
198
+ !!/^y/i.match?(response)
43
199
  end
44
200
 
45
201
  def update_db
202
+ @updating_db = true
46
203
  output('db_update_started')
47
204
  output('db_update_finished', updated: local_db.update, verbose: ParsedCli.verbose)
205
+ @updating_db = false
48
206
 
49
207
  exit(0) unless ParsedCli.url
50
208
  end
51
209
 
52
- def before_scan
53
- @last_update = local_db.last_update
54
-
55
- maybe_output_banner_help_and_version # From CMSScanner
56
-
57
- update_db if update_db_required?
58
- setup_cache
59
- check_target_availability
60
- load_server_module
61
- check_wordpress_state
62
- rescue Error::NotWordPress => e
63
- target.maybe_add_cookies
64
- raise e unless target.wordpress?(ParsedCli.detection_mode)
210
+ # @return [ Boolean ] Whether the DB update is currently in progress
211
+ def updating_db?
212
+ @updating_db
65
213
  end
66
214
 
67
- # Raises errors if the target is hosted on wordpress.com or is not running WordPress
68
- # Also check if the homepage_url is still the install url
215
+ # Raises errors if the target is hosted on wordpress.com or is not running WordPress.
216
+ # Also checks if the homepage_url is still the install URL.
69
217
  def check_wordpress_state
70
218
  raise Error::WordPressHosted if target.wordpress_hosted?
71
219
 
@@ -79,15 +227,13 @@ module WPScan
79
227
  raise Error::NotWordPress unless target.wordpress?(ParsedCli.detection_mode) || ParsedCli.force
80
228
  end
81
229
 
82
- # Loads the related server module in the target
83
- # and includes it in the WpItem class which will be needed
84
- # to check if directory listing is enabled etc
230
+ # Loads the related server module into the target and includes it on WpItem
231
+ # (needed to check if directory listing is enabled etc.).
85
232
  #
86
233
  # @return [ Symbol ] The server module loaded
87
234
  def load_server_module
88
- server = target.server || :Apache # Tries to auto detect the server
235
+ server = target.server || :Apache # auto-detect
89
236
 
90
- # Force a specific server module to be loaded if supplied
91
237
  case ParsedCli.server
92
238
  when :apache
93
239
  server = :Apache
@@ -97,13 +243,42 @@ module WPScan
97
243
  server = :Nginx
98
244
  end
99
245
 
100
- mod = CMSScanner::Target::Server.const_get(server)
246
+ mod = WPScan::Target::Server.const_get(server)
101
247
 
102
248
  target.extend mod
103
249
  Model::WpItem.include mod
104
250
 
105
251
  server
106
252
  end
253
+
254
+ def run
255
+ @start_time = Time.now
256
+ @start_memory = WPScan.start_memory
257
+
258
+ output('started',
259
+ url: target.url,
260
+ ip: target.ip,
261
+ effective_url: target.homepage_url,
262
+ command_line: WPScan.command_line,
263
+ hostname: Socket.gethostname)
264
+ end
265
+
266
+ def after_scan
267
+ @stop_time = Time.now
268
+ @elapsed = @stop_time - @start_time
269
+ @used_memory = GetProcessMem.new.bytes - @start_memory
270
+
271
+ warnings = WPScan.error_warning_messages
272
+
273
+ output('finished',
274
+ cached_requests: WPScan.cached_requests,
275
+ requests_done: WPScan.total_requests,
276
+ data_sent: WPScan.total_data_sent,
277
+ data_received: WPScan.total_data_received,
278
+ response_status_codes: WPScan.format_status_codes(WPScan.top_status_codes),
279
+ response_status_codes_warning: warnings.any?,
280
+ response_status_codes_warnings: warnings)
281
+ end
107
282
  end
108
283
  end
109
284
  end
@@ -4,7 +4,7 @@ module WPScan
4
4
  module Controller
5
5
  # Controller to ensure that the wp-content and wp-plugins
6
6
  # directories are found
7
- class CustomDirectories < CMSScanner::Controller::Base
7
+ class CustomDirectories < WPScan::Controller::Base
8
8
  def cli_options
9
9
  [
10
10
  OptString.new(['--wp-content-dir DIR',
@@ -3,18 +3,19 @@
3
3
  module WPScan
4
4
  module Controller
5
5
  # Enumeration CLI Options
6
- class Enumeration < CMSScanner::Controller::Base
6
+ class Enumeration < WPScan::Controller::Base
7
7
  def cli_options
8
8
  cli_enum_choices + cli_plugins_opts + cli_themes_opts +
9
9
  cli_timthumbs_opts + cli_config_backups_opts + cli_db_exports_opts +
10
- cli_medias_opts + cli_users_opts
10
+ cli_backup_folders_opts + cli_medias_opts + cli_users_opts
11
11
  end
12
12
 
13
13
  # @return [ Array<OptParseValidator::OptBase> ]
14
14
  def cli_enum_choices
15
15
  [
16
16
  OptMultiChoices.new(
17
- ['-e', '--enumerate [OPTS]', 'Enumeration Process'],
17
+ ['-e', '--enumerate [OPTS]', 'Enumeration Process',
18
+ 'Note: --plugins-list overrides vp/ap/p; --themes-list overrides vt/at/t.'],
18
19
  choices: {
19
20
  vp: OptBoolean.new(['--vulnerable-plugins']),
20
21
  ap: OptBoolean.new(['--all-plugins']),
@@ -25,15 +26,15 @@ module WPScan
25
26
  tt: OptBoolean.new(['--timthumbs']),
26
27
  cb: OptBoolean.new(['--config-backups']),
27
28
  dbe: OptBoolean.new(['--db-exports']),
29
+ bf: OptBoolean.new(['--backup-folders']),
28
30
  u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'),
29
31
  m: OptIntegerRange.new(['--medias',
30
32
  'Media IDs range. e.g m1-15',
31
33
  'Note: Permalink setting must be set to "Plain" for those to be detected'],
32
34
  value_if_empty: '1-100')
33
35
  },
34
- value_if_empty: 'vp,vt,tt,cb,dbe,u,m',
35
- incompatible: [%i[vp ap p], %i[vt at t]],
36
- default: { all_plugins: true, config_backups: true }
36
+ value_if_empty: 'vp,vt,tt,cb,dbe,bf,u,m',
37
+ incompatible: [%i[vp ap p], %i[vt at t]]
37
38
  ),
38
39
  OptRegexp.new(
39
40
  [
@@ -52,7 +53,7 @@ module WPScan
52
53
  OptChoice.new(
53
54
  ['--plugins-detection MODE',
54
55
  'Use the supplied mode to enumerate Plugins.'],
55
- choices: %w[mixed passive aggressive], normalize: :to_sym, default: :passive
56
+ choices: %w[mixed passive aggressive], normalize: :to_sym
56
57
  ),
57
58
  OptBoolean.new(
58
59
  ['--plugins-version-all',
@@ -63,7 +64,7 @@ module WPScan
63
64
  OptChoice.new(
64
65
  ['--plugins-version-detection MODE',
65
66
  'Use the supplied mode to check plugins\' versions.'],
66
- choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
67
+ choices: %w[mixed passive aggressive], normalize: :to_sym
67
68
  ),
68
69
  OptInteger.new(
69
70
  ['--plugins-threshold THRESHOLD',
@@ -107,12 +108,7 @@ module WPScan
107
108
  [
108
109
  OptFilePath.new(
109
110
  ['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'],
110
- exists: true, default: DB_DIR.join('timthumbs-v3.txt').to_s, advanced: true
111
- ),
112
- OptChoice.new(
113
- ['--timthumbs-detection MODE',
114
- 'Use the supplied mode to enumerate Timthumbs, instead of the global (--detection-mode) mode.'],
115
- choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
111
+ exists: true, default: DB_DIR.join('timthumbs-v3.txt'), advanced: true
116
112
  )
117
113
  ]
118
114
  end
@@ -122,12 +118,7 @@ module WPScan
122
118
  [
123
119
  OptFilePath.new(
124
120
  ['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'],
125
- exists: true, default: DB_DIR.join('config_backups.txt').to_s, advanced: true
126
- ),
127
- OptChoice.new(
128
- ['--config-backups-detection MODE',
129
- 'Use the supplied mode to enumerate Config Backups, instead of the global (--detection-mode) mode.'],
130
- choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
121
+ exists: true, default: DB_DIR.join('config_backups.txt'), advanced: true
131
122
  )
132
123
  ]
133
124
  end
@@ -137,27 +128,26 @@ module WPScan
137
128
  [
138
129
  OptFilePath.new(
139
130
  ['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'],
140
- exists: true, default: DB_DIR.join('db_exports.txt').to_s, advanced: true
141
- ),
142
- OptChoice.new(
143
- ['--db-exports-detection MODE',
144
- 'Use the supplied mode to enumerate DB Exports, instead of the global (--detection-mode) mode.'],
145
- choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
131
+ exists: true, default: DB_DIR.join('db_exports.txt'), advanced: true
146
132
  )
147
133
  ]
148
134
  end
149
135
 
150
136
  # @return [ Array<OptParseValidator::OptBase> ]
151
- def cli_medias_opts
137
+ def cli_backup_folders_opts
152
138
  [
153
- OptChoice.new(
154
- ['--medias-detection MODE',
155
- 'Use the supplied mode to enumerate Medias, instead of the global (--detection-mode) mode.'],
156
- choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
139
+ OptFilePath.new(
140
+ ['--backup-folders-list FILE-PATH', 'List of backup folders to use'],
141
+ exists: true, default: DB_DIR.join('backup_folders.txt'), advanced: true
157
142
  )
158
143
  ]
159
144
  end
160
145
 
146
+ # @return [ Array<OptParseValidator::OptBase> ]
147
+ def cli_medias_opts
148
+ []
149
+ end
150
+
161
151
  # @return [ Array<OptParseValidator::OptBase> ]
162
152
  def cli_users_opts
163
153
  [