wpscan 3.8.28 → 4.0.1

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 (253) 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_items/urls_in_page.rb +1 -1
  78. data/app/finders/wp_version/atom_generator.rb +1 -1
  79. data/app/finders/wp_version/rdf_generator.rb +1 -1
  80. data/app/finders/wp_version/readme.rb +1 -1
  81. data/app/finders/wp_version/rss_generator.rb +1 -1
  82. data/app/finders/wp_version/unique_fingerprinting.rb +2 -2
  83. data/app/finders/wp_version.rb +1 -1
  84. data/app/finders.rb +1 -0
  85. data/app/formatters/cli.rb +79 -0
  86. data/app/formatters/cli_no_color.rb +9 -0
  87. data/app/formatters/cli_no_colour.rb +17 -0
  88. data/app/formatters/json.rb +14 -0
  89. data/app/formatters/jsonl.rb +29 -0
  90. data/app/formatters/sarif.rb +311 -0
  91. data/app/models/backup_folder.rb +39 -0
  92. data/app/models/fantastico_fileslist.rb +34 -0
  93. data/app/models/headers.rb +44 -0
  94. data/app/models/interesting_finding.rb +41 -2
  95. data/app/models/plugin.rb +8 -2
  96. data/app/models/robots_txt.rb +31 -0
  97. data/app/models/search_replace_db_2.rb +17 -0
  98. data/app/models/theme.rb +9 -2
  99. data/app/models/timthumb.rb +4 -4
  100. data/app/models/user.rb +35 -0
  101. data/app/models/version.rb +49 -0
  102. data/app/models/wp_item/wordpress_org_data.rb +55 -0
  103. data/app/models/wp_item.rb +116 -14
  104. data/app/models/wp_version.rb +5 -3
  105. data/app/models/xml_rpc.rb +73 -3
  106. data/app/models.rb +2 -1
  107. data/app/user_agents.txt +46 -0
  108. data/app/views/cli/core/banner.erb +3 -3
  109. data/app/views/cli/core/finished.erb +15 -0
  110. data/app/views/cli/core/help.erb +4 -0
  111. data/app/views/cli/core/started.erb +11 -0
  112. data/app/views/cli/enumeration/backup_folders.erb +11 -0
  113. data/app/views/cli/enumeration/plugin.erb +13 -0
  114. data/app/views/cli/enumeration/plugins.erb +1 -12
  115. data/app/views/cli/enumeration/theme.erb +4 -0
  116. data/app/views/cli/enumeration/themes.erb +1 -3
  117. data/app/views/cli/enumeration/user.erb +4 -0
  118. data/app/views/cli/enumeration/users.erb +1 -3
  119. data/app/views/cli/finding.erb +1 -1
  120. data/app/views/cli/interesting_findings/_array.erb +10 -0
  121. data/app/views/cli/interesting_findings/findings.erb +23 -0
  122. data/app/views/cli/scan_aborted.erb +5 -0
  123. data/app/views/cli/update_aborted.erb +5 -0
  124. data/app/views/cli/vuln_api/status.erb +2 -0
  125. data/app/views/cli/vulnerability.erb +6 -0
  126. data/app/views/cli/wp_item.erb +4 -1
  127. data/app/views/json/core/banner.erb +2 -8
  128. data/app/views/json/core/finished.erb +13 -0
  129. data/app/views/json/core/help.erb +4 -0
  130. data/app/views/json/core/started.erb +10 -0
  131. data/app/views/json/enumeration/backup_folders.erb +11 -0
  132. data/app/views/json/enumeration/plugin.erb +15 -0
  133. data/app/views/json/enumeration/theme.erb +5 -0
  134. data/app/views/json/enumeration/user.erb +6 -0
  135. data/app/views/json/finding.erb +8 -2
  136. data/app/views/json/interesting_findings/findings.erb +24 -0
  137. data/app/views/json/notice.erb +1 -0
  138. data/app/views/json/scan_aborted.erb +5 -0
  139. data/app/views/json/update_aborted.erb +5 -0
  140. data/app/views/json/vuln_api/status.erb +2 -0
  141. data/app/views/json/wp_item.erb +4 -1
  142. data/bin/wpscan +1 -0
  143. data/lib/opt_parse_validator/config_files_loader_merger/base.rb +26 -0
  144. data/lib/opt_parse_validator/config_files_loader_merger/json.rb +17 -0
  145. data/lib/opt_parse_validator/config_files_loader_merger/yml.rb +17 -0
  146. data/lib/opt_parse_validator/config_files_loader_merger.rb +62 -0
  147. data/lib/opt_parse_validator/errors.rb +9 -0
  148. data/lib/opt_parse_validator/hacks.rb +19 -0
  149. data/lib/opt_parse_validator/opts/alias.rb +28 -0
  150. data/lib/opt_parse_validator/opts/array.rb +34 -0
  151. data/lib/opt_parse_validator/opts/base.rb +142 -0
  152. data/lib/opt_parse_validator/opts/boolean.rb +19 -0
  153. data/lib/opt_parse_validator/opts/choice.rb +43 -0
  154. data/lib/opt_parse_validator/opts/credentials.rb +15 -0
  155. data/lib/opt_parse_validator/opts/directory_path.rb +17 -0
  156. data/lib/opt_parse_validator/opts/file_path.rb +34 -0
  157. data/lib/opt_parse_validator/opts/headers.rb +33 -0
  158. data/lib/opt_parse_validator/opts/integer.rb +15 -0
  159. data/lib/opt_parse_validator/opts/integer_range.rb +37 -0
  160. data/lib/opt_parse_validator/opts/multi_choices.rb +135 -0
  161. data/lib/opt_parse_validator/opts/path.rb +78 -0
  162. data/lib/opt_parse_validator/opts/positive_integer.rb +16 -0
  163. data/lib/opt_parse_validator/opts/proxy.rb +7 -0
  164. data/lib/opt_parse_validator/opts/regexp.rb +14 -0
  165. data/lib/opt_parse_validator/opts/smart_list.rb +30 -0
  166. data/lib/opt_parse_validator/opts/string.rb +8 -0
  167. data/lib/opt_parse_validator/opts/uri.rb +41 -0
  168. data/lib/opt_parse_validator/opts/url.rb +11 -0
  169. data/lib/opt_parse_validator/opts.rb +9 -0
  170. data/lib/opt_parse_validator/version.rb +6 -0
  171. data/lib/opt_parse_validator.rb +161 -0
  172. data/lib/wpscan/browser/actions.rb +48 -0
  173. data/lib/wpscan/browser/options.rb +92 -0
  174. data/lib/wpscan/browser.rb +87 -2
  175. data/lib/wpscan/browser_authenticator.rb +64 -0
  176. data/lib/wpscan/cache/file_store.rb +77 -0
  177. data/lib/wpscan/cache/typhoeus.rb +25 -0
  178. data/lib/wpscan/controller.rb +100 -4
  179. data/lib/wpscan/controllers.rb +78 -3
  180. data/lib/wpscan/db/dynamic_finders/base.rb +3 -7
  181. data/lib/wpscan/db/dynamic_finders/plugin.rb +2 -2
  182. data/lib/wpscan/db/dynamic_finders/wordpress.rb +1 -1
  183. data/lib/wpscan/db/fingerprints.rb +2 -2
  184. data/lib/wpscan/db/updater.rb +23 -13
  185. data/lib/wpscan/db/vuln_api.rb +25 -9
  186. data/lib/wpscan/db/wp_item.rb +2 -2
  187. data/lib/wpscan/errors/enumeration.rb +4 -4
  188. data/lib/wpscan/errors/http.rb +82 -3
  189. data/lib/wpscan/errors/saml.rb +28 -0
  190. data/lib/wpscan/errors/scan.rb +14 -0
  191. data/lib/wpscan/errors/update.rb +11 -3
  192. data/lib/wpscan/errors/vuln_api.rb +24 -0
  193. data/lib/wpscan/errors/wordpress.rb +2 -2
  194. data/lib/wpscan/errors/wp_auth.rb +37 -0
  195. data/lib/wpscan/errors.rb +4 -3
  196. data/lib/wpscan/exit_code.rb +25 -0
  197. data/lib/wpscan/finders/base_finders.rb +45 -0
  198. data/lib/wpscan/finders/dynamic_finder/finder.rb +1 -1
  199. data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +1 -1
  200. data/lib/wpscan/finders/dynamic_finder/version/comment.rb +1 -1
  201. data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +1 -1
  202. data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +1 -1
  203. data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +3 -5
  204. data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +1 -1
  205. data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +3 -3
  206. data/lib/wpscan/finders/dynamic_finder/wp_version.rb +1 -1
  207. data/lib/wpscan/finders/finder/breadth_first_dictionary_attack.rb +257 -0
  208. data/lib/wpscan/finders/finder/enumerator.rb +77 -0
  209. data/lib/wpscan/finders/finder/fingerprinter.rb +48 -0
  210. data/lib/wpscan/finders/finder/smart_url_checker/findings.rb +33 -0
  211. data/lib/wpscan/finders/finder/smart_url_checker.rb +60 -0
  212. data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +1 -1
  213. data/lib/wpscan/finders/finder.rb +78 -0
  214. data/lib/wpscan/finders/finding.rb +54 -0
  215. data/lib/wpscan/finders/findings.rb +33 -0
  216. data/lib/wpscan/finders/independent_finder.rb +33 -0
  217. data/lib/wpscan/finders/independent_finders.rb +26 -0
  218. data/lib/wpscan/finders/same_type_finder.rb +19 -0
  219. data/lib/wpscan/finders/same_type_finders.rb +28 -0
  220. data/lib/wpscan/finders/unique_finder.rb +19 -0
  221. data/lib/wpscan/finders/unique_finders.rb +47 -0
  222. data/lib/wpscan/finders.rb +11 -12
  223. data/lib/wpscan/formatter/buffer.rb +17 -0
  224. data/lib/wpscan/formatter.rb +152 -0
  225. data/lib/wpscan/helper.rb +7 -1
  226. data/lib/wpscan/http_status_tracking.rb +128 -0
  227. data/lib/wpscan/numeric.rb +13 -0
  228. data/lib/wpscan/parsed_cli.rb +31 -2
  229. data/lib/wpscan/progressbar_null_output.rb +23 -0
  230. data/lib/wpscan/public_suffix/domain.rb +44 -0
  231. data/lib/wpscan/references.rb +118 -4
  232. data/lib/wpscan/scan.rb +130 -0
  233. data/lib/wpscan/target/hashes.rb +45 -0
  234. data/lib/wpscan/target/platform/php.rb +124 -0
  235. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +3 -3
  236. data/lib/wpscan/target/platform/wordpress.rb +7 -8
  237. data/lib/wpscan/target/platform.rb +3 -0
  238. data/lib/wpscan/target/scope.rb +103 -0
  239. data/lib/wpscan/target/server/apache.rb +27 -0
  240. data/lib/wpscan/target/server/generic.rb +72 -0
  241. data/lib/wpscan/target/server/iis.rb +29 -0
  242. data/lib/wpscan/target/server/nginx.rb +27 -0
  243. data/lib/wpscan/target/server.rb +6 -0
  244. data/lib/wpscan/target.rb +129 -9
  245. data/lib/wpscan/typhoeus/hydra.rb +12 -0
  246. data/lib/wpscan/typhoeus/response.rb +24 -1
  247. data/lib/wpscan/version.rb +1 -1
  248. data/lib/wpscan/vulnerability.rb +157 -5
  249. data/lib/wpscan/vulnerability_filter.rb +68 -0
  250. data/lib/wpscan/vulnerable.rb +13 -1
  251. data/lib/wpscan/web_site.rb +152 -0
  252. data/lib/wpscan.rb +126 -29
  253. metadata +362 -20
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Model
5
+ # Version
6
+ class Version
7
+ include Finders::Finding
8
+
9
+ attr_reader :number
10
+
11
+ def initialize(number, opts = {})
12
+ @number = number.to_s
13
+ @number = "0#{number}" if @number[0, 1] == '.'
14
+
15
+ parse_finding_options(opts)
16
+ end
17
+
18
+ # @param [ Version, String ] other
19
+ # rubocop:disable Style/NumericPredicate
20
+ def ==(other)
21
+ (self <=> other) == 0
22
+ end
23
+ # rubocop:enable all
24
+
25
+ # @param [ Version, String ] other
26
+ def <(other)
27
+ (self <=> other) == -1
28
+ end
29
+
30
+ # @param [ Version, String ] other
31
+ def >(other)
32
+ (self <=> other) == 1
33
+ end
34
+
35
+ # @param [ Version, String ] other
36
+ def <=>(other)
37
+ other = Version.new(other) unless other.is_a?(Version) # handle potential '.1' version
38
+
39
+ Gem::Version.new(number) <=> Gem::Version.new(other.number)
40
+ rescue ArgumentError
41
+ false
42
+ end
43
+
44
+ def to_s
45
+ number
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WPScan
4
+ module Model
5
+ class WpItem
6
+ # Fetches and exposes the public wordpress.org info API response for a
7
+ # plugin or theme. The HTTP call is performed at most once per item.
8
+ module WordpressOrgData
9
+ # Timeout (in seconds) for the wordpress.org API lookup. Kept low so a
10
+ # slow or unreachable wordpress.org does not noticeably stall the scan.
11
+ WORDPRESS_ORG_API_TIMEOUT = 5
12
+
13
+ # @return [ String, nil ] The wordpress.org API URL returning info for
14
+ # this item. Subclasses override this; nil disables the lookup.
15
+ def wordpress_org_api_url
16
+ nil
17
+ end
18
+
19
+ # @return [ Hash ] Empty hash if the item is not on wordpress.org or the
20
+ # lookup fails.
21
+ def wordpress_org_data
22
+ return @wordpress_org_data if defined?(@wordpress_org_data)
23
+
24
+ @wordpress_org_data = fetch_wordpress_org_data
25
+ end
26
+
27
+ # Number of active installs as reported by the wordpress.org API.
28
+ # See https://codex.wordpress.org/WordPress.org_API
29
+ #
30
+ # @return [ Integer, nil ]
31
+ def active_installs
32
+ wordpress_org_data['active_installs']
33
+ end
34
+
35
+ private
36
+
37
+ # @return [ Hash ]
38
+ def fetch_wordpress_org_data
39
+ url = wordpress_org_api_url
40
+ return {} unless url
41
+
42
+ res = Browser.get(url, connecttimeout: WORDPRESS_ORG_API_TIMEOUT, timeout: WORDPRESS_ORG_API_TIMEOUT)
43
+ return {} unless res.code == 200
44
+
45
+ data = JSON.parse(res.body)
46
+ data.is_a?(Hash) ? data : {}
47
+ rescue StandardError
48
+ {}
49
+ end
50
+ end
51
+
52
+ include WordpressOrgData
53
+ end
54
+ end
55
+ end
@@ -6,8 +6,8 @@ module WPScan
6
6
  class WpItem
