Class: WPScan::Model::WpItem
- Inherits:
-
Object
- Object
- WPScan::Model::WpItem
- Includes:
- Finders::Finding, WordpressOrgData, Target::Platform::PHP, Target::Server::Generic, Vulnerable
- Defined in:
- app/models/wp_item.rb,
app/models/wp_item/wordpress_org_data.rb
Overview
WpItem (superclass of Plugin & Theme)
Defined Under Namespace
Modules: WordpressOrgData
Constant Summary collapse
- READMES =
Most common readme filenames, based on checking all public plugins and themes.
%w[readme.txt README.txt README.md readme.md Readme.txt].freeze
Constants included from WordpressOrgData
WordpressOrgData::WORDPRESS_ORG_API_TIMEOUT
Constants included from Target::Platform::PHP
Target::Platform::PHP::DEBUG_LOG_PATTERN, Target::Platform::PHP::ERROR_LOG_PATTERN, Target::Platform::PHP::FPD_PATTERN
Constants included from Finders::Finding
Finders::Finding::FINDING_OPTS
Instance Attribute Summary collapse
-
#blog ⇒ Object
readonly
Returns the value of attribute blog.
-
#db_data ⇒ Object
readonly
Returns the value of attribute db_data.
-
#detection_opts ⇒ Object
readonly
Returns the value of attribute detection_opts.
-
#path_from_blog ⇒ Object
readonly
Returns the value of attribute path_from_blog.
-
#slug ⇒ Object
readonly
Returns the value of attribute slug.
-
#uri ⇒ Object
readonly
Returns the value of attribute uri.
-
#version_detection_opts ⇒ Object
readonly
Returns the value of attribute version_detection_opts.
Instance Method Summary collapse
- #==(other) ⇒ Boolean
-
#classify ⇒ Symbol
The Class symbol associated to the item.
- #directory_listing?(path = nil, params = {}) ⇒ Boolean
- #error_log?(path = 'error_log', params = {}) ⇒ Boolean
-
#head_and_get(path, codes = [200], params = {}) ⇒ Typhoeus::Response
See WPScan::Target#head_and_get.
-
#initialize(slug, blog, opts = {}) ⇒ WpItem
constructor
A new instance of WpItem.
- #last_updated ⇒ String?
-
#last_updated_cli_suffix ⇒ String
Parenthesized annotation appended to the CLI “Last Updated” line, e.g.
-
#last_updated_display ⇒ String?
Friendly representation of last_updated, used in CLI output, matching the wordpress.org API style (e.g. “2026-04-14 12:01pm GMT”).
-
#last_updated_iso ⇒ String?
ISO 8601 (UTC) representation of last_updated, used by the JSON output so downstream consumers always see a consistent format regardless of whether the value came from the local DB or the wordpress.org API.
-
#last_updated_relative ⇒ String?
Human-friendly relative time hint for last_updated (e.g. “3 months ago”).
-
#last_updated_source ⇒ String?
‘db’, ‘wordpress.org’, or nil when last_updated is unknown.
- #latest_version ⇒ String
- #outdated? ⇒ Boolean
-
#parse_last_updated ⇒ Time?
Last_updated parsed as UTC Time, or nil.
- #pluralize_unit(count, unit) ⇒ String
-
#popular? ⇒ Boolean
Not used anywhere ATM.
- #potential_readme_filenames ⇒ Object
-
#readme_url ⇒ String, False
The readme url if found, false otherwise.
- #relative_time_for(value) ⇒ String?
-
#resolve_last_updated ⇒ Object
WordPress.org takes precedence over local DB metadata since the WPScan DB is not synced in real time and may be stale.
- #to_s ⇒ Object
- #url(path = nil) ⇒ String
- #vulnerabilities ⇒ Array<Vulnerabily>
-
#vulnerable_to?(vuln) ⇒ Boolean
Checks if the wp_item is vulnerable to a specific vulnerability.
Methods included from WordpressOrgData
#active_installs, #wordpress_org_api_url, #wordpress_org_data
Methods included from Target::Server::Generic
#directory_listing_entries, #headers, #server
Methods included from Target::Platform::PHP
#debug_log?, #full_path_disclosure?, #full_path_disclosure_entries, #install_body_cap, #log_file?, #stream_capped_body
Methods included from Finders::Finding
#<=>, #confidence, #confidence=, #confirmed_by, #eql?, included, #interesting_entries, #parse_finding_options
Methods included from Vulnerable
#filtered_vulnerabilities, #vulnerability_filter, #vulnerable?
Constructor Details
#initialize(slug, blog, opts = {}) ⇒ WpItem
Returns a new instance of WpItem.
25 26 27 28 29 30 31 32 33 34 |
# File 'app/models/wp_item.rb', line 25 def initialize(slug, blog, opts = {}) @slug = Addressable::URI.unencode(slug) @blog = blog @uri = Addressable::URI.parse(opts[:url]) if opts[:url] @detection_opts = { mode: opts[:mode] } @version_detection_opts = opts[:version_detection] || {} (opts) end |
Instance Attribute Details
#blog ⇒ Object (readonly)
Returns the value of attribute blog.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def blog @blog end |
#db_data ⇒ Object (readonly)
Returns the value of attribute db_data.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def db_data @db_data end |
#detection_opts ⇒ Object (readonly)
Returns the value of attribute detection_opts.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def detection_opts @detection_opts end |
#path_from_blog ⇒ Object (readonly)
Returns the value of attribute path_from_blog.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def path_from_blog @path_from_blog end |
#slug ⇒ Object (readonly)
Returns the value of attribute slug.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def slug @slug end |
#uri ⇒ Object (readonly)
Returns the value of attribute uri.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def uri @uri end |
#version_detection_opts ⇒ Object (readonly)
Returns the value of attribute version_detection_opts.
15 16 17 |
# File 'app/models/wp_item.rb', line 15 def version_detection_opts @version_detection_opts end |
Instance Method Details
#==(other) ⇒ Boolean
199 200 201 |
# File 'app/models/wp_item.rb', line 199 def ==(other) self.class == other.class && slug == other.slug end |
#classify ⇒ Symbol
Returns The Class symbol associated to the item.
208 209 210 |
# File 'app/models/wp_item.rb', line 208 def classify @classify ||= classify_slug(slug) end |
#directory_listing?(path = nil, params = {}) ⇒ Boolean
235 236 237 238 239 |
# File 'app/models/wp_item.rb', line 235 def directory_listing?(path = nil, params = {}) return false if detection_opts[:mode] == :passive super end |
#error_log?(path = 'error_log', params = {}) ⇒ Boolean
245 246 247 248 249 |
# File 'app/models/wp_item.rb', line 245 def error_log?(path = 'error_log', params = {}) return false if detection_opts[:mode] == :passive super end |
#head_and_get(path, codes = [200], params = {}) ⇒ Typhoeus::Response
See WPScan::Target#head_and_get
This is used by the error_log? above in the super() to have the correct path (ie readme.txt checked from the plugin/theme location and not from the blog root). Could also be used in finders
264 265 266 267 268 269 |
# File 'app/models/wp_item.rb', line 264 def head_and_get(path, codes = [200], params = {}) final_path = @path_from_blog.dup # @path_from_blog is set in the plugin/theme final_path << path unless path.nil? blog.head_and_get(final_path, codes, params) end |
#last_updated ⇒ String?
75 76 77 78 |
# File 'app/models/wp_item.rb', line 75 def last_updated resolve_last_updated unless defined?(@last_updated) @last_updated end |
#last_updated_cli_suffix ⇒ String
Parenthesized annotation appended to the CLI “Last Updated” line, e.g. “ (3 months ago, per wordpress.org)”. Empty string when there is nothing to annotate.
112 113 114 115 116 117 |
# File 'app/models/wp_item.rb', line 112 def last_updated_cli_suffix parts = [] parts << last_updated_relative if last_updated_relative parts << 'per WordPress.org' if last_updated_source == 'WordPress.org' parts.empty? ? '' : " (#{parts.join(', ')})" end |
#last_updated_display ⇒ String?
Friendly representation of last_updated, used in CLI output, matching the wordpress.org API style (e.g. “2026-04-14 12:01pm GMT”). Falls back to the raw string when the value cannot be parsed.
135 136 137 138 139 140 |
# File 'app/models/wp_item.rb', line 135 def last_updated_display return @last_updated_display if defined?(@last_updated_display) time = parse_last_updated @last_updated_display = time ? time.strftime('%Y-%m-%d %-l:%M%P GMT') : last_updated end |
#last_updated_iso ⇒ String?
ISO 8601 (UTC) representation of last_updated, used by the JSON output so downstream consumers always see a consistent format regardless of whether the value came from the local DB or the wordpress.org API.
124 125 126 127 128 |
# File 'app/models/wp_item.rb', line 124 def last_updated_iso return @last_updated_iso if defined?(@last_updated_iso) @last_updated_iso = parse_last_updated&.iso8601 end |
#last_updated_relative ⇒ String?
Returns Human-friendly relative time hint for last_updated (e.g. “3 months ago”). nil when last_updated cannot be parsed.
103 104 105 |
# File 'app/models/wp_item.rb', line 103 def last_updated_relative @last_updated_relative ||= relative_time_for(last_updated) end |
#last_updated_source ⇒ String?
Returns ‘db’, ‘wordpress.org’, or nil when last_updated is unknown.
81 82 83 84 |
# File 'app/models/wp_item.rb', line 81 def last_updated_source resolve_last_updated unless defined?(@last_updated_source) @last_updated_source end |
#latest_version ⇒ String
64 65 66 |
# File 'app/models/wp_item.rb', line 64 def latest_version @latest_version ||= ['latest_version'] ? Model::Version.new(['latest_version']) : nil end |
#outdated? ⇒ Boolean
180 181 182 183 184 185 186 |
# File 'app/models/wp_item.rb', line 180 def outdated? @outdated ||= if version && latest_version version < latest_version else false end end |
#parse_last_updated ⇒ Time?
Returns last_updated parsed as UTC Time, or nil.
143 144 145 146 147 148 149 150 |
# File 'app/models/wp_item.rb', line 143 def parse_last_updated value = last_updated return nil if value.nil? || value.to_s.strip.empty? Time.parse(value.to_s).utc rescue ArgumentError, TypeError nil end |
#pluralize_unit(count, unit) ⇒ String
175 176 177 |
# File 'app/models/wp_item.rb', line 175 def pluralize_unit(count, unit) "#{count} #{unit}#{'s' if count != 1} ago" end |
#popular? ⇒ Boolean
Not used anywhere ATM
70 71 72 |
# File 'app/models/wp_item.rb', line 70 def popular? @popular ||= ['popular'] ? true : false end |
#potential_readme_filenames ⇒ Object
227 228 229 |
# File 'app/models/wp_item.rb', line 227 def potential_readme_filenames @potential_readme_filenames ||= READMES end |
#readme_url ⇒ String, False
Returns The readme url if found, false otherwise.
213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'app/models/wp_item.rb', line 213 def readme_url return if detection_opts[:mode] == :passive return @readme_url unless @readme_url.nil? potential_readme_filenames.each do |path| t_url = url(path) return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200 end @readme_url = false end |
#relative_time_for(value) ⇒ String?
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'app/models/wp_item.rb', line 154 def relative_time_for(value) return nil if value.nil? || value.to_s.strip.empty? time = Time.parse(value.to_s).utc delta = Time.now.utc - time return 'in the future' if delta.negative? seconds = delta.to_i case seconds when 0...60 then 'just now' when 60...3600 then pluralize_unit(seconds / 60, 'minute') when 3600...86_400 then pluralize_unit(seconds / 3600, 'hour') when 86_400...2_592_000 then pluralize_unit(seconds / 86_400, 'day') when 2_592_000...31_536_000 then pluralize_unit(seconds / 2_592_000, 'month') else pluralize_unit(seconds / 31_536_000, 'year') end rescue ArgumentError, TypeError nil end |
#resolve_last_updated ⇒ Object
WordPress.org takes precedence over local DB metadata since the WPScan DB is not synced in real time and may be stale.
88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'app/models/wp_item.rb', line 88 def resolve_last_updated if (api_value = wordpress_org_data['last_updated']) @last_updated = api_value @last_updated_source = 'WordPress.org' elsif (db_value = ['last_updated']) @last_updated = db_value @last_updated_source = 'db' else @last_updated = nil @last_updated_source = nil end end |
#to_s ⇒ Object
203 204 205 |
# File 'app/models/wp_item.rb', line 203 def to_s slug end |
#url(path = nil) ⇒ String
191 192 193 194 195 196 |
# File 'app/models/wp_item.rb', line 191 def url(path = nil) return unless @uri return @uri.to_s unless path @uri.join(Addressable::URI.encode(path)).to_s end |
#vulnerabilities ⇒ Array<Vulnerabily>
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'app/models/wp_item.rb', line 37 def vulnerabilities return @vulnerabilities if @vulnerabilities @vulnerabilities = [] Array(db_data['vulnerabilities']).each do |json_vuln| vulnerability = Vulnerability.load_from_json(json_vuln) @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) end @vulnerabilities end |
#vulnerable_to?(vuln) ⇒ Boolean
Checks if the wp_item is vulnerable to a specific vulnerability
55 56 57 58 59 60 61 |
# File 'app/models/wp_item.rb', line 55 def vulnerable_to?(vuln) return false if version && vuln&.introduced_in && version < vuln.introduced_in return true unless version && vuln&.fixed_in && !vuln.fixed_in.empty? version < vuln.fixed_in end |