From 816b18b6040cb5339ff256a6a1cb4dc609947a29 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Tue, 23 Feb 2016 18:07:20 +0100 Subject: [PATCH] drop ruby 1.9 support, whitespaces --- .travis.yml | 5 - README.md | 8 +- lib/common/cache_file_store.rb | 4 +- lib/common/collections/wp_items.rb | 2 +- lib/common/collections/wp_plugins.rb | 16 +- lib/common/collections/wp_themes.rb | 16 +- lib/common/collections/wp_timthumbs.rb | 16 +- lib/common/collections/wp_users.rb | 22 +- lib/common/collections/wp_users/detectable.rb | 68 ++--- lib/common/common_helper.rb | 4 + lib/common/hacks.rb | 30 --- lib/common/models/vulnerability.rb | 124 ++++----- lib/common/models/wp_item.rb | 244 +++++++++--------- lib/common/models/wp_item/existable.rb | 100 +++---- lib/common/models/wp_item/findable.rb | 38 +-- lib/common/models/wp_item/vulnerable.rb | 88 +++---- lib/common/models/wp_plugin.rb | 32 +-- lib/common/models/wp_theme.rb | 74 +++--- lib/common/models/wp_theme/findable.rb | 128 ++++----- lib/common/models/wp_theme/versionable.rb | 18 +- lib/common/models/wp_timthumb.rb | 40 +-- lib/common/models/wp_timthumb/versionable.rb | 48 ++-- lib/common/models/wp_user/existable.rb | 172 ++++++------ lib/common/models/wp_version.rb | 76 +++--- lib/common/models/wp_version/findable.rb | 2 - lib/environment.rb | 4 +- lib/wpscan/wp_target/wp_config_backup.rb | 2 +- spec/lib/common/models/wp_item_spec.rb | 5 - .../wp_target/wp_config_backup.rb | 6 +- 29 files changed, 675 insertions(+), 717 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0392dec4..1520f271 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: ruby sudo: false cache: bundler rvm: - - 1.9.2 - - 1.9.3 - 2.0.0 - 2.1.0 - 2.1.1 @@ -23,9 +21,6 @@ script: bundle exec rspec notifications: email: - team@wpscan.org -matrix: - allow_failures: - - rvm: 1.9.2 # do not build gh-pages branch branches: except: diff --git a/README.md b/README.md index da97e242..77c7ba61 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ WPScan comes pre-installed on the following Linux distributions: Prerequisites: -- Ruby >= 1.9.2 - Recommended: 2.3.0 +- Ruby >= 2.0.0 - Recommended: 2.3.0 - Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault - RubyGems - Recommended: latest - Git @@ -156,8 +156,8 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc - rvm install 2.2.4 - rvm use 2.2.4 --default + rvm install 2.3.0 + rvm use 2.3.0 --default echo "gem: --no-ri --no-rdoc" > ~/.gemrc gem install bundler git clone https://github.com/wpscanteam/wpscan.git @@ -192,7 +192,7 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install Then, open the directory of the readline gem (you have to locate it) - cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline + cd ~/.rvm/src/ruby-XXXX/ext/readline ruby extconf.rb make make install diff --git a/lib/common/cache_file_store.rb b/lib/common/cache_file_store.rb index a0f34e3c..09af0714 100644 --- a/lib/common/cache_file_store.rb +++ b/lib/common/cache_file_store.rb @@ -23,9 +23,7 @@ class CacheFileStore @storage_path = File.expand_path(File.join(storage_path, storage_dir)) @serializer = serializer - # File.directory? for ruby <= 1.9 otherwise, - # it makes more sense to do Dir.exist? :/ - unless File.directory?(@storage_path) + unless Dir.exist?(@storage_path) FileUtils.mkdir_p(@storage_path) end end diff --git a/lib/common/collections/wp_items.rb b/lib/common/collections/wp_items.rb index 0ac3adb4..e7a24531 100755 --- a/lib/common/collections/wp_items.rb +++ b/lib/common/collections/wp_items.rb @@ -67,7 +67,7 @@ class WpItems < Array end protected - + # @return [ Class ] def item_class Object.const_get(self.class.to_s.gsub(/.$/, '')) diff --git a/lib/common/collections/wp_plugins.rb b/lib/common/collections/wp_plugins.rb index 997018f2..955259c4 100755 --- a/lib/common/collections/wp_plugins.rb +++ b/lib/common/collections/wp_plugins.rb @@ -1,8 +1,8 @@ -# encoding: UTF-8 - -require 'common/collections/wp_plugins/detectable' - -class WpPlugins < WpItems - extend WpPlugins::Detectable - -end +# encoding: UTF-8 + +require 'common/collections/wp_plugins/detectable' + +class WpPlugins < WpItems + extend WpPlugins::Detectable + +end diff --git a/lib/common/collections/wp_themes.rb b/lib/common/collections/wp_themes.rb index efc61460..4da64e5e 100755 --- a/lib/common/collections/wp_themes.rb +++ b/lib/common/collections/wp_themes.rb @@ -1,8 +1,8 @@ -# encoding: UTF-8 - -require 'common/collections/wp_themes/detectable' - -class WpThemes < WpItems - extend WpThemes::Detectable - -end +# encoding: UTF-8 + +require 'common/collections/wp_themes/detectable' + +class WpThemes < WpItems + extend WpThemes::Detectable + +end diff --git a/lib/common/collections/wp_timthumbs.rb b/lib/common/collections/wp_timthumbs.rb index e274c129..7f94a867 100755 --- a/lib/common/collections/wp_timthumbs.rb +++ b/lib/common/collections/wp_timthumbs.rb @@ -1,8 +1,8 @@ -# encoding: UTF-8 - -require 'common/collections/wp_timthumbs/detectable' - -class WpTimthumbs < WpItems - extend WpTimthumbs::Detectable - -end +# encoding: UTF-8 + +require 'common/collections/wp_timthumbs/detectable' + +class WpTimthumbs < WpItems + extend WpTimthumbs::Detectable + +end diff --git a/lib/common/collections/wp_users.rb b/lib/common/collections/wp_users.rb index 8316b2e7..42a1fa03 100755 --- a/lib/common/collections/wp_users.rb +++ b/lib/common/collections/wp_users.rb @@ -1,11 +1,11 @@ -# encoding: UTF-8 - -require 'common/collections/wp_users/detectable' -require 'common/collections/wp_users/output' -require 'common/collections/wp_users/brute_forcable' - -class WpUsers < WpItems - extend WpUsers::Detectable - include WpUsers::Output - include WpUsers::BruteForcable -end +# encoding: UTF-8 + +require 'common/collections/wp_users/detectable' +require 'common/collections/wp_users/output' +require 'common/collections/wp_users/brute_forcable' + +class WpUsers < WpItems + extend WpUsers::Detectable + include WpUsers::Output + include WpUsers::BruteForcable +end diff --git a/lib/common/collections/wp_users/detectable.rb b/lib/common/collections/wp_users/detectable.rb index 929b9b0e..9a4de606 100755 --- a/lib/common/collections/wp_users/detectable.rb +++ b/lib/common/collections/wp_users/detectable.rb @@ -1,34 +1,34 @@ -# encoding: UTF-8 - -class WpUsers < WpItems - module Detectable - - # @return [ Hash ] - def request_params; {} end - - # No passive detection - # - # @return [ WpUsers ] - def passive_detection(wp_target, options = {}) - new - end - - protected - - # @param [ WpTarget ] wp_target - # @param [ Hash ] options - # @option options [ Range ] :range ((1..10)) - # - # @return [ Array ] - def targets_items(wp_target, options = {}) - range = options[:range] || (1..10) - targets = [] - - range.each do |user_id| - targets << WpUser.new(wp_target.uri, id: user_id) - end - targets - end - - end -end +# encoding: UTF-8 + +class WpUsers < WpItems + module Detectable + + # @return [ Hash ] + def request_params; {} end + + # No passive detection + # + # @return [ WpUsers ] + def passive_detection(wp_target, options = {}) + new + end + + protected + + # @param [ WpTarget ] wp_target + # @param [ Hash ] options + # @option options [ Range ] :range ((1..10)) + # + # @return [ Array ] + def targets_items(wp_target, options = {}) + range = options[:range] || (1..10) + targets = [] + + range.each do |user_id| + targets << WpUser.new(wp_target.uri, id: user_id) + end + targets + end + + end +end diff --git a/lib/common/common_helper.rb b/lib/common/common_helper.rb index 2ffff163..58efa51e 100644 --- a/lib/common/common_helper.rb +++ b/lib/common/common_helper.rb @@ -266,3 +266,7 @@ end def directory_listing_enabled?(url) Browser.get(url.to_s).body[%r{Index of}] ? true : false end + +def url_encode(str) + CGI.escape(str).gsub("+", "%20") +end diff --git a/lib/common/hacks.rb b/lib/common/hacks.rb index d45b085a..eef7fc56 100644 --- a/lib/common/hacks.rb +++ b/lib/common/hacks.rb @@ -1,35 +1,5 @@ # encoding: UTF-8 -# Since ruby 1.9.2, URI::escape is obsolete -# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489 -if RUBY_VERSION >= '1.9.2' - module URI - extend self - - def escape(str) - URI::Parser.new.escape(str) - end - alias :encode :escape - - end -end - -if RUBY_VERSION < '1.9' - class Array - # Fix for grep with symbols in ruby <= 1.8.7 - def _grep_(regexp) - matches = [] - self.each do |value| - value = value.to_s - matches << value if value.match(regexp) - end - matches - end - - alias_method :grep, :_grep_ - end -end - # This is used in WpItem::Existable module Typhoeus class Response diff --git a/lib/common/models/vulnerability.rb b/lib/common/models/vulnerability.rb index c1a94992..78f1078d 100755 --- a/lib/common/models/vulnerability.rb +++ b/lib/common/models/vulnerability.rb @@ -1,62 +1,62 @@ -# encoding: UTF-8 - -require 'vulnerability/output' -require 'vulnerability/urls' - -class Vulnerability - include Vulnerability::Output - include Vulnerability::Urls - - attr_accessor :title, :references, :type, :fixed_in - - # - # @param [ String ] title The title of the vulnerability - # @param [ String ] type The type of the vulnerability - # @param [ Hash ] references References - # @param [ String ] fixed_in Vuln fixed in Version X - # - # @return [ Vulnerability ] - def initialize(title, type, references = {}, fixed_in = '') - @title = title - @type = type - @references = references - @fixed_in = fixed_in - end - - # @param [ Vulnerability ] other - # - # @return [ Boolean ] - # :nocov: - def ==(other) - title == other.title && - type == other.type && - references == other.references && - fixed_in == other.fixed_in - end - # :nocov: - - # Create the Vulnerability from the json_item - # - # @param [ Hash ] json_item - # - # @return [ Vulnerability ] - def self.load_from_json_item(json_item) - references = {} - references['id'] = [json_item['id']] - - %w(url cve secunia osvdb metasploit exploitdb).each do |key| - if json_item['references'][key] - json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array - references[key] = json_item['references'][key] - end - end - - new( - json_item['title'], - json_item['type'], - references, - json_item['fixed_in'] - ) - end - -end +# encoding: UTF-8 + +require 'vulnerability/output' +require 'vulnerability/urls' + +class Vulnerability + include Vulnerability::Output + include Vulnerability::Urls + + attr_accessor :title, :references, :type, :fixed_in + + # + # @param [ String ] title The title of the vulnerability + # @param [ String ] type The type of the vulnerability + # @param [ Hash ] references References + # @param [ String ] fixed_in Vuln fixed in Version X + # + # @return [ Vulnerability ] + def initialize(title, type, references = {}, fixed_in = '') + @title = title + @type = type + @references = references + @fixed_in = fixed_in + end + + # @param [ Vulnerability ] other + # + # @return [ Boolean ] + # :nocov: + def ==(other) + title == other.title && + type == other.type && + references == other.references && + fixed_in == other.fixed_in + end + # :nocov: + + # Create the Vulnerability from the json_item + # + # @param [ Hash ] json_item + # + # @return [ Vulnerability ] + def self.load_from_json_item(json_item) + references = {} + references['id'] = [json_item['id']] + + %w(url cve secunia osvdb metasploit exploitdb).each do |key| + if json_item['references'][key] + json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array + references[key] = json_item['references'][key] + end + end + + new( + json_item['title'], + json_item['type'], + references, + json_item['fixed_in'] + ) + end + +end diff --git a/lib/common/models/wp_item.rb b/lib/common/models/wp_item.rb index 5176c30d..ce6abd9f 100755 --- a/lib/common/models/wp_item.rb +++ b/lib/common/models/wp_item.rb @@ -1,123 +1,121 @@ -# encoding: UTF-8 - -require 'wp_item/findable' -require 'wp_item/versionable' -require 'wp_item/vulnerable' -require 'wp_item/existable' -require 'wp_item/infos' -require 'wp_item/output' - -class WpItem - - extend WpItem::Findable - include WpItem::Versionable - include WpItem::Vulnerable - include WpItem::Existable - include WpItem::Infos - include WpItem::Output - - attr_reader :path - attr_accessor :name, :wp_content_dir, :wp_plugins_dir - - # @return [ Array ] - # Make it private ? - def allowed_options - [:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file] - end - - # @param [ URI ] target_base_uri - # @param [ Hash ] options See allowed_option - # - # @return [ WpItem ] - def initialize(target_base_uri, options = {}) - options[:wp_content_dir] ||= 'wp-content' - options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins' - - set_options(options) - forge_uri(target_base_uri) - end - - def identifier - @identifier ||= name - end - - # @return [ Hash ] - def db_data - @db_data ||= json(db_file)[identifier] || {} - end - - def latest_version - db_data['latest_version'] - end - - def last_updated - db_data['last_ipdated'] - end - - def popular? - db_data['popular'] - end - - # @param [ Hash ] options - # - # @return [ void ] - def set_options(options) - allowed_options.each do |allowed_option| - if options.has_key?(allowed_option) - method = :"#{allowed_option}=" - - if self.respond_to?(method) - self.send(method, options[allowed_option]) - else - raise "#{self.class} does not respond to #{method}" - end - end - end - end - private :set_options - - # @param [ URI ] target_base_uri - # - # @return [ void ] - def forge_uri(target_base_uri) - @uri = target_base_uri - end - - # @return [ URI ] The uri to the WpItem, with the path if present - def uri - path ? @uri.merge(path) : @uri - end - - # @return [ String ] The url to the WpItem - def url; uri.to_s end - - # Sets the path - # - # Variable, such as $wp-plugins$ and $wp-content$ can be used - # and will be replace by their value - # - # @param [ String ] path - # - # @return [ void ] - def path=(path) - @path = URI.encode( - path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir) - ) - end - - # @param [ WpItem ] other - def <=>(other) - name <=> other.name - end - - # @param [ WpItem ] other - def ==(other) - name === other.name - end - - # @param [ WpItem ] other - def ===(other) - self == other && version === other.version - end - -end +# encoding: UTF-8 + +require 'wp_item/findable' +require 'wp_item/versionable' +require 'wp_item/vulnerable' +require 'wp_item/existable' +require 'wp_item/infos' +require 'wp_item/output' + +class WpItem + + extend WpItem::Findable + include WpItem::Versionable + include WpItem::Vulnerable + include WpItem::Existable + include WpItem::Infos + include WpItem::Output + + attr_reader :path + attr_accessor :name, :wp_content_dir, :wp_plugins_dir + + # @return [ Array ] + # Make it private ? + def allowed_options + [:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file] + end + + # @param [ URI ] target_base_uri + # @param [ Hash ] options See allowed_option + # + # @return [ WpItem ] + def initialize(target_base_uri, options = {}) + options[:wp_content_dir] ||= 'wp-content' + options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins' + + set_options(options) + forge_uri(target_base_uri) + end + + def identifier + @identifier ||= name + end + + # @return [ Hash ] + def db_data + @db_data ||= json(db_file)[identifier] || {} + end + + def latest_version + db_data['latest_version'] + end + + def last_updated + db_data['last_ipdated'] + end + + def popular? + db_data['popular'] + end + + # @param [ Hash ] options + # + # @return [ void ] + def set_options(options) + allowed_options.each do |allowed_option| + if options.has_key?(allowed_option) + method = :"#{allowed_option}=" + + if self.respond_to?(method) + self.send(method, options[allowed_option]) + else + raise "#{self.class} does not respond to #{method}" + end + end + end + end + private :set_options + + # @param [ URI ] target_base_uri + # + # @return [ void ] + def forge_uri(target_base_uri) + @uri = target_base_uri + end + + # @return [ URI ] The uri to the WpItem, with the path if present + def uri + path ? @uri.merge(path) : @uri + end + + # @return [ String ] The url to the WpItem + def url; uri.to_s end + + # Sets the path + # + # Variable, such as $wp-plugins$ and $wp-content$ can be used + # and will be replace by their value + # + # @param [ String ] path + # + # @return [ void ] + def path=(path) + @path = path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir) + end + + # @param [ WpItem ] other + def <=>(other) + name <=> other.name + end + + # @param [ WpItem ] other + def ==(other) + name === other.name + end + + # @param [ WpItem ] other + def ===(other) + self == other && version === other.version + end + +end diff --git a/lib/common/models/wp_item/existable.rb b/lib/common/models/wp_item/existable.rb index 7d7c6586..be06e483 100755 --- a/lib/common/models/wp_item/existable.rb +++ b/lib/common/models/wp_item/existable.rb @@ -1,50 +1,50 @@ -# encoding: UTF-8 - -class WpItem - module Existable - - # Check the existence of the WpItem - # If the response is supplied, it's used for the verification - # Otherwise a new request is done - # - # @param [ Hash ] options See exists_from_response? - # @param [ Typhoeus::Response ] response - # - # @return [ Boolean ] - def exists?(options = {}, response = nil) - unless response - response = Browser.get(url) - end - exists_from_response?(response, options) - end - - protected - - # @param [ Typhoeus::Response ] response - # @param [ options ] options - # - # @option options [ Hash ] :error_404_hash The hash of the error 404 page - # @option options [ Hash ] :homepage_hash The hash of the homepage - # @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response - # - # @return [ Boolean ] - def exists_from_response?(response, options = {}) - # 301 included as some items do a self-redirect - # Redirects to the 404 and homepage should be ignored (unless dynamic content is used) - # by the page hashes (error_404_hash & homepage_hash) - if [200, 401, 403, 301].include?(response.code) - if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash]) - if options[:exclude_content] - unless response.body.match(options[:exclude_content]) - return true - end - else - return true - end - end - end - false - end - - end -end +# encoding: UTF-8 + +class WpItem + module Existable + + # Check the existence of the WpItem + # If the response is supplied, it's used for the verification + # Otherwise a new request is done + # + # @param [ Hash ] options See exists_from_response? + # @param [ Typhoeus::Response ] response + # + # @return [ Boolean ] + def exists?(options = {}, response = nil) + unless response + response = Browser.get(url) + end + exists_from_response?(response, options) + end + + protected + + # @param [ Typhoeus::Response ] response + # @param [ options ] options + # + # @option options [ Hash ] :error_404_hash The hash of the error 404 page + # @option options [ Hash ] :homepage_hash The hash of the homepage + # @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response + # + # @return [ Boolean ] + def exists_from_response?(response, options = {}) + # 301 included as some items do a self-redirect + # Redirects to the 404 and homepage should be ignored (unless dynamic content is used) + # by the page hashes (error_404_hash & homepage_hash) + if [200, 401, 403, 301].include?(response.code) + if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash]) + if options[:exclude_content] + unless response.body.match(options[:exclude_content]) + return true + end + else + return true + end + end + end + false + end + + end +end diff --git a/lib/common/models/wp_item/findable.rb b/lib/common/models/wp_item/findable.rb index 476b8cac..f1b7b90c 100755 --- a/lib/common/models/wp_item/findable.rb +++ b/lib/common/models/wp_item/findable.rb @@ -1,19 +1,19 @@ -# encoding: UTF-8 - -class WpItem - attr_reader :found_from - - # Sets the found_from attribute - # - # @param [ String ] method The method which found the WpItem - # - # @return [ void ] - def found_from=(method) - found = method[%r{find_from_(.*)}, 1] - @found_from = found.gsub('_', ' ') if found - end - - module Findable - - end -end +# encoding: UTF-8 + +class WpItem + attr_reader :found_from + + # Sets the found_from attribute + # + # @param [ String ] method The method which found the WpItem + # + # @return [ void ] + def found_from=(method) + found = method[%r{find_from_(.*)}, 1] + @found_from = found.gsub('_', ' ') if found + end + + module Findable + + end +end diff --git a/lib/common/models/wp_item/vulnerable.rb b/lib/common/models/wp_item/vulnerable.rb index 25c2413f..bdfc7925 100755 --- a/lib/common/models/wp_item/vulnerable.rb +++ b/lib/common/models/wp_item/vulnerable.rb @@ -1,44 +1,44 @@ -# encoding: UTF-8 - -class WpItem - module Vulnerable - attr_accessor :db_file, :identifier - - # Get the vulnerabilities associated to the WpItem - # Filters out already fixed vulnerabilities - # - # @return [ Vulnerabilities ] - def vulnerabilities - return @vulnerabilities if @vulnerabilities - - @vulnerabilities = Vulnerabilities.new - - [*db_data['vulnerabilities']].each do |vulnerability| - vulnerability = Vulnerability.load_from_json_item(vulnerability) - @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) - end - - @vulnerabilities - end - - def vulnerable? - vulnerabilities.empty? ? false : true - end - - # Checks if a item is vulnerable to a specific vulnerability - # - # @param [ Vulnerability ] vuln Vulnerability to check the item against - # - # @return [ Boolean ] - def vulnerable_to?(vuln) - if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty? - unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version) - return true - end - else - return true - end - return false - end - end -end +# encoding: UTF-8 + +class WpItem + module Vulnerable + attr_accessor :db_file, :identifier + + # Get the vulnerabilities associated to the WpItem + # Filters out already fixed vulnerabilities + # + # @return [ Vulnerabilities ] + def vulnerabilities + return @vulnerabilities if @vulnerabilities + + @vulnerabilities = Vulnerabilities.new + + [*db_data['vulnerabilities']].each do |vulnerability| + vulnerability = Vulnerability.load_from_json_item(vulnerability) + @vulnerabilities << vulnerability if vulnerable_to?(vulnerability) + end + + @vulnerabilities + end + + def vulnerable? + vulnerabilities.empty? ? false : true + end + + # Checks if a item is vulnerable to a specific vulnerability + # + # @param [ Vulnerability ] vuln Vulnerability to check the item against + # + # @return [ Boolean ] + def vulnerable_to?(vuln) + if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty? + unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version) + return true + end + else + return true + end + return false + end + end +end diff --git a/lib/common/models/wp_plugin.rb b/lib/common/models/wp_plugin.rb index 8c47e158..88dff5ef 100755 --- a/lib/common/models/wp_plugin.rb +++ b/lib/common/models/wp_plugin.rb @@ -1,16 +1,16 @@ -# encoding: UTF-8 - -class WpPlugin < WpItem - # Sets the @uri - # - # @param [ URI ] target_base_uri The URI of the wordpress blog - # - # @return [ void ] - def forge_uri(target_base_uri) - @uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/')) - end - - def db_file - @db_file ||= PLUGINS_FILE - end -end +# encoding: UTF-8 + +class WpPlugin < WpItem + # Sets the @uri + # + # @param [ URI ] target_base_uri The URI of the wordpress blog + # + # @return [ void ] + def forge_uri(target_base_uri) + @uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/") + end + + def db_file + @db_file ||= PLUGINS_FILE + end +end diff --git a/lib/common/models/wp_theme.rb b/lib/common/models/wp_theme.rb index 0a8a478b..dd62ac07 100755 --- a/lib/common/models/wp_theme.rb +++ b/lib/common/models/wp_theme.rb @@ -1,37 +1,37 @@ -# encoding: UTF-8 - -require 'wp_theme/findable' -require 'wp_theme/versionable' -require 'wp_theme/info' -require 'wp_theme/output' -require 'wp_theme/childtheme' - -class WpTheme < WpItem - extend WpTheme::Findable - include WpTheme::Versionable - include WpTheme::Info - include WpTheme::Output - include WpTheme::Childtheme - - attr_accessor :referenced_url - - def allowed_options; super << :referenced_url end - - # Sets the @uri - # - # @param [ URI ] target_base_uri The URI of the wordpress blog - # - # @return [ void ] - def forge_uri(target_base_uri) - @uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/')) - end - - # @return [ String ] The url to the theme stylesheet - def style_url - @uri.merge('style.css').to_s - end - - def db_file - @db_file ||= THEMES_FILE - end -end +# encoding: UTF-8 + +require 'wp_theme/findable' +require 'wp_theme/versionable' +require 'wp_theme/info' +require 'wp_theme/output' +require 'wp_theme/childtheme' + +class WpTheme < WpItem + extend WpTheme::Findable + include WpTheme::Versionable + include WpTheme::Info + include WpTheme::Output + include WpTheme::Childtheme + + attr_accessor :referenced_url + + def allowed_options; super << :referenced_url end + + # Sets the @uri + # + # @param [ URI ] target_base_uri The URI of the wordpress blog + # + # @return [ void ] + def forge_uri(target_base_uri) + @uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/") + end + + # @return [ String ] The url to the theme stylesheet + def style_url + @uri.merge('style.css').to_s + end + + def db_file + @db_file ||= THEMES_FILE + end +end diff --git a/lib/common/models/wp_theme/findable.rb b/lib/common/models/wp_theme/findable.rb index d3aec6a7..10ce0116 100755 --- a/lib/common/models/wp_theme/findable.rb +++ b/lib/common/models/wp_theme/findable.rb @@ -1,64 +1,64 @@ -# encoding: UTF-8 - -class WpTheme < WpItem - module Findable - - # Find the main theme of the blog - # - # @param [ URI ] target_uri - # - # @return [ WpTheme ] - def find(target_uri) - methods.grep(/^find_from_/).each do |method| - if wp_theme = self.send(method, target_uri) - wp_theme.found_from = method - - return wp_theme - end - end - nil - end - - protected - - # Discover the wordpress theme by parsing the css link rel - # - # @param [ URI ] target_uri - # - # @return [ WpTheme ] - def find_from_css_link(target_uri) - response = Browser.get_and_follow_location(target_uri.to_s) - - # https + domain is optional because of relative links - return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i - - new( - target_uri, - name: Regexp.last_match[2], - referenced_url: Regexp.last_match[0], - wp_content_dir: Regexp.last_match[1] - ) - end - - # @param [ URI ] target_uri - # - # @return [ WpTheme ] - def find_from_wooframework(target_uri) - body = Browser.get(target_uri.to_s).body - regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />} - - if matches = regexp.match(body) - woo_theme_name = matches[1] - woo_theme_version = matches[2] - #woo_framework_version = matches[3] # Not used at this time - - return new( - target_uri, - name: woo_theme_name, - version: woo_theme_version - ) - end - end - - end -end +# encoding: UTF-8 + +class WpTheme < WpItem + module Findable + + # Find the main theme of the blog + # + # @param [ URI ] target_uri + # + # @return [ WpTheme ] + def find(target_uri) + methods.grep(/^find_from_/).each do |method| + if wp_theme = self.send(method, target_uri) + wp_theme.found_from = method + + return wp_theme + end + end + nil + end + + protected + + # Discover the wordpress theme by parsing the css link rel + # + # @param [ URI ] target_uri + # + # @return [ WpTheme ] + def find_from_css_link(target_uri) + response = Browser.get_and_follow_location(target_uri.to_s) + + # https + domain is optional because of relative links + return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i + + new( + target_uri, + name: Regexp.last_match[2], + referenced_url: Regexp.last_match[0], + wp_content_dir: Regexp.last_match[1] + ) + end + + # @param [ URI ] target_uri + # + # @return [ WpTheme ] + def find_from_wooframework(target_uri) + body = Browser.get(target_uri.to_s).body + regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />} + + if matches = regexp.match(body) + woo_theme_name = matches[1] + woo_theme_version = matches[2] + #woo_framework_version = matches[3] # Not used at this time + + return new( + target_uri, + name: woo_theme_name, + version: woo_theme_version + ) + end + end + + end +end diff --git a/lib/common/models/wp_theme/versionable.rb b/lib/common/models/wp_theme/versionable.rb index 2b7d9d59..9dad09fa 100755 --- a/lib/common/models/wp_theme/versionable.rb +++ b/lib/common/models/wp_theme/versionable.rb @@ -1,9 +1,9 @@ -# encoding: UTF-8 - -class WpTheme < WpItem - module Versionable - def version - @version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1] - end - end -end +# encoding: UTF-8 + +class WpTheme < WpItem + module Versionable + def version + @version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1] + end + end +end diff --git a/lib/common/models/wp_timthumb.rb b/lib/common/models/wp_timthumb.rb index 8eb4d247..eaf11219 100755 --- a/lib/common/models/wp_timthumb.rb +++ b/lib/common/models/wp_timthumb.rb @@ -1,20 +1,20 @@ -# encoding: UTF-8 - -require 'wp_timthumb/versionable' -require 'wp_timthumb/existable' -require 'wp_timthumb/output' -require 'wp_timthumb/vulnerable' - -class WpTimthumb < WpItem - include WpTimthumb::Versionable - include WpTimthumb::Existable - include WpTimthumb::Output - include WpTimthumb::Vulnerable - - # @param [ WpTimthumb ] other - # - # @return [ Boolean ] - def ==(other) - url == other.url - end -end +# encoding: UTF-8 + +require 'wp_timthumb/versionable' +require 'wp_timthumb/existable' +require 'wp_timthumb/output' +require 'wp_timthumb/vulnerable' + +class WpTimthumb < WpItem + include WpTimthumb::Versionable + include WpTimthumb::Existable + include WpTimthumb::Output + include WpTimthumb::Vulnerable + + # @param [ WpTimthumb ] other + # + # @return [ Boolean ] + def ==(other) + url == other.url + end +end diff --git a/lib/common/models/wp_timthumb/versionable.rb b/lib/common/models/wp_timthumb/versionable.rb index d570966f..9043beda 100755 --- a/lib/common/models/wp_timthumb/versionable.rb +++ b/lib/common/models/wp_timthumb/versionable.rb @@ -1,24 +1,24 @@ -# encoding: UTF-8 - -class WpTimthumb < WpItem - module Versionable - - # Get the version from the body of an invalid request - # See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426 - # - # @return [ String ] The version - def version - unless @version - response = Browser.get(url) - @version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1] - end - @version - end - - # @return [ String ] - def to_s - "#{url}#{ ' v' + version if version}" - end - - end -end +# encoding: UTF-8 + +class WpTimthumb < WpItem + module Versionable + + # Get the version from the body of an invalid request + # See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426 + # + # @return [ String ] The version + def version + unless @version + response = Browser.get(url) + @version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1] + end + @version + end + + # @return [ String ] + def to_s + "#{url}#{ ' v' + version if version}" + end + + end +end diff --git a/lib/common/models/wp_user/existable.rb b/lib/common/models/wp_user/existable.rb index 03f7137a..7af41e96 100755 --- a/lib/common/models/wp_user/existable.rb +++ b/lib/common/models/wp_user/existable.rb @@ -1,86 +1,86 @@ -# encoding: UTF-8 - -class WpUser < WpItem - module Existable - - # @param [ Typhoeus::Response ] response - # @param [ Hash ] options - # - # @return [ Boolean ] - def exists_from_response?(response, options = {}) - load_from_response(response) - - @login ? true : false - end - - # Load the login and display_name from the response - # - # @param [ Typhoeus::Response ] response - # - # @return [ void ] - def load_from_response(response) - if response.code == 301 # login in location? - location = response.headers_hash['Location'] - - return if location.nil? || location.empty? - - @login = Existable.login_from_author_pattern(location) - @display_name = Existable.display_name_from_body( - Browser.get(location).body - ) - elsif response.code == 200 # login in body? - @login = Existable.login_from_body(response.body) - @display_name = Existable.display_name_from_body(response.body) - end - end - private :load_from_response - - # @param [ String ] text - # - # @return [ String ] The login - def self.login_from_author_pattern(text) - return unless text =~ %r{/author/([^/\b"']+)/?}i - - Regexp.last_match[1].force_encoding('UTF-8') - end - - # @param [ String ] body - # - # @return [ String ] The login - def self.login_from_body(body) - # Feed URL with Permalinks - login = WpUser::Existable.login_from_author_pattern(body) - - unless login - # No Permalinks - login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1] - login ? login.force_encoding('UTF-8') : nil - end - - login - end - - # @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it - # So it's forced to UTF-8 when this encoding is detected - # - # @param [ String ] body - # - # @return [ String ] The display_name - def self.display_name_from_body(body) - if title_tag = body[%r{<title>([^<]+)}i, 1] - title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT - title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s - # & are not decoded with Nokogiri - title_tag.gsub!('&', '&') - - # replace UTF chars like » with dummy character - title_tag.gsub!(/&#(\d+);/, '|') - - name = title_tag[%r{([^|«»]+) }, 1] - - return name.strip if name - end - end - - end -end +# encoding: UTF-8 + +class WpUser < WpItem + module Existable + + # @param [ Typhoeus::Response ] response + # @param [ Hash ] options + # + # @return [ Boolean ] + def exists_from_response?(response, options = {}) + load_from_response(response) + + @login ? true : false + end + + # Load the login and display_name from the response + # + # @param [ Typhoeus::Response ] response + # + # @return [ void ] + def load_from_response(response) + if response.code == 301 # login in location? + location = response.headers_hash['Location'] + + return if location.nil? || location.empty? + + @login = Existable.login_from_author_pattern(location) + @display_name = Existable.display_name_from_body( + Browser.get(location).body + ) + elsif response.code == 200 # login in body? + @login = Existable.login_from_body(response.body) + @display_name = Existable.display_name_from_body(response.body) + end + end + private :load_from_response + + # @param [ String ] text + # + # @return [ String ] The login + def self.login_from_author_pattern(text) + return unless text =~ %r{/author/([^/\b"']+)/?}i + + Regexp.last_match[1].force_encoding('UTF-8') + end + + # @param [ String ] body + # + # @return [ String ] The login + def self.login_from_body(body) + # Feed URL with Permalinks + login = WpUser::Existable.login_from_author_pattern(body) + + unless login + # No Permalinks + login = body[%r{([^<]+)}i, 1] + title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT + title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s + # & are not decoded with Nokogiri + title_tag.gsub!('&', '&') + + # replace UTF chars like » with dummy character + title_tag.gsub!(/&#(\d+);/, '|') + + name = title_tag[%r{([^|«»]+) }, 1] + + return name.strip if name + end + end + + end +end diff --git a/lib/common/models/wp_version.rb b/lib/common/models/wp_version.rb index 2c18161a..5cde2eca 100755 --- a/lib/common/models/wp_version.rb +++ b/lib/common/models/wp_version.rb @@ -1,38 +1,38 @@ -# encoding: UTF-8 - -require 'wp_version/findable' -require 'wp_version/output' - -class WpVersion < WpItem - extend WpVersion::Findable - include WpVersion::Output - - # The version number - attr_accessor :number - alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to? - - # @return [ Array ] - def allowed_options; super << :number << :found_from end - - def identifier - @identifier ||= number - end - - def db_file - @db_file ||= WORDPRESSES_FILE - end - - # @param [ WpVersion ] other - # - # @return [ Boolean ] - def ==(other) - number == other.number - end - - # @return [ Array ] All the stable versions from version_file - def self.all(versions_file = WP_VERSIONS_FILE) - Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node| - a << node.text.to_s - end - end -end +# encoding: UTF-8 + +require 'wp_version/findable' +require 'wp_version/output' + +class WpVersion < WpItem + extend WpVersion::Findable + include WpVersion::Output + + # The version number + attr_accessor :number + alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to? + + # @return [ Array ] + def allowed_options; super << :number << :found_from end + + def identifier + @identifier ||= number + end + + def db_file + @db_file ||= WORDPRESSES_FILE + end + + # @param [ WpVersion ] other + # + # @return [ Boolean ] + def ==(other) + number == other.number + end + + # @return [ Array ] All the stable versions from version_file + def self.all(versions_file = WP_VERSIONS_FILE) + Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node| + a << node.text.to_s + end + end +end diff --git a/lib/common/models/wp_version/findable.rb b/lib/common/models/wp_version/findable.rb index 94be20c1..1dfda5cd 100755 --- a/lib/common/models/wp_version/findable.rb +++ b/lib/common/models/wp_version/findable.rb @@ -130,8 +130,6 @@ class WpVersion < WpItem def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml) xml = xml(versions_xml) - # This wp_item will take care of encoding the path - # and replace variables like $wp-content$ & $wp-plugins$ wp_item = WpItem.new(target_uri, wp_content_dir: wp_content_dir, wp_plugins_dir: wp_plugins_dir) diff --git a/lib/environment.rb b/lib/environment.rb index 85ae571d..91e74e36 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -3,8 +3,8 @@ require 'rubygems' version = RUBY_VERSION.dup -if Gem::Version.create(version) < Gem::Version.create(1.9) - puts "Ruby >= 1.9 required to run wpscan (You have #{version})" +if Gem::Version.create(version) < Gem::Version.create(2.0) + puts "Ruby >= 2.0.0 required to run wpscan (You have #{version})" exit(1) end diff --git a/lib/wpscan/wp_target/wp_config_backup.rb b/lib/wpscan/wp_target/wp_config_backup.rb index 12c6b568..076eff9a 100644 --- a/lib/wpscan/wp_target/wp_config_backup.rb +++ b/lib/wpscan/wp_target/wp_config_backup.rb @@ -14,7 +14,7 @@ class WpTarget < WebSite queue_count = 0 backups.each do |file| - file_url = @uri.merge(URI.escape(file)).to_s + file_url = @uri.merge(url_encode(file)).to_s request = browser.forge_request(file_url) request.on_complete do |response| diff --git a/spec/lib/common/models/wp_item_spec.rb b/spec/lib/common/models/wp_item_spec.rb index 3af882d8..ed33df30 100644 --- a/spec/lib/common/models/wp_item_spec.rb +++ b/spec/lib/common/models/wp_item_spec.rb @@ -105,11 +105,6 @@ describe WpItem do @expected = 'plugins/readme.txt' end end - - it 'also encodes chars' do - @path = 'some dir with spaces' - @expected = 'some%20dir%20with%20spaces' - end end describe '#uri' do diff --git a/spec/shared_examples/wp_target/wp_config_backup.rb b/spec/shared_examples/wp_target/wp_config_backup.rb index 30eea4d2..0e1502fb 100644 --- a/spec/shared_examples/wp_target/wp_config_backup.rb +++ b/spec/shared_examples/wp_target/wp_config_backup.rb @@ -10,7 +10,7 @@ shared_examples 'WpTarget::WpConfigBackup' do # set all @config_backup_files to point to a 404 before :each do config_backup_files.each do |backup_file| - file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s + file_url = wp_target.uri.merge(url_encode(backup_file)).to_s stub_request(:get, file_url).to_return(status: 404) end @@ -24,7 +24,7 @@ shared_examples 'WpTarget::WpConfigBackup' do expected = [] config_backup_files.sample(1).each do |backup_file| - file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s + file_url = wp_target.uri.merge(url_encode(backup_file)).to_s expected << file_url stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php') @@ -40,7 +40,7 @@ shared_examples 'WpTarget::WpConfigBackup' do expected = [] config_backup_files.sample(2).each do |backup_file| - file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s + file_url = wp_target.uri.merge(url_encode(backup_file)).to_s expected << file_url stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')