app/models/wp_item.rb in wpscan-3.4.5 vs app/models/wp_item.rb in wpscan-3.5.0

- old
+ new

@@ -1,158 +1,170 @@ +# frozen_string_literal: true + module WPScan - # WpItem (superclass of Plugin & Theme) - class WpItem - include Vulnerable - include Finders::Finding - include CMSScanner::Target::Platform::PHP - include CMSScanner::Target::Server::Generic + module Model + # WpItem (superclass of Plugin & Theme) + class WpItem + include Vulnerable + include Finders::Finding + include CMSScanner::Target::Platform::PHP + include CMSScanner::Target::Server::Generic - READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze - CHANGELOGS = %w[changelog.txt CHANGELOG.md changelog.md].freeze + READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze - attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :db_data + attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data - delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, to: :blog + delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, :head_or_get_params, to: :blog - # @param [ String ] slug The plugin/theme slug - # @param [ Target ] blog The targeted blog - # @param [ Hash ] opts - # @option opts [ Symbol ] :mode The detection mode to use - # @option opts [ Hash ] :version_detection The options to use when looking for the version - # @option opts [ String ] :url The URL of the item - def initialize(slug, blog, opts = {}) - @slug = URI.decode(slug) - @blog = blog - @uri = Addressable::URI.parse(opts[:url]) if opts[:url] + # @param [ String ] slug The plugin/theme slug + # @param [ Target ] blog The targeted blog + # @param [ Hash ] opts + # @option opts [ Symbol ] :mode The detection mode to use + # @option opts [ Hash ] :version_detection The options to use when looking for the version + # @option opts [ String ] :url The URL of the item + def initialize(slug, blog, opts = {}) + @slug = URI.decode(slug) + @blog = blog + @uri = Addressable::URI.parse(opts[:url]) if opts[:url] - @detection_opts = { mode: opts[:mode] } - @version_detection_opts = opts[:version_detection] || {} + @detection_opts = { mode: opts[:mode] } + @version_detection_opts = opts[:version_detection] || {} - parse_finding_options(opts) - end + parse_finding_options(opts) + end - # @return [ Array<Vulnerabily> ] - def vulnerabilities - return @vulnerabilities if @vulnerabilities + # @return [ Array<Vulnerabily> ] + def vulnerabilities + return @vulnerabilities if @vulnerabilities - @vulnerabilities = [] + @vulnerabilities = [] - [*db_data['vulnerabilities']].each do |json_vuln| - vulnerability = Vulnerability.load_from_json(json_vuln) - @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) + [*db_data['vulnerabilities']].each do |json_vuln| + vulnerability = Vulnerability.load_from_json(json_vuln) + @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) + end + + @vulnerabilities end - @vulnerabilities - end + # Checks if the wp_item is vulnerable to a specific vulnerability + # + # @param [ Vulnerability ] vuln Vulnerability to check the item against + # + # @return [ Boolean ] + def vulnerable_to?(vuln) + return true unless version && vuln && vuln.fixed_in && !vuln.fixed_in.empty? - # Checks if the wp_item is vulnerable to a specific vulnerability - # - # @param [ Vulnerability ] vuln Vulnerability to check the item against - # - # @return [ Boolean ] - def vulnerable_to?(vuln) - return true unless version && vuln && vuln.fixed_in && !vuln.fixed_in.empty? + version < vuln.fixed_in + end - version < vuln.fixed_in - end + # @return [ String ] + def latest_version + @latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil + end - # @return [ String ] - def latest_version - @latest_version ||= db_data['latest_version'] ? WPScan::Version.new(db_data['latest_version']) : nil - end + # Not used anywhere ATM + # @return [ Boolean ] + def popular? + @popular ||= db_data['popular'] + end - # Not used anywhere ATM - # @return [ Boolean ] - def popular? - @popular ||= db_data['popular'] - end + # @return [ String ] + def last_updated + @last_updated ||= db_data['last_updated'] + end - # @return [ String ] - def last_updated - @last_updated ||= db_data['last_updated'] - end + # @return [ Boolean ] + def outdated? + @outdated ||= if version && latest_version + version < latest_version + else + false + end + end - # @return [ Boolean ] - def outdated? - @outdated ||= if version && latest_version - version < latest_version - else - false - end - end + # URI.encode is preferered over Addressable::URI.encode as it will encode + # leading # character: + # URI.encode('#t#') => %23t%23 + # Addressable::URI.encode('#t#') => #t%23 + # + # @param [ String ] path Optional path to merge with the uri + # + # @return [ String ] + def url(path = nil) + return unless @uri + return @uri.to_s unless path - # URI.encode is preferered over Addressable::URI.encode as it will encode - # leading # character: - # URI.encode('#t#') => %23t%23 - # Addressable::URI.encode('#t#') => #t%23 - # - # @param [ String ] path Optional path to merge with the uri - # - # @return [ String ] - def url(path = nil) - return unless @uri - return @uri.to_s unless path + @uri.join(URI.encode(path)).to_s + end - @uri.join(URI.encode(path)).to_s - end + # @return [ Boolean ] + def ==(other) + self.class == other.class && slug == other.slug + end - # @return [ Boolean ] - def ==(other) - self.class == other.class && slug == other.slug - end + def to_s + slug + end - def to_s - slug - end + # @return [ Symbol ] The Class symbol associated to the item + def classify + @classify ||= classify_slug(slug) + end - # @return [ Symbol ] The Class symbol associated to the item - def classify - @classify ||= classify_slug(slug) - end + # @return [ String, False ] The readme url if found, false otherwise + def readme_url + return if detection_opts[:mode] == :passive - # @return [ String ] The readme url if found - def readme_url - return if detection_opts[:mode] == :passive + return @readme_url unless @readme_url.nil? - if @readme_url.nil? READMES.each do |path| - return @readme_url = url(path) if Browser.get(url(path)).code == 200 + 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 - @readme_url - end + # @param [ String ] path + # @param [ Hash ] params The request params + # + # @return [ Boolean ] + def directory_listing?(path = nil, params = {}) + return if detection_opts[:mode] == :passive - # @return [ String, false ] The changelog urr if found - def changelog_url - return if detection_opts[:mode] == :passive - - if @changelog_url.nil? - CHANGELOGS.each do |path| - return @changelog_url = url(path) if Browser.get(url(path)).code == 200 - end + super(path, params) end - @changelog_url - end + # @param [ String ] path + # @param [ Hash ] params The request params + # + # @return [ Boolean ] + def error_log?(path = 'error_log', params = {}) + return if detection_opts[:mode] == :passive - # @param [ String ] path - # @param [ Hash ] params The request params - # - # @return [ Boolean ] - def directory_listing?(path = nil, params = {}) - return if detection_opts[:mode] == :passive + super(path, params) + end - super(path, params) - end + # See CMSScanner::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 + # + # @param [ String ] path + # @param [ Array<String> ] codes + # @param [ Hash ] params The requests params + # @option params [ Hash ] :head Request params for the HEAD + # @option params [ hash ] :get Request params for the GET + # + # @return [ Typhoeus::Response ] + def head_and_get(path, codes = [200], params = {}) + final_path = +@path_from_blog + final_path << URI.encode(path) unless path.nil? - # @param [ String ] path - # @param [ Hash ] params The request params - # - # @return [ Boolean ] - def error_log?(path = 'error_log', params = {}) - return if detection_opts[:mode] == :passive - - super(path, params) + blog.head_and_get(final_path, codes, params) + end end end end