7
7
  include Vulnerable
8
8
  include Finders::Finding
9
- include CMSScanner::Target::Platform::PHP
10
- include CMSScanner::Target::Server::Generic
9
+ include WPScan::Target::Platform::PHP
10
+ include WPScan::Target::Server::Generic
11
11
 
12
12
  # Most common readme filenames, based on checking all public plugins and themes.
13
13
  READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
@@ -41,23 +41,25 @@ module WPScan
41
41
 
42
42
  Array(db_data['vulnerabilities']).each do |json_vuln|
43
43
  vulnerability = Vulnerability.load_from_json(json_vuln)
44
+ vulnerability.detected_version = version || nil
44
45
  @vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
45
46
  end
46
47
 
47
48
  @vulnerabilities
48
49
  end
49
50
 
50
- # Checks if the wp_item is vulnerable to a specific vulnerability
51
+ # Checks if the wp_item is vulnerable to a specific vulnerability.
52
+ # A vuln with multiple backported ranges matches when any single range
53
+ # covers the detected version.
51
54
  #
52
55
  # @param [ Vulnerability ] vuln Vulnerability to check the item against
53
56
  #
54
57
  # @return [ Boolean ]
55
58
  def vulnerable_to?(vuln)
56
- return false if version && vuln&.introduced_in && version < vuln.introduced_in
59
+ return true unless version
60
+ return true if vuln.nil?
57
61
 
