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
201 202 203 |
# File 'app/models/wp_item.rb', line 201 def ==(other) self.class == other.class && slug == other.slug end |
#classify ⇒ Symbol
Returns The Class symbol associated to the item.
210 211 212 |
# File 'app/models/wp_item.rb', line 210 def classify @classify ||= classify_slug(slug) end |
#directory_listing?(path = nil, params = {}) ⇒ Boolean
237 238 239 240 241 |
# File 'app/models/wp_item.rb', line 237 def directory_listing?(path = nil, params = {}) return false if detection_opts[:mode] == :passive super end |
#error_log?(path = 'error_log', params = {}) ⇒ Boolean
247 248 249 250 251 |
# File 'app/models/wp_item.rb', line 247 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
266 267 268 269 270 271 |
# File 'app/models/wp_item.rb', line 266 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?
77 78 79 80 |
# File 'app/models/wp_item.rb', line 77 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.
114 115 116 117 118 119 |
# File 'app/models/wp_item.rb', line 114 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.
137 138 139 140 141 142 |
# File 'app/models/wp_item.rb', line 137 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.
126 127 128 129 130 |
# File 'app/models/wp_item.rb', line 126 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.
105 106 107 |
# File 'app/models/wp_item.rb', line 105 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.
83 84 85 86 |
# File 'app/models/wp_item.rb', line 83 def last_updated_source resolve_last_updated unless defined?(@last_updated_source) @last_updated_source end |
#latest_version ⇒ String
66 67 68 |
# File 'app/models/wp_item.rb', line 66 def latest_version @latest_version ||= ['latest_version'] ? Model::Version.new(['latest_version']) : nil end |
#outdated? ⇒ Boolean
182 183 184 185 186 187 188 |
# File 'app/models/wp_item.rb', line 182 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.
145 146 147 148 149 150 151 152 |
# File 'app/models/wp_item.rb', line 145 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
177 178 179 |
# File 'app/models/wp_item.rb', line 177 def pluralize_unit(count, unit) "#{count} #{unit}#{'s' if count != 1} ago" end |
#popular? ⇒ Boolean
Not used anywhere ATM
72 73 74 |
# File 'app/models/wp_item.rb', line 72 def popular? @popular ||= ['popular'] ? true : false end |
#potential_readme_filenames ⇒ Object
229 230 231 |
# File 'app/models/wp_item.rb', line 229 def potential_readme_filenames @potential_readme_filenames ||= READMES end |
#readme_url ⇒ String, False
Returns The readme url if found, false otherwise.
215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'app/models/wp_item.rb', line 215 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?
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'app/models/wp_item.rb', line 156 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.
90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'app/models/wp_item.rb', line 90 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
205 206 207 |
# File 'app/models/wp_item.rb', line 205 def to_s slug end |
#url(path = nil) ⇒ String
193 194 195 196 197 198 |
# File 'app/models/wp_item.rb', line 193 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 49 |
# 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) vulnerability.detected_version = version || nil @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) end @vulnerabilities end |
#vulnerable_to?(vuln) ⇒ Boolean
Checks if the wp_item is vulnerable to a specific vulnerability. A vuln with multiple backported ranges matches when any single range covers the detected version.
58 59 60 61 62 63 |
# File 'app/models/wp_item.rb', line 58 def vulnerable_to?(vuln) return true unless version return true if vuln.nil? vuln.affects?(version) end |