58
- return true unless version && vuln&.fixed_in && !vuln.fixed_in.empty?
59
-
60
- version < vuln.fixed_in
62
+ vuln.affects?(version)
61
63
  end
62
64
 
63
65
  # @return [ String ]
@@ -71,9 +73,109 @@ module WPScan
71
73
  @popular ||= metadata['popular'] ? true : false
72
74
  end
73
75
 
74
- # @return [ String ]
76
+ # @return [ String, nil ]
75
77
  def last_updated
76
- @last_updated ||= metadata['last_updated']
78
+ resolve_last_updated unless defined?(@last_updated)
79
+ @last_updated
80
+ end
81
+
82
+ # @return [ String, nil ] 'db', 'wordpress.org', or nil when last_updated is unknown
83
+ def last_updated_source
84
+ resolve_last_updated unless defined?(@last_updated_source)
85
+ @last_updated_source
86
+ end
87
+
88
+ # WordPress.org takes precedence over local DB metadata since the WPScan
89
+ # DB is not synced in real time and may be stale.
90
+ def resolve_last_updated
91
+ if (api_value = wordpress_org_data['last_updated'])
92
+ @last_updated = api_value
93
+ @last_updated_source = 'WordPress.org'
94
+ elsif (db_value = metadata['last_updated'])
95
+ @last_updated = db_value
96
+ @last_updated_source = 'db'
97
+ else
98
+ @last_updated = nil
99
+ @last_updated_source = nil
100
+ end
101
+ end
102
+
103
+ # @return [ String, nil ] Human-friendly relative time hint for last_updated
104
+ # (e.g. "3 months ago"). nil when last_updated cannot be parsed.
105
+ def last_updated_relative
106
+ @last_updated_relative ||= relative_time_for(last_updated)
107
+ end
108
+
109
+ # Parenthesized annotation appended to the CLI "Last Updated" line, e.g.
110
+ # " (3 months ago, per wordpress.org)". Empty string when there is nothing
111
+ # to annotate.
112
+ #
113
+ # @return [ String ]
114
+ def last_updated_cli_suffix
115
+ parts = []
116
+ parts << last_updated_relative if last_updated_relative
117
+ parts << 'per WordPress.org' if last_updated_source == 'WordPress.org'
118
+ parts.empty? ? '' : " (#{parts.join(', ')})"
119
+ end
120
+
121
+ # ISO 8601 (UTC) representation of last_updated, used by the JSON output so
122
+ # downstream consumers always see a consistent format regardless of whether
123
+ # the value came from the local DB or the wordpress.org API.
124
+ #
125
+ # @return [ String, nil ]
126
+ def last_updated_iso
127
+ return @last_updated_iso if defined?(@last_updated_iso)
128
+
129
+ @last_updated_iso = parse_last_updated&.iso8601
130
+ end
131
+
132
+ # Friendly representation of last_updated, used in CLI output, matching the
133
+ # wordpress.org API style (e.g. "2026-04-14 12:01pm GMT"). Falls back to the
134
+ # raw string when the value cannot be parsed.
135
+ #
136
+ # @return [ String, nil ]
137
+ def last_updated_display
138
+ return @last_updated_display if defined?(@last_updated_display)
139
+
140
+ time = parse_last_updated
141
+ @last_updated_display = time ? time.strftime('%Y-%m-%d %-l:%M%P GMT') : last_updated
142
+ end
143
+
144
+ # @return [ Time, nil ] last_updated parsed as UTC Time, or nil
145
+ def parse_last_updated
146
+ value = last_updated
147
+ return nil if value.nil? || value.to_s.strip.empty?
148
+
149
+ Time.parse(value.to_s).utc
150
+ rescue ArgumentError, TypeError
151
+ nil
152
+ end
153
+
154
+ # @param [ String, nil ] value A timestamp parseable by Time.parse
155
+ # @return [ String, nil ]
156
+ def relative_time_for(value)
157
+ return nil if value.nil? || value.to_s.strip.empty?
158
+
159
+ time = Time.parse(value.to_s).utc
160
+ delta = Time.now.utc - time
161
+ return 'in the future' if delta.negative?
162
+
163
+ seconds = delta.to_i
164
+ case seconds
165
+ when 0...60 then 'just now'
166
+ when 60...3600 then pluralize_unit(seconds / 60, 'minute')
167
+ when 3600...86_400 then pluralize_unit(seconds / 3600, 'hour')
168
+ when 86_400...2_592_000 then pluralize_unit(seconds / 86_400, 'day')
169
+ when 2_592_000...31_536_000 then pluralize_unit(seconds / 2_592_000, 'month')
170
+ else pluralize_unit(seconds / 31_536_000, 'year')
171
+ end
172
+ rescue ArgumentError, TypeError
173
+ nil
174
+ end
175
+
176
+ # @return [ String ]
177
+ def pluralize_unit(count, unit)
178
+ "#{count} #{unit}#{'s' if count != 1} ago"
77
179
  end
78
180
 
79
181
  # @return [ Boolean ]
@@ -133,9 +235,9 @@ module WPScan
133
235
  #
134
236
  # @return [ Boolean ]
135
237
  def directory_listing?(path = nil, params = {})
136
- return if detection_opts[:mode] == :passive
238
+ return false if detection_opts[:mode] == :passive
137
239
 
138
- super(path, params)
240
+ super
139
241
  end
140
242
 
141
243
  # @param [ String ] path
@@ -143,12 +245,12 @@ module WPScan
143
245
  #
144
246
  # @return [ Boolean ]
145
247
  def error_log?(path = 'error_log', params = {})
146
- return if detection_opts[:mode] == :passive
248
+ return false if detection_opts[:mode] == :passive
147
249
 
148
- super(path, params)
250
+ super
149
251
  end
150
252
 
151
- # See CMSScanner::Target#head_and_get
253
+ # See WPScan::Target#head_and_get
152
254
  #
153
255
  # This is used by the error_log? above in the super()
154
256
  # to have the correct path (ie readme.txt checked from the plugin/theme location
@@ -3,13 +3,13 @@
3
3
  module WPScan
4
4
  module Model
5
5
  # WP Version
6
- class WpVersion < CMSScanner::Model::Version
6
+ class WpVersion < WPScan::Model::Version
7
7
  include Vulnerable
8
8
 
9
9
  def initialize(number, opts = {})
10
10
  raise Error::InvalidWordPressVersion unless WpVersion.valid?(number.to_s)
11
11
 
12
- super(number, opts)
12
+ super
13
13
  end
14
14
 
15
15
  # @param [ String ] number
@@ -58,7 +58,9 @@ module WPScan
58
58
  @vulnerabilities = []
59
59
 
60
60
  Array(db_data['vulnerabilities']).each do |json_vuln|
61
- @vulnerabilities << Vulnerability.load_from_json(json_vuln)
61
+ vulnerability = Vulnerability.load_from_json(json_vuln)
62
+ vulnerability.detected_version = self
63
+ @vulnerabilities << vulnerability
62
64
  end
63
65
 
64
66
  @vulnerabilities
@@ -2,9 +2,79 @@
2
2
 
3
3
  module WPScan
4
4
  module Model
5
- # Override of the CMSScanner::XMLRPC to include the references
6
- class XMLRPC < CMSScanner::Model::XMLRPC
7
- include References # To be able to use the :wpvulndb reference if needed
5
+ # XML-RPC interface.
6
+ class XMLRPC < InterestingFinding
7
+ include References
8
+
9
+ # @return [ String ]
10
+ def to_s
11
+ @to_s ||= "XML-RPC seems to be enabled: #{url}"
12
+ end
13
+
14
+ # @return [ Browser ]
15
+ def browser
16
+ @browser ||= WPScan::Browser.instance
17
+ end
18
+
19
+ # @return [ Array<String> ]
20
+ def available_methods
21
+ return @available_methods if @available_methods
22
+
23
+ @available_methods = []
24
+
25
+ res = method_call('system.listMethods').run
26
+ doc = Nokogiri::XML.parse(res.body)
27
+
28
+ doc.search('methodResponse params param value array data value string').each do |s|
29
+ @available_methods << s.text
30
+ end
31
+
32
+ @available_methods
33
+ end
34
+
35
+ # @return [ Boolean ] Whether or not the XMLRPC is enabled
36
+ def enabled?
37
+ !available_methods.empty?
38
+ end
39
+
40
+ # @param [ String ] method_name
41
+ # @param [ Array ] method_params
42
+ # @param [ Hash ] request_params
43
+ #
44
+ # @return [ Typhoeus::Request ]
45
+ def method_call(method_name, method_params = [], request_params = {})
46
+ browser.forge_request(
47
+ url,
48
+ request_params.merge(
49
+ method: :post,
50
+ body: ::XMLRPC::Create.new.methodCall(method_name, *method_params)
51
+ )
52
+ )
53
+ end
54
+
55
+ # @param [ Array<Array> ] methods_and_params
56
+ # @param [ Hash ] request_params
57
+ #
58
+ # Example of methods_and_params:
59
+ # [
60
+ # [method1, param1, param2],
61
+ # [method2, param1],
62
+ # [method3]
63
+ # ]
64
+ #
65
+ # @return [ Typhoeus::Request ]
66
+ def multi_call(methods_and_params = [], request_params = {})
67
+ browser.forge_request(
68
+ url,
69
+ request_params.merge(
70
+ method: :post,
71
+ body: ::XMLRPC::Create.new.methodCall(
72
+ 'system.multicall',
73
+ methods_and_params.collect { |m| { methodName: m[0], params: m[1..] } }
74
+ )
75
+ )
76
+ )
77
+ end
8
78
 
9
79
  # @return [ Hash ]
10
80
  def references
data/app/models.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  module WPScan
4
4
  module Model
5
- include CMSScanner::Model
6
5
  end
7
6
  end
8
7
 
@@ -10,9 +9,11 @@ require_relative 'models/interesting_finding'
10
9
  require_relative 'models/wp_version'
11
10
  require_relative 'models/xml_rpc'
12
11
  require_relative 'models/wp_item'
12
+ require_relative 'models/wp_item/wordpress_org_data'
13
13
  require_relative 'models/timthumb'
14
14
  require_relative 'models/media'
15
15
  require_relative 'models/plugin'
16
16
  require_relative 'models/theme'
17
17
  require_relative 'models/config_backup'
18
18
  require_relative 'models/db_export'
19
+ require_relative 'models/backup_folder'
@@ -0,0 +1,46 @@
1
+ # Windows
2
+ Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5
3
+ Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14
4
+ Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27
5
+ Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1
6
+ Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)
7
+ Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
8
+ Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
9
+ Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1
10
+ Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6
11
+ Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1
12
+ Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0
13
+ Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1
14
+ Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)
15
+ Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
16
+ Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0)
17
+ Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00
18
+ Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5
19
+
20
+ # MAC
21
+ Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13
22
+ Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15
23
+ Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
24
+ Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3
25
+ Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3
26
+ Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2; rv:10.0.1) Gecko/20100101 Firefox/10.0.1
27
+ Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10
28
+
29
+ # Linux
30
+ Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1
31
+ Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24
32
+ Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9
33
+ Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Gecko Firefox/11.0
34
+ Mozilla/5.0 (X11; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0
35
+ Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00
36
+ Mozilla/5.0 (X11; U; Linux x86_64; us; rv:1.9.1.19) Gecko/20110430 shadowfox/7.0 (like Firefox/7.0
37
+
38
+ # iPad
39
+ Mozilla/5.0 (iPad; CPU OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D201 Safari/9537.53
40
+ Mozilla/5.0 (iPad; CPU OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53
41
+ Mozilla/5.0 (iPad; CPU OS 6_1_3 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10B329 Safari/8536.25
42
+
43
+ # iPhone
44
+ Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53
45
+ Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_3 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B511 Safari/9537.53
46
+ Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D201 Safari/9537.53
@@ -6,9 +6,9 @@ _______________________________________________________________
6
6
  \ /\ / | | ____) | (__| (_| | | | |
7
7
  \/ \/ |_| |_____/ \___|\__,_|_| |_|
8
8
 
9
- WordPress Security Scanner by the WPScan Team
9
+ WordPress Security Scanner
10
10
  Version <%= WPScan::VERSION %>
11
- <%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
12
- @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
11
+ An Automattic endeavor
12
+ https://automattic.com
13
13
  _______________________________________________________________
14
14
 
@@ -0,0 +1,15 @@
1
+ <%= info_icon %> Finished: <%= @stop_time.asctime %>
2
+ <%= info_icon %> Requests Done: <%= @requests_done %>
3
+ <%= info_icon %> Cached Requests: <%= @cached_requests %>
4
+ <% if @response_status_codes && !@response_status_codes.empty? -%>
5
+ <%= info_icon %> Most response codes received: <%= @response_status_codes.map { |label, count| "#{label}: #{count}" }.join(', ') %>
6
+ <% end -%>
7
+ <% if @response_status_codes_warnings && !@response_status_codes_warnings.empty? -%>
8
+ <% @response_status_codes_warnings.each do |warning| -%>
9
+ <%= warning_icon %> <%= warning %>
10
+ <% end -%>
11
+ <% end -%>
12
+ <%= info_icon %> Data Sent: <%= @data_sent.bytes_to_human %>
13
+ <%= info_icon %> Data Received: <%= @data_received.bytes_to_human %>
14
+ <%= info_icon %> Memory used: <%= @used_memory.bytes_to_human %>
15
+ <%= info_icon %> Elapsed time: <%= Time.at(@elapsed).utc.strftime('%H:%M:%S') %>
@@ -0,0 +1,4 @@
1
+ <%= @help %>
2
+ <% if @simple -%>
3
+ [!] To see full list of options use --hh.
4
+ <% end -%>
@@ -0,0 +1,11 @@
1
+ <%= info_icon %> URL: <%= @url %> [<%= @ip %>]
2
+ <% if @url != @effective_url -%>
3
+ <%= info_icon %> Effective URL: <%= @effective_url %>
4
+ <% end -%>
5
+ <%= info_icon %> Started: <%= @start_time.asctime %>
6
+ <%= info_icon %> Command Line: wpscan <%= @command_line %>
7
+ <%= info_icon %> Hostname: <%= @hostname %>
8
+ <% if WPScan::ParsedCli.exclude_vulns && !WPScan::ParsedCli.exclude_vulns.empty? -%>
9
+ <%= warning_icon %> Excluded vulnerabilities: <%= Array(WPScan::ParsedCli.exclude_vulns).join(', ') %>
10
+ <% end -%>
11
+
@@ -0,0 +1,11 @@
1
+ <% if @backup_folders.empty? -%>
2
+ <%= notice_icon %> No Backup Folders Found.
3
+ <% else -%>
4
+ <%= notice_icon %> Backup Folder(s) Identified:
5
+ <% @backup_folders.each do |backup_folder| -%>
6
+
7
+ <% icon = backup_folder.severity == :high ? critical_icon : warning_icon -%>
8
+ <%= icon %> <%= backup_folder %>
9
+ <%= render('@finding', item: backup_folder) -%>
10
+ <% end -%>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= bar_clear -%>
2
+
3
+ <%= info_icon %> <%= @plugin %>
4
+ <%= render('@wp_item', wp_item: @plugin) -%>
5
+ |
6
+ <%= render('@finding', item: @plugin) -%>
7
+ |
8
+ <% if @plugin.version -%>
9
+ | Version: <%= @plugin.version %> (<%= @plugin.version.confidence %>% confidence)
10
+ <%= render('@finding', item: @plugin.version) -%>
11
+ <% else -%>
12
+ | The version could not be determined.
13
+ <% end -%>
@@ -4,17 +4,6 @@
4
4
  <% else -%>
5
5
  <%= notice_icon %> Plugin(s) Identified:
6
6
  <% @plugins.each do |plugin| -%>
7
-
8
- <%= info_icon %> <%= plugin %>
9
- <%= render('@wp_item', wp_item: plugin) -%>
10
- |
11
- <%= render('@finding', item: plugin) -%>
12
- |
13
- <% if plugin.version -%>
14
- | Version: <%= plugin.version %> (<%= plugin.version.confidence %>% confidence)
15
- <%= render('@finding', item: plugin.version) -%>
16
- <% else -%>
17
- | The version could not be determined.
18
- <% end -%>
7
+ <%= render('plugin', plugin: plugin) -%>
19
8
  <% end -%>
20
9
  <% end %>
@@ -0,0 +1,4 @@
1
+ <%= bar_clear -%>
2
+
3
+ <%= info_icon %> <%= @theme %>
4
+ <%= render('@theme', theme: @theme, show_parents: false) -%>
@@ -4,8 +4,6 @@
4
4
  <% else -%>
5
5
  <%= notice_icon %> Theme(s) Identified:
6
6
  <% @themes.each do |theme| -%>
7
-
8
- <%= info_icon %> <%= theme %>
9
- <%= render('@theme', theme: theme, show_parents: false) -%>
7
+ <%= render('theme', theme: theme) -%>
10
8
  <% end -%>
11
9
  <% end %>
@@ -0,0 +1,4 @@
1
+ <%= bar_clear -%>
2
+
3
+ <%= info_icon %> <%= @user %>
4
+ <%= render('@finding', item: @user) -%>
@@ -4,8 +4,6 @@
4
4
  <% else -%>
5
5
  <%= notice_icon %> User(s) Identified:
6
6
  <% @users.each do |user| -%>
7
-
8
- <%= info_icon %> <%= user %>
9
- <%= render('@finding', item: user) -%>
7
+ <%= render('user', user: user) -%>
10
8
  <% end -%>
11
9
  <% end %>
@@ -18,7 +18,7 @@
18
18
  <% end -%>
19
19
  <% end -%>
20
20
  <% end -%>
21
- <% if @item.respond_to?(:vulnerabilities) && !(vulns = @item.vulnerabilities).empty? -%>
21
+ <% if @item.respond_to?(:filtered_vulnerabilities) && !(vulns = @item.filtered_vulnerabilities).empty? -%>
22
22
  <% vulns_size = vulns.size -%>
23
23
  |
24
24
  | <%= critical_icon %> <%= vulns_size %> <%= vulns_size == 1 ? 'vulnerability' : 'vulnerabilities' %> identified:
@@ -0,0 +1,10 @@
1
+ <% unless @a.empty? -%>
2
+ <% if @a.size == 1 -%>
3
+ | <%= @s %>: <%= @a.first %>
4
+ <% else -%>
5
+ | <%= @p %>:
6
+ <% @a.each do |line| -%>
7
+ | - <%= line %>
8
+ <% end -%>
9
+ <% end -%>
10
+ <% end -%>
@@ -0,0 +1,23 @@
1
+ <% unless @findings.empty? -%>
2
+ Interesting Finding(s):
3
+ <% @findings.each do |finding| -%>
4
+
5
+ <%= info_icon %> <%= finding %>
6
+ <%= render('_array', a: finding.interesting_entries, s: 'Interesting Entry', p: 'Interesting Entries') -%>
7
+ | Found By: <%= finding.found_by %>
8
+ <% if finding.confidence > 0 -%>
9
+ | Confidence: <%= finding.confidence %>%
10
+ <% end -%>
11
+ <% unless (confirmed = finding.confirmed_by).empty? -%>
12
+ <% if confirmed.size == 1 -%>
13
+ | Confirmed By: <%= confirmed.first.found_by %><% if confirmed.first.confidence > 0 %>, <%= confirmed.first.confidence %>% confidence<% end %>
14
+ <% else -%>
15
+ | Confirmed By:
16
+ <% confirmed.each do |c| -%>
17
+ | - <%= c.found_by %><% if c.confidence > 0 %>, <%= c.confidence %>% confidence<% end %>
18
+ <% end -%>
19
+ <% end -%>
20
+ <% end -%>
21
+ <%= render('_array', a: finding.references_urls, s: 'Reference', p: 'References') -%>
22
+ <% end -%>
23
+ <% end %>
@@ -0,0 +1,5 @@
1
+
2
+ Scan Aborted: <%= @reason %>
3
+ <% if @verbose -%>
4
+ Trace: <%= @trace.join("\n") %>
5
+ <% end %>
@@ -0,0 +1,5 @@
1
+
2
+ Update Aborted: <%= @reason %>
3
+ <% if @verbose -%>
4
+ Trace: <%= @trace.join("\n") %>
5
+ <% end %>
@@ -1,6 +1,8 @@
1
1
  <% unless @status.empty? -%>
2
2
  <% if @status['http_error'] -%>
3
3
  <%= critical_icon %> WPScan DB API, <%= @status['http_error'].to_s %>
4
+ <% elsif @status['parse_error'] -%>
5
+ <%= critical_icon %> WPScan DB API returned an invalid response. Please check your network/proxy configuration or try again later.
4
6
  <% else -%>
5
7
  <%= info_icon %> WPScan DB API OK
6
8
  | Plan: <%= @status['plan'] %>